3 Most of the implementations of Thun support
4 [BigNums](https://en.wikipedia.org/wiki/BigNum), either built-in or as
5 libraries, but some host languages and systems do not. In those cases it
6 would be well to have a pure-Joy implementation.
8 We can model bignums as a pair of a Boolean value for the sign and a list
9 of integers for the digits. The bool will be the first item on a list
10 followed by zero or more integer digits, with the Least Significant digit
11 at the top (closest to the head of the list.) E.g.:
15 Our *base* for the digits will be dictated by the size of the integers
16 supported by the host system. Let's imagine we're using 32-bit signed
17 ints, so our base will be not 10, but 2³¹. (We're ignoring the sign
23 So our digits are not 0..9, but 0..2147483647
27 We can `inscribe` a constant function `base` to keep this value handy.
30 joy? unit [base] swoncat
34 It's a little "wrong" to use the
35 dictionary to store values like this, however, this is how Forth does it
36 and if your design is good it works fine. Just be careful, and wash
39 This also permits a kind of parameterization. E.g. let's say we wanted
40 to use base 10 for our digits, maybe during debugging. All that requires
41 is to rebind the symbol `base` to 10.
45 ## Converting Between Host BigNums and Joy BigNums
47 We will work with one of the Joy interpreters that has bignums already so
48 we can convert "native" ints to our Joy bignums and vice versa. This
49 will be helpful to check our work. Later we can deal with converting to
50 and from strings (which this Joy doesn't have anyway, so it's probably
53 To get the sign bool we can just use `!-` ("not negative") and to get the
54 list of digits we repeatedly `divmod` the number by our `base`:
58 We will want the results in the opposite order, so let's define a little
59 helper function to do that:
61 [moddiv divmod swap] inscribe
65 [get-digit base moddiv] inscribe
67 We keep it up until we get to zero. This suggests a `while` loop:
69 [0 >] [get-digit] while
73 joy? 1234567890123456789012345678901234567890
74 1234567890123456789012345678901234567890
76 joy? [0 >] [get-digit] while
77 1312754386 1501085485 57659106 105448366 58 0
79 We need to `pop` at the end to ditch that zero.
81 [0 >] [get-digit] while pop
83 But we want these numbers in a list. The naive way using `infra`
84 generates them in the reverse order of what we would like.
86 joy? [1234567890123456789012345678901234567890]
87 [1234567890123456789012345678901234567890]
89 joy? [[0 >] [get-digit] while pop]
90 [1234567890123456789012345678901234567890] [[0 >] [get-digit] while pop]
93 [58 105448366 57659106 1501085485 1312754386]
95 We could just reverse the list, but it's more efficient to build the
96 result list in the order we want. We construct a simple recursive
97 function. (TODO: link to the recursion combinators notebook.)
99 The predicate will check that our number is yet positive:
103 When we find the zero we will discard it and start a list:
107 But until we do find the zero, get digits:
111 Once we have found all the digits and ditched the zero and put our
112 initial empty list on the stack we `cons` up the digits we have found:
118 joy? 1234567890123456789012345678901234567890
119 1234567890123456789012345678901234567890
121 joy? [0 <=] [pop []] [get-digit] [i cons] genrec
122 [1312754386 1501085485 57659106 105448366 58]
126 ### Representing Zero
128 This will return the empty list for zero:
130 joy? 0 [0 <=] [pop []] [get-digit] [i cons] genrec
133 I think this is better than returning `[0]` because that amounts to a
143 Let's `inscribe` this function under the name `digitalize`:
145 [digitalize [0 <=] [pop []] [get-digit] [i cons] genrec] inscribe
147 Putting it all together we have `!-` for the sign and `abs digitalize`
148 for the digits, followed by `cons`:
150 [!-] [abs digitalize] cleave cons
154 [to-bignum [!-] [abs digitalize] cleave cons] inscribe
156 ### Converting from Joy BigNums to Host BigNums
158 To convert a bignum into a host integer we need to keep a "power" value
159 on the stack, setting it up and discarding it at the end, as well as an
160 accumulator value starting at zero. We will deal with the sign bit later.
164 So the problem is to derive:
166 1 0 [digits...] [F] step
167 ------------------------------
173 ---------------------------------------
174 (power*base) (acc + (power*digit)
176 Now this is an interesting function. The first thing I noticed is that it
177 has two results that can be computed independently, suggesting a form
182 (Then I noticed that `power *` is a sub-function of both `G` and `H`, but
183 let's not overthink it, eh?)
185 So for the first result (the next power) we want:
195 Let's call this `add-digit`:
197 [add-digit [popop base *] [rolldown * +] clop popdd] inscribe
201 [true 1312754386 1501085485 57659106 105448366 58]
202 joy? rest 1 0 rolldown
204 1 0 [1312754386 1501085485 57659106 105448366 58]
206 joy? [add-digit] step
207 45671926166590716193865151022383844364247891968 1234567890123456789012345678901234567890
210 1234567890123456789012345678901234567890
214 [from-bignum′ rest 1 0 rolldown [add-digit] step popd] inscribe
218 joy? 1234567890123456789012345678901234567890 to-bignum
219 [true 1312754386 1501085485 57659106 105448366 58]
222 1234567890123456789012345678901234567890
226 ### What about that sign bit?
228 Time to deal with that.
230 Consider a Joy bignum:
232 [true 1312754386 1501085485 57659106 105448366 58]
234 To get the sign bit would just be `first`.
236 [true 1312754386 1501085485 57659106 105448366 58]
238 joy? [from-bignum′] [first] cleave
239 1234567890123456789012345678901234567890 true
241 Then use the sign flag to negate the int if the bignum was negative:
249 [from-bignum [from-bignum′] [first] cleave [neg] [] branch] inscribe
252 ## Our Source Code So Far
254 [base 2147483648] inscribe
255 [moddiv divmod swap] inscribe
256 [get-digit base moddiv] inscribe
257 [digitalize [0 <=] [pop []] [get-digit] [i cons] genrec] inscribe
258 [to-bignum [!-] [abs digitalize] cleave cons] inscribe
260 [add-digit [popop base *] [rolldown * +] clop popdd] inscribe
261 [from-bignum′.prep rest 1 0 rolldown] inscribe
262 [from-bignum′ from-bignum′.prep [add-digit] step popd] inscribe
263 [from-bignum [from-bignum′] [first] cleave [neg] [] branch] inscribe
266 ## Addition of Like Signs
270 Let's figure out how to add two lists of digits. We will assume that the
271 signs are the same (both lists of digits represent numbers of the same
272 sign, both positive or both negative.) We're going to want a recursive
273 function, of course, but it's not quite a standard *hylomorphism* for (at
276 - We're tearing down two lists simultaneously.
277 - They might not be the same length.
279 There are two base cases: two empty lists or one empty list, the
280 recursive branch is taken only if both lists are non-empty.
282 We will also need an inital `false` value for a carry flag. This implies
283 the following structure:
285 false rollup [add-digits.P] [add-digits.THEN] [add-digits.R0] [add-digits.R1] genrec
289 The situation will be like this, a Boolean flag followed by two lists of
292 bool [a ...] [b ...] add-digits.P
294 The predicate must evaluate to `false` *iff* both lists are non-`null`:
296 add-digits.P == [null] ii \/
300 On the non-recursive branch of the `genrec` we have to decide between
301 three cases, but because addition is commutative we can lump together the
304 bool [] [b ...] add-digits.THEN
305 bool [a ...] [] add-digits.THEN
307 bool [] [] add-digits.THEN
309 So we have an `ifte` expression:
311 add-digits.THEN == [add-digits.THEN.P] [add-digits.THEN.THEN] [add-digits.THEN.ELSE] ifte
313 Let's define the predicate:
315 add-digits.THEN.P == [null] ii /\
317 So `add-digits.THEN.THEN` deals with the case of both lists being empty,
318 and the `add-digits.THEN.ELSE` branch deals with one list of digits being
319 longer than the other.
323 In the cases where one of the two lists (but not both) is empty:
325 carry [a ...] [] add-digits.THEN.ELSE
326 carry [] [b ...] add-digits.THEN.ELSE
328 We first get rid of the empty list:
330 [null] [pop] [popd] ifte
332 ### ≡ `ditch-empty-list`
334 [ditch-empty-list [null] [pop] [popd] ifte] inscribe
336 add-digits.THEN.ELSE == ditch-empty-list add-digits.THEN.ELSE′
340 carry [n ...] add-digits.THEN.ELSE′
342 This is just `add-carry-to-digits` which we will derive in a moment, but
343 first a side-quest...
347 To get ahead of ourselves a bit, we will want some function
348 `add-with-carry` that accepts a bool and two ints and leaves behind a new
349 int and a new Boolean carry flag. With some abuse of notation we can
350 treat bools as ints (type punning as in Python) and write:
352 carry a b add-with-carry
353 ---------------------------------
356 (I find it interesting that this function accepts the carry from below
357 the int args but returns it above the result. Hmm...)
361 [bool-to-int [0] [1] branch] inscribe
363 We can use this function to convert the carry flag to an integer and then
364 add it to the sum of the two digits:
366 [bool-to-int] dipd + +
368 So the first part of `add-with-carry` is `[bool-to-int] dipd + +` to get
369 the total, then we need to do `base mod` to get the new digit and `base >=`
370 to get the new carry flag. Factoring give us:
374 Put it all together and we have:
376 [add-with-carry.0 [bool-to-int] dipd + +] inscribe
377 [add-with-carry.1 base [mod] [>=] clop] inscribe
378 [add-with-carry add-with-carry.0 add-with-carry.1] inscribe
380 ### Now back to `add-carry-to-digits`
382 This should be a very simple recursive function. It accepts a Boolean
383 `carry` flag and a non-empty list of digits (the list is only going to be
384 non-empty on the first iteration, after that we have to check it
385 ourselves because we may have emptied it of digits and still have a
386 `true` `carry` flag) and it returns a list of digits, consuming the carry
389 add-carry-to-digits == [actd.P] [actd.THEN] [actd.R0] [actd.R1] genrec
391 The predicate is the carry flag itself inverted:
395 The base case simply discards the carry flag:
401 add-carry-to-digits == [pop not] [popd] [actd.R0] [actd.R1] genrec
403 That leaves the recursive branch:
405 true [n ...] actd.R0 [add-carry-to-digits] actd.R1
409 true [] actd.R0 [add-carry-to-digits] actd.R1
411 We know that the Boolean value is `true`. We also know that the list will
412 be non-empty, but only on the first iteration of the `genrec`. It may be
413 that the list is empty on a later iteration.
415 The `actd.R0` function should check the list.
417 actd.R0 == [null] [actd.R0.THEN] [actd.R0.ELSE] ifte
421 true [] actd.R0.THEN [add-carry-to-digits] actd.R1
422 --------------------------------------------------------
423 1 false [] [add-carry-to-digits] i cons
425 What we're seeing here is that `actd.R0.THEN` leaves the empty list of
426 digits on the stack, converts the carry flag to `false` and leave 1 on
427 the stack to be picked up by `actd.R1` and `cons`'d onto the list of
428 digits (e.g.: 999 -> 1000, it's the new 1.)
436 actd.R0.THEN == popd 1 false rolldown
438 We have the results in this order `1 false []` rather than some other
439 arrangement to be compatible (same types and order) with the result of
440 the other branch, which we now derive.
442 ### If the list of digits isn't empty...
444 With `actd.R1 == i cons` as above we have:
446 true [a ...] actd.R0.ELSE [add-carry-to-digits] i cons
448 We want to get out that `a` value and use `add-with-carry` here:
450 true 0 a add-with-carry [...] [add-carry-to-digits] i cons
451 ----------------------------------------------------------------
452 (a+1) carry [...] [add-carry-to-digits] i cons
454 This leaves behind the new digit (a+1) for `actd.R1` and the new carry
455 flag for the next iteration.
457 So here is the specification of `actd.R0.ELSE`:
459 true [a ...] actd.R0.ELSE
460 -----------------------------------
461 true 0 a add-with-carry [...]
463 It accepts a Boolean value and a non-empty list on the stack and is
464 responsible for `uncons`'ing `a` and `add-with-carry` and the initial 0:
466 true [a ...] . 0 swap
467 true 0 [a ...] . uncons
468 true 0 a [...] . [add-with-carry] dip
469 true 0 a add-with-carry [...] .
473 [actd.R0.ELSE 0 swap uncons [add-with-carry] dip] inscribe
475 Putting it all together:
477 [bool-to-int [0] [1] branch] inscribe
478 [ditch-empty-list [null] [pop] [popd] ifte] inscribe
480 [add-with-carry.0 [bool-to-int] dipd + +] inscribe
481 [add-with-carry.1 base [mod] [>=] clop] inscribe
482 [add-with-carry add-with-carry.0 add-with-carry.1] inscribe
484 [actd.R0.THEN popd 1 false rolldown] inscribe
485 [actd.R0.ELSE 0 swap uncons [add-with-carry] dip] inscribe
486 [actd.R0 [null] [actd.R0.THEN] [actd.R0.ELSE] ifte] inscribe
488 [add-carry-to-digits [pop not] [popd] [actd.R0] [i cons] genrec] inscribe
491 We can set `base` to 10 to see it in action with familiar decimal digits:
493 joy? [base 10] inscribe
495 Let's add a carry to 999:
500 joy? add-carry-to-digits
503 Not bad! Recall that our digits are stored in with the Most Significant
504 Digit at the bottom of the list.
506 Let's add another carry:
511 joy? add-carry-to-digits
514 What if we make the just the first digit into 9?
522 joy? add-carry-to-digits
527 And adding `false` does nothing, yes?
532 joy? add-carry-to-digits
537 So that handles the cases where one of the two lists (but not both) is
540 add-digits.THEN.ELSE == ditch-empty-list add-carry-to-digits
544 If both lists are empty we discard one list and check the carry to
545 determine our result as described above:
547 bool [] [] add-digits.THEN.THEN
553 [] bool . [] [1 swons] branch
557 [] true . [] [1 swons] branch
562 [] false . [] [1 swons] branch
568 add-digits.THEN.THEN == pop swap [] [1 swons] branch
570 Here are the definitions, ready to `inscribe`:
572 [add-digits.THEN.THEN pop swap [] [1 swons] branch] inscribe
573 [add-digits.THEN.ELSE ditch-empty-list add-carry-to-digits] inscribe
574 [add-digits.THEN [[null] ii /\] [add-digits.THEN.THEN] [add-digits.THEN.ELSE] ifte] inscribe
578 Now we go back and derive the recursive branch that is taken only if both
581 bool [a ...] [b ...] add-digits.R0 [add-digits′] add-digits.R1
583 We just need to knock out those recursive branch functions
584 `add-digits.R0` and `add-digits.R1` and we're done.
586 First we will want to `uncons` the digits. Let's write a function that
596 joy? [uncons] ii swapd
601 We could call this `uncons-two`:
603 [uncons-two [uncons] ii swapd] inscribe
607 bool a b [...] [...] add-digits.R0′ [add-digits′] add-digits.R1
609 It's at this point that we'll want to employ the `add-with-carry`
612 bool a b [...] [...] [add-with-carry] dipd add-digits.R0″ [add-digits'] add-digits.R1
614 bool a b add-with-carry [...] [...] add-digits.R0″ [add-digits'] add-digits.R1
616 (a+b) bool [...] [...] add-digits.R0″ [add-digits'] add-digits.R1
618 If we postulate a `cons` in our `add-digits.R1` function...
620 (a+b) bool [...] [...] add-digits.R0″ [add-digits'] i cons
622 Then it seems like we're done? `add-digits.R0″` is nothing?
624 add-digits.R0 == uncons-two [add-with-carry] dipd
626 add-digits.R1 == i cons
630 add-digits == false rollup [add-digits.P] [add-digits.THEN] [add-digits.R0] [i cons] genrec
632 The source code so far is now:
634 [bool-to-int [0] [1] branch] inscribe
635 [ditch-empty-list [null] [pop] [popd] ifte] inscribe
636 [uncons-two [uncons] ii swapd] inscribe
638 [add-with-carry.0 [bool-to-int] dipd + +] inscribe
639 [add-with-carry.1 base [mod] [>=] clop] inscribe
640 [add-with-carry add-with-carry.0 add-with-carry.1] inscribe
642 [actd.R0.THEN popd 1 false rolldown] inscribe
643 [actd.R0.ELSE 0 swap uncons [add-with-carry] dip] inscribe
644 [actd.R0 [null] [actd.R0.THEN] [actd.R0.ELSE] ifte] inscribe
646 [add-carry-to-digits [pop not] [popd] [actd.R0] [i cons] genrec] inscribe
648 [add-digits.R0 uncons-two [add-with-carry] dipd] inscribe
650 [add-digits.THEN.THEN pop swap [] [1 swons] branch] inscribe
651 [add-digits.THEN.ELSE ditch-empty-list add-carry-to-digits] inscribe
652 [add-digits.THEN [[null] ii /\] [add-digits.THEN.THEN] [add-digits.THEN.ELSE] ifte] inscribe
654 [add-digits′ [[null] ii \/] [add-digits.THEN] [add-digits.R0] [i cons] genrec] inscribe
655 [add-digits false rollup add-digits′] inscribe
657 Let's set `base` to 10 and try it out:
659 joy? [base 10] inscribe
668 [5 4 3 2 1] [true 9 9 9]
689 There is one more thing we have to do to use this: we have to deal with
692 add-bignums [add-bignums.P] [add-bignums.THEN] [add-bignums.ELSE] ifte
694 To check are they the same sign?
698 [xor [] [not] branch] inscribe
699 [nxor xor not] inscribe
703 add-bignums.P == [first] ii nxor
705 If they are the same sign (both positive or both negative) we can use
706 `uncons` to keep one of the sign Boolean flags around and reuse it at the
707 end, and `rest` to discard the other, then `add-digits` to add the
708 digits, then `cons` that flag we saved onto the result digits list:
710 add-bignums.THEN == [uncons] dip rest add-digits cons
712 If they are not both positive or both negative then we negate one of them
713 and subtract instead (adding unlikes is actually subtraction):
715 add-bignums.ELSE == neg-bignum sub-bignums
719 [same-sign [first] ii xor not] inscribe
720 [add-like-bignums [uncons] dip rest add-digits cons] inscribe
722 [add-bignums [same-sign] [add-like-bignums] [neg-bignum sub-bignums] ifte] inscribe
724 But we haven't implemented `neg-bignum` or `sub-bignums` yet...
726 We'll get to those in a moment, but first an interlude.
728 ## Interlude: `list-combiner`
730 Let's review the form of our function `add-digits` (eliding the preamble
731 `false rollup`) and `add-digits.THEN`:
733 add-digits′ == [add-digits.P] [add-digits.THEN] [add-digits.R0] [add-digits.R1] genrec
735 add-digits.THEN == [add-digits.THEN.P] [add-digits.THEN.THEN] [add-digits.THEN.ELSE] ifte
739 add-digits.P == [null] ii \/
740 add-digits.THEN.P == [null] ii /\
742 Generalizing the names:
744 F == [P] [THEN] [R0] [R1] genrec
745 THEN == [THEN.P] [THEN.THEN] [THEN.ELSE] ifte
747 With auxiliary definitions:
749 null-two == [null] ii
750 both-null == null-two /\
751 either-or-both-null == null-two \/
755 F == [either-or-both-null] [THEN] [R0] [R1] genrec
756 THEN == [both-null] [THEN.THEN] [THEN.ELSE] ifte
760 F == [either-or-both-null] [[both-null] [THEN.THEN] [THEN.ELSE] ifte] [R0] [R1] genrec
762 This is a little awkward, so let's pretend that we have a new combinator
763 `two-list-genrec` that accepts four quotes and does `F`:
765 F == [THEN.THEN] [THEN.ELSE] [R0] [R1] two-list-genrec
767 So `THEN.THEN` handles the (non-recursive) case of both lists being
768 empty, `THEN.ELSE` handles the (non-recursive) case of one or the other
769 list being empty, and `R0 [F] R1` handles the (recursive) case of both
770 lists being non-empty.
772 Recall that our `R1` is just `i cons`, we can fold that in to the
773 definition of another new combinator that combines two lists into one:
775 list-combiner-genrec == [i cons] two-list-genrec
779 F == [both-empty] [one-empty] [both-non-empty] list-combiner-genrec
781 Then for `add-digits′` we would have:
783 both-empty == pop swap [] [1 swons] branch
784 one-empty == ditch-empty-list add-carry-to-digits
785 both-non-empty == uncons-two [add-with-carry] dipd
787 add-digits′ == [both-empty] [one-empty] [both-non-empty] list-combiner-genrec
789 Which would expand into:
791 add-digits′ == [either-or-both-null]
792 [[both-null] [both-empty] [one-empty] ifte]
797 It's pretty straight forward to make a functions that converts the three
798 quotes into the expanded form (a kind of "macro") but you might want to
799 separate that from the actual `genrec` evaluation. It would be better to
800 run the "macro" once, append the `[genrec]` quote to the resulting form,
801 and `inscribe` that, rather than putting the "macro" into the definition.
802 That way you avoid re-evaluating the "macro" on each iteration.
804 The simplification of the expanded form to the simpler version by coining
805 the `list-combiner-genrec` function is the "semantic compression" aspect
806 of factoring. If you choose your seams and names well, the code is
807 (relatively) self-descriptive.
809 In any event, now that we know what's going on, we don't actually need
810 the "macro", we can just write out the expanded version directly.
814 [null-two [null] ii] inscribe
815 [both-null null-two /\] inscribe
816 [either-or-both-null null-two \/] inscribe
818 [add-digits.both-empty pop swap [] [1 swons] branch] inscribe
819 [add-digits.one-empty ditch-empty-list add-carry-to-digits] inscribe
820 [add-digits.both-non-empty uncons-two [add-with-carry] dipd] inscribe
822 [add-digits′ [either-or-both-null] [[both-null] [add-digits.both-empty] [add-digits.one-empty] ifte] [add-digits.both-non-empty] [i cons] genrec] inscribe
827 Well, that was fun! And we'll reuse it in a moment when we derive `sub-bignums`.
828 But for now let's clear our palate with a nice simple function: `neg-bignum`.
830 To negate a Joy bignum you just invert the Boolean value at the head of the list.
832 neg-bignum == [not] infra
835 ## Subtraction of Like Signs
837 Subtraction is similar to addition in that it's a simple recursive algorithm that works digit-by-digit.
838 It has the same three cases as well, so we can reuse the `list-combiner-genrec` "macro" that
839 we specified (but did not yet derive) a moment ago.
841 sub-digits == initial-carry sub-digits'
842 sub-digits' == [both-empty] [one-empty] [both-non-empty] list-combiner-genrec
844 Okay, we're almost ready to implement subtraction, but there's a wrinkle!
845 When we subtract a smaller (absolute) value from a larger (absolute)
846 value there's no problem:
850 But I don't know the algorithm to subtract a larger number from a smaller
855 The answer is -5, of course, but what's the algorithm? How to make the
856 computer figure that out?
858 We make use of the simple algebraic identity:
862 So if we want to subtract a larger number `a` from a smaller one `b` we
863 can instead subtract the smaller from the larger and invert the sign:
867 To do this we need a function `gt-digits` that will tell us which of two
868 digit lists represents the larger integer.
873 Gentle reader, it was at this time that I realized I don't have a list length function yet!
875 [length [pop ++] step_zero] inscribe
877 ### Comparing Lists of Integers
879 We only need to compare the digits of the numbers if one list of digits is longer than the other.
880 We could use `length` on both lists and then `cmp`:
884 If the top list is longer than the second list the function should return `true`,
885 and if the top list is shorter than the second list the function should return `false`,
887 dup2 [length] ii [true] [E] [false] cmp
889 If both lists are non-empty we have to compare digits starting with the ends.
891 E == zip reverse compare-digits
893 But this is inefficient! The `length` function will traverse each list once,
894 then the `zip` function will traverse both lists and build a new list of pairs,
895 then the `reverse` function will traverse that list and rebuild it,
896 then the `compare-digits` will traverse that list looking for unequal pairs...
897 It's a lot of work that we don't really want or need to do.
899 ### A More Efficient Comparison
901 What we really want is a function that iterates through both lists together
904 - If the top list is empty and the second list isn't then the whole function should return `false`.
905 - If the top list is non-empty and the second list is empty then the whole function should return `true`.
906 - If both lists are empty we start checking pairs of digits (that we got from the recursive case.)
907 - If both lists are non-empty we `uncons-two` digits for later comparison and recur.
909 Let's start designing the function.
915 We will need a list on which to put pairs
920 ----------------------
923 It's a recursive function:
925 F′ == [P] [THEN] [R0] [R1] genrec
927 The predicate tests whether both of the two input lists are non-empty:
931 (We defined this as `either-or-both-null` above.)
933 Let's look at the recursive case first:
935 [...] [b ...] [a ...] R0 [F] R1
936 -------------------------------------------
937 [[b a] ...] [...] [...] F
939 So `R0` transfers items from the source list to the pairs list,
940 let's call it `shift-pair`:
942 [...] [b ...] [a ...] shift-pair
943 --------------------------------------
944 [[b a] ...] [...] [...]
946 I'll leave that as an exercise for the reader for now.
948 `R1` is just `i` (this is a `tailrec` function.)
950 F == <<{} [either-or-both-null] [THEN] [shift-pair] tailrec
952 Now let's derive `THEN`, there are three cases:
954 [pairs...] [] [] THEN
955 [pairs...] [b ...] [] THEN
956 [pairs...] [] [a ...] THEN
958 We can model this as a pair of `ifte` expressions, one nested in the other:
960 [P] [THEN′] [[P′] [THEN′′] [ELSE′] ifte] ifte
962 But in the event we won't need the inner `ifte`, see below.
964 The first predicate should check if both lists are empty:
968 (We defined this as `both-null` above.)
970 If both lists are empty we check the pairs:
972 THEN′ == popop compare-pairs
974 Otherwise if the top list is empty we return `false`, otherwise `true`,
975 and since this is a destructive operation we don't have to use `ifte` here:
977 THEN == [both-null] [popop compare-pairs] [popopd null] ifte
979 F == <<{} [either-or-both-null] [THEN] [shift-pair] tailrec
981 Now we just have to write `compare-pairs` (and `shift-pair`.)
985 [pair-up unit cons] inscribe
987 [shift-pair uncons-two [pair-up swons] dipd] inscribe
992 This function takes a list of pairs of digits (ints) and compares
993 them until it finds an unequal pair or runs out of pairs.
995 We are implementing "greater than" (b > a) so if we run out of digits
996 that means the two numbers were equal, and so we return `false`:
998 F == [null] [pop false] [R0] [R1] genrec
1000 That leaves the recursive branch:
1002 [[b a] ...] R0 [F] R1
1004 I figure we're going to want some sort of `ifte`. (But this turns out to
1007 [[b a] ...] [P] [THEN] [F] ifte
1009 if b > a we can stop and report `true`, otherwise we discard the pair and recur.
1015 Note that that fails to discard the pair!
1017 [[b a] ...] [first i >] [pop true] [F] ifte
1019 If b <= a this would just re-run `F` with the same list!
1021 Oops! D'oh! I didn't think it through properly.
1023 We need to distinguish all three case (> = <) so we want to use `cmp`:
1025 [[b a] ...] unswons i [G] [F] [L] cmp
1029 [...] b a [G] [F] [L] cmp
1031 Note that we recur on equality (that is our `E` function is just `F` itself).
1033 If we the digits are not equal we can quit the loop with the answer:
1035 [...] b a [pop true] [F] [pop false] cmp
1039 R0 == unswons i [pop true]
1041 R1 == [pop false] cmp
1043 ### ≡ `compare-pairs`
1045 [compare-pairs.R0 unswons i [pop true]] inscribe
1046 [compare-pairs.R1 [pop false] cmp] inscribe
1047 [compare-pairs [null] [pop false] [compare-pairs.R0] [compare-pairs.R1] genrec] inscribe
1051 [gt-digits.THEN [both-null] [popop compare-pairs] [popopd null] ifte] inscribe
1052 [gt-digits <<{} [either-or-both-null] [gt-digits.THEN] [shift-pair] tailrec] inscribe
1055 ### Almost Ready to Subtract
1057 Now we can subtract, we just have to remember to invert the sign bit if we swap the digit lists.
1059 Maybe something like:
1061 check-gt == [gt-digits] [swap true] [false] ifte
1063 To keep the decision around as a Boolean flag? We can `xor` it with the sign bit?
1065 Let's start with two numbers on the stack, with the same sign:
1067 [bool int int int] [bool int int int]
1069 Then we keep one of the sign Booleans around and discard the other:
1071 [bool int int int] [bool int int int] [uncons] dip rest
1072 [bool int int int] uncons [bool int int int] rest
1073 bool [int int int] [bool int int int] rest
1074 bool [int int int] [int int int]
1076 So what we really want to do is `swap` and `not`:
1078 check-gt == [gt-digits] [swap [not] dipd] [] ifte
1080 ### ≡ `extract-sign`
1082 [extract-sign [uncons] dip rest] inscribe
1086 [check-gt [gt-bignum] [swap [not] dipd] [] ifte] inscribe
1089 ### Subtraction, at last...
1091 So now that we can compare digit lists to see if one is larger than the other
1092 we can subtract (inverting the sign if necessary) much like we did addition:
1094 sub-bignums == [same-sign] [sub-like-bignums] [1 0 /] ifte
1096 sub-like-bignums == extract-sign check-gt sub-digits cons
1098 sub-digits == initial-carry sub-digits'
1100 initial-carry == false rollup
1103 both-empty == pop swap [] [1 swons] branch
1104 one-empty == ditch-empty-list sub-carry-from-digits
1105 both-non-empty == uncons-two [sub-with-carry] dipd
1107 sub-digits′ == [both-empty] [one-empty] [both-non-empty] list-combiner-genrec
1109 Which would expand into:
1111 sub-digits′ == [either-or-both-null]
1112 [[both-null] [both-empty] [one-empty] ifte]
1117 sub-digits′ == [either-or-both-null] [[both-null] [both-empty] [ditch-empty-list sub-carry-from-digits] ifte] [uncons-two [sub-with-carry] dipd] [i cons] genrec
1121 We just need to define the pieces.
1123 ### ≡ `sub-with-carry`
1125 We know we will never be subtracting a larger (absolute) number from a smaller (absolute) number (they might be equal)
1126 so the carry flag will never be true *at the end of a digit list subtraction.*
1128 carry a b sub-with-carry
1129 ------------------------------
1130 (a-b-carry) new-carry
1132 [sub-with-carry.0 - swap [] [--] branch] inscribe
1133 [sub-with-carry.1 [base + base mod] [0 <] cleave] inscribe
1134 [sub-with-carry sub-with-carry.0 sub-with-carry.1] inscribe
1137 ### `sub-carry-from-digits`
1139 Should be easy to make modeled on `add-carry-to-digits`, another very simple recursive function.
1140 The predicate, base case, and `R1` are the same:
1142 carry [n ...] sub-carry-from-digits
1143 carry [n ...] [pop not] [popd] [R0] [i cons] genrec
1145 That leaves the recursive branch:
1147 true [n ...] R0 [sub-carry-from-digits] i cons
1151 true [] R0 [sub-carry-from-digits] i cons
1153 **Except** that this latter case should should never happen when subtracting,
1154 because we already made sure that we're only ever subtracting a number less than or equal to the, uh,
1155 number we are subtracting from.
1157 true [a ...] R0 [sub-carry-from-digits] i cons
1158 ----------------------------------------------------------------
1159 true 0 a sub-with-carry [...] [sub-carry-from-digits] i cons
1160 ------------------------------------------------------------------
1161 (a-1) carry [...] [sub-carry-from-digits] i cons
1163 It would work like this:
1166 true [a ...] 0 swap uncons [sub-with-carry] dip
1167 true 0 [a ...] uncons [sub-with-carry] dip
1168 true 0 a [...] [sub-with-carry] dip
1169 true 0 a sub-with-carry [...]
1171 R0 == 0 swap uncons [sub-with-carry] dip
1173 But there's a problem! This winds up subtracting `a` from 0 rather than the other way around:
1175 R0 == uncons 0 swap [sub-with-carry] dip
1177 ### ≡ `sub-carry-from-digits`
1179 [sub-carry-from-digits.R0 uncons 0 swap [sub-with-carry] dip] inscribe
1180 [sub-carry-from-digits [pop not] [popd] [sub-carry-from-digits.R0] [i cons] genrec] inscribe
1184 joy? clear false [3 2 1] sub-carry-from-digits
1187 joy? clear true [0 1] sub-carry-from-digits
1190 joy? clear true [3 2 1] sub-carry-from-digits
1193 joy? clear true [0 0 1] sub-carry-from-digits
1196 But what about those leading zeroes?
1198 ### ≡ `cons-but-not-leading-zeroes` and `sub-carry-from-digits`
1200 We could use a version of `cons` that refuses to put 0 onto an empty list?
1202 [cons-but-not-leading-zeroes [[bool] ii \/ not] [popd] [cons] ifte] inscribe
1203 [sub-carry-from-digits [pop not] [popd] [sub-carry-from-digits.R0] [i cons-but-not-leading-zeroes] genrec] inscribe
1207 joy? clear true [0 1] sub-carry-from-digits
1210 joy? clear true [0 0 1] sub-carry-from-digits
1216 # ======================================================
1226 [sub-like-bignums [uncons] dip rest check-gt sub-digits cons] inscribe
1227 [sub-digits initial-carry sub-digits'] inscribe
1229 [sub-carry-from-digits]
1232 build-two-list-combiner
1242 true [3 2 1] [6 5 4]
1245 true [3 2 1] [6 5 4]
1249 check-gt initial-carry
1252 false false [6 5 4] [3 2 1]
1264 12345 to-bignum 109 to-bignum
1267 [true 5 4 3 2 1] [true 9 0 1]
1294 [neg-bignum [not] infra] inscribe
1308 to-bignum neg-bignum from-bignum
1315 to-bignum neg-bignum from-bignum
1323 [sub-bignums [same-sign] [sub-like-bignums] [neg-bignum add-like-bignums] ifte] inscribe
1324 [add-bignums [same-sign] [add-like-bignums] [neg-bignum sub-like-bignums] ifte] inscribe
1338 ## Appendix: Source Code
1341 [ditch-empty-list [bool] [popd] [pop] ifte]
1342 [bool-to-int [0] [1] branch]
1343 [uncons-two [uncons] ii swapd]
1344 [sandwich swap [cons] dip swoncat]
1346 [digitalize [0 <=] [pop []] [base divmod swap] [i cons] genrec]
1347 [to-bignum [!-] [abs digitalize] cleave cons]
1349 [prep rest 1 0 rolldown]
1350 [from-bignum′ [next-digit] step popd]
1351 [next-digit [increase-power] [accumulate-digit] clop popdd]
1352 [increase-power popop base *]
1353 [accumulate-digit rolldown * +]
1355 [sign-int [first] [prep from-bignum′] cleave]
1356 [neg-if-necessary swap [neg] [] branch]
1357 [from-bignum sign-int neg-if-necessary]
1359 [add-with-carry _add-with-carry0 _add-with-carry1]
1360 [_add-with-carry0 [bool-to-int] dipd + +]
1361 [_add-with-carry1 base [mod] [>=] clop]
1363 [add-carry-to-digits [pop not] [popd] [actd.R0] [i cons] genrec]
1364 [actd.R0 [bool] [actd.R0.then] [actd.R0.else] ifte]
1365 [actd.R0.else popd 1 false rolldown]
1366 [actd.R0.then 0 swap uncons [add-with-carry] dip]
1368 [add-digits initial-carry add-digits']
1369 [initial-carry false rollup]
1371 [add-digits' [P] [THEN] [R0] [R1] genrec]
1373 [THEN [P'] [THEN'] [ELSE] ifte]
1374 [R0 uncons-two [add-with-carry] dipd]
1377 [THEN' ditch-empty-list add-carry-to-digits]
1378 [ELSE pop swap [] [1 swons] branch]
1380 [same-sign [first] ii xor not]
1381 [add-like-bignums [uncons] dip rest add-digits cons]
1382 [add-bignums [same-sign] [add-like-bignums] [neg-bignum sub-like-bignums] ifte]
1384 [build-two-list-combiner _btlc0 _btlc1 [i cons]]
1385 [_btlc0.0 [[ditch-empty-list] swoncat] dip]
1386 [_btlc0.1 [pop] swoncat]
1387 [_btlc0.3 [_btlc0.0 _btlc0.1] dip]
1388 [_btlc0.4 [uncons-two] [dipd] sandwich]
1389 [_btlc0 _btlc0.3 _btlc0.4]
1390 [_btlc1 [[ifte] ccons [P'] swons [P] swap] dip]
1392 [carry [] [1 swons] branch]
1394 [compare-pairs [bool not] [pop false] [[first [>=] infrst] [pop true]] [[rest] swoncat ifte] genrec]
1395 [xR1 uncons-two [unit cons swons] dipd]
1396 [xP [bool] ii & not]
1397 [BASE [bool] [popop pop true] [[pop bool] [popop pop false] [popop compare-pairs] ifte] ifte]
1398 [gt-bignum <<{} [xP] [BASE] [xR1] tailrec]
1399 [check-gt [gt-bignum] [swap [not] dipd] [] ifte]
1403 [sub-carry-from-digits [pop not] [popd] [_scfd_R0] [i cons-but-not-leading-zeroes] genrec] inscribe
1404 [_scfd_R0 uncons 0 swap [sub-with-carry] dip] inscribe
1405 [cons-but-not-leading-zeroes [P'] [cons] [popd] ifte]
1407 [sub-with-carry _sub-with-carry0 _sub-with-carry1]
1408 [_sub-with-carry0 rolldown bool-to-int [-] ii]
1409 [_sub-with-carry1 [base + base mod] [0 <] cleave]
1411 [sub-like-bignums [uncons] dip rest check-gt sub-digits cons]
1412 [sub-digits initial-carry sub-digits']
1414 enstacken [inscribe] step
1416 [add-carry-to-digits]
1419 build-two-list-combiner
1420 [genrec] ccons ccons
1421 [add-digits'] swoncat
1424 [sub-carry-from-digits]
1427 build-two-list-combiner
1428 [genrec] ccons ccons
1429 [sub-digits'] swoncat
1435 So far I have three formats for Joy source:
1437 - `def.txt` is a list of definitions (UTF-8), one per line, with no special marks.
1438 - `foo ≡ bar baz...` lines in the `joy.py` embedded definition text, because why not? (Sometimes I use `==` instead of `≡` mostly because some tools can't handle the Unicode glyph. Like converting this notebook to PDF via LaTeX just omitted them.)
1439 - `[name body] inscribe` Joy source code that literally defines new words in the dictionary at runtime. A text of those commands can be fed to the interpreter to customize it without any special processing (like the other two formats require.)
1441 So far I prefer the `def.txt` style but that makes it tricky to embed them automatically into the `joy.py` file.
1445 We have `i cons` but that's pretty tight already, eh?
1447 However, `[i cons] genrec` is an interesting combinator. It's almost `tailrec` with that `i` combinator for the recursion, but then `cons` means it's a list-builder (an *anamorphism* if you go for that sort of thing.)
1449 simple-list-builder == [i cons] genrec