From 805ec23e113e616b6b269bd537f9b0d159dc090b Mon Sep 17 00:00:00 2001 From: sforman Date: Fri, 18 Aug 2023 16:57:54 -0700 Subject: [PATCH] Edits on BigNums notebook. --- docs/html/notebooks/BigInts.html | 958 ++++++---------------- docs/source/notebooks/BigInts.md | 1301 ++++++------------------------ 2 files changed, 457 insertions(+), 1802 deletions(-) diff --git a/docs/html/notebooks/BigInts.html b/docs/html/notebooks/BigInts.html index e4077ff..03a19f4 100644 --- a/docs/html/notebooks/BigInts.html +++ b/docs/html/notebooks/BigInts.html @@ -666,798 +666,304 @@ the "macro", we can just write out the expanded version directly.

[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 -

===================================================================================

neg-bignum

Well, that was fun! And we'll reuse it in a moment when we derive sub-bignums. -But for now, let's clear our palate with a nice simple function: neg-bignum.

+But for now let's clear our palate with a nice simple function: neg-bignum.

To negate a Joy bignum you just invert the Boolean value at the head of the list.

neg-bignum == [not] infra
 
-

Subtraction of Like Signs sub-digits

-

Subtraction is similar to addition in that it's a simple recursive algorithm that works digit-by-digit. It has the same four cases as well, we can reuse P and P'.

-
initial-carry == false rollup
-  sub-digits' == [P] [sub.THEN] [sub.R0] [sub.R1] genrec
-   sub-digits == initial-carry add-digits'
-     sub.THEN == [P'] [sub.THEN'] [sub.ELSE] ifte
+

Subtraction of Like Signs

+

Subtraction is similar to addition in that it's a simple recursive algorithm that works digit-by-digit. +It has the same three cases as well, so we can reuse the list-combiner-genrec "macro" that +we specified (but did not yet derive) a moment ago.

+
   sub-digits == initial-carry sub-digits'
+  sub-digits' == [both-empty] [one-empty] [both-non-empty] list-combiner-genrec
 
-

Refactoring For The Win

-

We noted above that the algorithm for subtraction is similar to that for addition. Maybe we can reuse more than just P and P'? In fact, I think we could refactor (prematurely, two cases is one too few) something like this?

-
             [sub.THEN'] [sub.ELSE] [sub.R0] [sub.R1] foo
----------------------------------------------------------------------
-   [P] [[P'] [sub.THEN'] [sub.ELSE] ifte] [sub.R0] [sub.R1] genrec
-
-

or just

-
             [THEN] [ELSE] [R0] [R1] foo
-----------------------------------------------------
-   [P] [[P'] [THEN] [ELSE] ifte] [R0] [R1] genrec
-
-

eh?

-

foo is something like:

-
F == [ifte] ccons [P'] swons
-G == [F] dipdd
-
-[THEN] [ELSE] [R0] [R1] [F] dipdd foo'
-[THEN] [ELSE] F [R0] [R1] foo'
-[THEN] [ELSE] [ifte] ccons [P'] swons [R0] [R1] foo'
-[[THEN] [ELSE] ifte] [P'] swons [R0] [R1] foo'
-[[P'] [THEN] [ELSE] ifte] [R0] [R1] foo'
-
-

That leaves [P]...

-
F == [ifte] ccons [P'] swons [P] swap
-G == [F] dipdd
-
-[THEN] [ELSE] [ifte] ccons [P'] swons [P] swap [R0] [R1] foo'
-[[THEN] [ELSE] ifte] [P'] swons [P] swap [R0] [R1] foo'
-[[P'] [THEN] [ELSE] ifte] [P] swap [R0] [R1] foo'
-[P] [[P'] [THEN] [ELSE] ifte] [R0] [R1] genrec
-
-

Ergo:

-
  F == [ifte] ccons [P'] swons [P] swap
-foo == [F] dipdd genrec
-combine-two-lists == [i cons] foo
-
-

-and-

-
add-digits' == [one-empty-list]
-               [both-empty]
-               [both-full]
-               combine-two-lists
-
-one-empty-list == ditch-empty-list add-carry-to-digits
-both-empty == pop swap carry
-both-full == uncons-two [add-with-carry] dipd
-
-

This illustrates how refactoring creates denser yet more readable code.

-

But this doesn't go quite far enough, I think.

-
R0 == uncons-two [add-with-carry] dipd
-
-

I think R0 will pretty much always do:

-
uncons-two [combine-two-values] dipd
-
-

And so it should be refactored further to something like:

-
         [F] R0
--------------------------
-   uncons-two [F] dipd
-
-

And then add-digits' becomes just:

-
add-digits' == [one-empty-list]
-               [both-empty]
-               [add-with-carry]
-               combine-two-lists
-
-

If we factor ditch-empty-list out of one-empty-list, and pop from both-empty:

-
add-digits' == [add-carry-to-digits]
-               [swap carry]
-               [add-with-carry]
-               combine-two-lists
-
-

Let's figure out the new form.

-
   [ONE-EMPTY] [BOTH-EMPTY] [COMBINE-VALUES] foo
----------------------------------------------------
-   [P]
-   [
-       [P']
-       [ditch-empty-list ONE-EMPTY]
-       [pop BOTH-EMPTY]
-       ifte
-   ]
-   [uncons-two [COMBINE-VALUES] dipd]
-   [i cons] genrec
-
-

eh?

-

Let's not over think it.

-
   [ONE-EMPTY] [ditch-empty-list] swoncat [BOTH-EMPTY] [pop] swoncat [COMBINE-VALUES] 
-   [ditch-empty-list ONE-EMPTY] [pop BOTH-EMPTY] [COMBINE-VALUES]
-
-

With:

-
   [C] [A] [B] sandwich
---------------------------
-        [A [C] B]
-
-

Joy -[sandwich swap [cons] dip swoncat] inscribe

-

Joy -clear [B] [A] [C]

-
[B] [A] [C]
-
-

Joy -sandwich

-
[A [B] C]
-
-

So to get from

-
[A] [B] [C]
-
-

to:

-
[ditch-empty-list A] [pop B] [uncons-two [C] dipd]
-
-

we use:

-
[[[ditch-empty-list] swoncat] dip [pop] swoncat] dip [uncons-two] [dipd] sandwich
-
-

It's gnarly, but simple:

-

```Joy -clear -[_foo0.0 [[ditch-empty-list] swoncat] dip] inscribe -[_foo0.1 [pop] swoncat] inscribe -[_foo0.3 [_foo0.0 _foo0.1] dip] inscribe -[_foo0.4 [uncons-two] [dipd] sandwich] inscribe -[_foo0 _foo0.3 _foo0.4] inscribe

-

[_foo1 [ - [ifte] ccons - [P'] swons - [P] swap - ] dip -] inscribe -```

-

Joy -[A] [B] [C] _foo0

-
[ditch-empty-list A] [pop B] [uncons-two [C] dipd]
-
-

Joy -_foo1

-
[P] [[P'] [ditch-empty-list A] [pop B] ifte] [uncons-two [C] dipd]
-
-

Joy -clear -[add-carry-to-digits] -[swap carry] -[add-with-carry] -_foo0 _foo1

-
[P] [[P'] [ditch-empty-list add-carry-to-digits] [pop swap carry] ifte] [uncons-two [add-with-carry] dipd]
-
-

Compare the above with what we wanted:

-
[P]
-[
-   [P']
-   [ditch-empty-list ONE-EMPTY]
-   [pop BOTH-EMPTY]
-   ifte
-]
-[uncons-two [COMBINE-VALUES] dipd]
-
-

Allwe need to do is add:

-
[i cons] genrec
-
-

```Joy -clear

-

[3 2 1] [6 5 4] initial-carry

-

[add-carry-to-digits] -[swap carry] -[add-with-carry] -_foo0 _foo1 -```

-
false [3 2 1] [6 5 4] [P] [[P'] [ditch-empty-list add-carry-to-digits] [pop swap carry] ifte] [uncons-two [add-with-carry] dipd]
-
-

Joy -[i cons] genrec

-
[9 7 5]
-
-

Joy -clear -[build-two-list-combiner _foo0 _foo1 [i cons]] inscribe -[combine-two-lists [add-carry-to-digits] [swap carry] [add-with-carry] build-two-list-combiner] inscribe

-

```Joy -clear

-

[3 2 1] [6 5 4] initial-carry -combine-two-lists -```

-
false [3 2 1] [6 5 4] [P] [[P'] [ditch-empty-list add-carry-to-digits] [pop swap carry] ifte] [uncons-two [add-with-carry] dipd] [i cons]
-
-

Joy -genrec

-
[9 7 5]
-
-

Joy -[base 10] inscribe

-
[9 7 5]
-
-

```Joy -clear

-

123456 to-bignum

-

```

-
[true 6 5 4 3 2 1]
-
-

Joy -clear

-

So that's nice.

-

In order to avoid the overhead of rebuilding the whole thing each time we could pre-compute the function and store it in the dictionary.

-

Joy -[add-carry-to-digits] -[swap carry] -[add-with-carry] -build-two-list-combiner

-
[P] [[P'] [ditch-empty-list add-carry-to-digits] [pop swap carry] ifte] [uncons-two [add-with-carry] dipd] [i cons]
-
-

Now grab the definition, add the genrec and symbol (name) and inscribe it:

-

Joy -[genrec] ccons ccons [add-digits'] swoncat

-
[add-digits' [P] [[P'] [ditch-empty-list add-carry-to-digits] [pop swap carry] ifte] [uncons-two [add-with-carry] dipd] [i cons] genrec]
-
-

Joy - inscribe

-

Try it out...

-

Joy -false [3 2 1] [4 3 2] add-digits'

-
[7 5 3]
-
-

Joy -false swap base -- unit

-
false [7 5 3] [9]
-
-

Joy -add-digits'

-
[6 6 3]
-
-

Joy -clear

-

Demonstrate add-bignums

-

Joy -1234 999 [to-bignum] ii

-
[true 4 3 2 1] [true 9 9 9]
-
-

Joy -add-bignums

-
[true 3 3 2 2]
-
-

Joy -from-bignum

-
2233
-
-

Joy -1234 999 +

-
2233 2233
-
-

Joy -clear

-

Subtracting

-

Okay, we're almost ready to implement subtraction, but there's a wrinkle! When we subtract a smaller (absolute) value from a larger (absolute) value there's no problem:

+

Okay, we're almost ready to implement subtraction, but there's a wrinkle! +When we subtract a smaller (absolute) value from a larger (absolute) +value there's no problem:

10 - 5 = 5
 
-

But I don't know the algorithm to subtract a larger number from a smaller one:

+

But I don't know the algorithm to subtract a larger number from a smaller +one:

5 - 10 = ???
 
-

The answer is -5, of course, but what's the algorithm? How to make the computer figure that out? We make use of the simple algebraic identity:

+

The answer is -5, of course, but what's the algorithm? How to make the +computer figure that out?

+

We make use of the simple algebraic identity:

a - b = -(b - a)
 
-

So if we want to subtract a larger number a from a smaller one b we can instead subtract the smaller from the larger and invert the sign:

+

So if we want to subtract a larger number a from a smaller one b we +can instead subtract the smaller from the larger and invert the sign:

5 - 10 = -(10 - 5)
 
-

To do this we need a function gt-digits that will tell us which of two digit lists represents the larger integer.

-

gt-digits

-

I just realized I don't have a list length function yet!

-

Joy -[length [pop ++] step_zero] inscribe

-

Joy -clear -[] length

-
0
+

To do this we need a function gt-digits that will tell us which of two +digit lists represents the larger integer.

+

length

+

Gentle reader, it was at this time that I realized I don't have a list length function yet!

+
[length [pop ++] step_zero] inscribe
 
-

Joy -clear -[this is a list] length

-
4
+

Comparing Lists of Integers

+

We only need to compare the digits of the numbers if one list of digits is longer than the other. +We could use length on both lists and then cmp:

+
a b [G] [E] [L] cmp
 
-

Joy -clear -[1 2 3] [4 5] over over [length] app2

-
[1 2 3] [4 5] 3 2
+

If the top list is longer than the second list the function should return true, +and if the top list is shorter than the second list the function should return false,

+
dup2 [length] ii [true] [E] [false] cmp
 
-

Joy -[swap][6][7]cmp

-
[4 5] [1 2 3]
+

If both lists are non-empty we have to compare digits starting with the ends.

+
E == zip reverse compare-digits
 
-

what about a function that iterates through two lists until one or the other ends, or they end at the same time (same length) and we walk back through comparing the digits?

-

Joy -clear

-

Joy -[1 2 3] [4 5 6] [bool] ii &

-
true
+

But this is inefficient! The length function will traverse each list once, +then the zip function will traverse both lists and build a new list of pairs, +then the reverse function will traverse that list and rebuild it, +then the compare-digits will traverse that list looking for unequal pairs... +It's a lot of work that we don't really want or need to do.

+

A More Efficient Comparison

+

What we really want is a function that iterates through both lists together +and:

+
    +
  • If the top list is empty and the second list isn't then the whole function should return false.
  • +
  • If the top list is non-empty and the second list is empty then the whole function should return true.
  • +
  • If both lists are empty we start checking pairs of digits (that we got from the recursive case.)
  • +
  • If both lists are non-empty we uncons-two digits for later comparison and recur.
  • +
+

Let's start designing the function.

+
   [...] [...] F
+-------------------
+       bool
 
-

Joy -clear -[1 2 3] [4 5 6] -[[bool] ii | not] -[pop] -[uncons-two] -[i [unit cons] dip cons] -genrec

-
[[1 4] [2 5] [3 6]]
-
-

Joy -clear -[1 2 3] [4 5 6] -[[bool] ii | not] -[pop] -[uncons-two] -[i [unit cons] dip cons] -genrec

-
[[1 4] [2 5] [3 6]]
-
-

Joy -clear

-

So I guess that's zip?

-

But we want something a little different.

-

It's a weird function: compare lengths, if they are the same length then compare contents pairwise from the end.

-

if the first list is empty and the second list isn't then the whole function should return false

-

if the first list is non-empty and the second list is empty then the whole function should return true

-

if both lists are non-empty we uncons some digits for later comparison? Where to put them? Leave them on the stack? What about short-circuits?

-

if both lists are empty we start comparing uncons'd pairs until we find an un-equal pair or run out of pairs.

-

if we run out of pairs before we find an unequal pair then the function returns true (the numbers are identical, we should try to shortcut the actual subtraction here, but let's just get it working first, eh?)

-

if we find an unequal pair we return a>b and discard the rest of the pairs. Or maybe this all happens in some sort of infra first situation?

-

So the predicate will be [bool] ii & not, if one list is longer than the other we are done. -We postulate a third list to contain the pairs:

-
[] [3 2 1] [4 5 6] [P] [BASE] [R0] [R1] genrec
-
-

The recursive branch seems simpler to figure out:

-
[] [3 2 1] [4 5 6] R0 [F] R1
+

We will need a list on which to put pairs

+
F == <<{} F′
 
-uncons-two [unit cons swons] dipd [F] i
+   [] [...] [...] F′
+----------------------
+        bool
+
+

It's a recursive function:

+
F′ == [P] [THEN] [R0] [R1] genrec
+
+

The predicate tests whether both of the two input lists are non-empty:

+
P = null-two \/
+
+

(We defined this as either-or-both-null above.)

+

Let's look at the recursive case first:

+
   [...] [b ...] [a ...] R0 [F] R1
+-------------------------------------------
+      [[b a] ...] [...] [...] F
+
+

So R0 transfers items from the source list to the pairs list, +let's call it shift-pair:

+
   [...] [b ...] [a ...] shift-pair
+--------------------------------------
+       [[b a] ...] [...] [...]
+
+

I'll leave that as an exercise for the reader for now.

+

R1 is just i (this is a tailrec function.)

+
F == <<{} [either-or-both-null] [THEN] [shift-pair] tailrec
+
+

Now let's derive THEN, there are three cases:

+
[pairs...] [] [] THEN
+[pairs...] [b ...] [] THEN
+[pairs...] [] [a ...] THEN
+
+

We can model this as a pair of ifte expressions, one nested in the other:

+
[P] [THEN′] [[P′] [THEN′′] [ELSE′] ifte] ifte
+
+

But in the event we won't need the inner ifte, see below.

+

The first predicate should check if both lists are empty:

+
P == null-two /\
+
+

(We defined this as both-null above.)

+

If both lists are empty we check the pairs:

+
THEN′ == popop compare-pairs
+
+

Otherwise if the top list is empty we return false, otherwise true, +and since this is a destructive operation we don't have to use ifte here:

+
THEN == [both-null] [popop compare-pairs] [popopd null] ifte
 
-[] [3 2 1] [4 5 6] [P] [BASE] [uncons-two [unit cons swons] dipd] tailrec
+F == <<{} [either-or-both-null] [THEN] [shift-pair] tailrec
 
-

Joy - [xR1 uncons-two [unit cons swons] dipd] inscribe

-

```Joy -clear

-

[] [3 2 1] [4 5 6] -```

-
[] [3 2 1] [4 5 6]
-
-

Joy -xR1 xR1 xR1

-
[[1 6] [2 5] [3 4]] [] []
-
-

Joy -clear -[xP [bool] ii & not] inscribe

-

```Joy -clear

-

[] [3 2 1] [5 4] [xP] [] [xR1] tailrec -```

-
[[2 4] [3 5]] [1] []
-
-

```Joy -clear

-

[] [3 2] [4 5 1] [xP] [] [xR1] tailrec -```

-
[[2 5] [3 4]] [] [1]
-
-

```Joy -clear

-

[] [3 2 1] [5 4 3] [xP] [] [xR1] tailrec -```

-
[[1 3] [2 4] [3 5]] [] []
-
-

Now comes the tricky part, that base case:

-

we have three lists. The first is a possibly-empty list of pairs to compare.

-

The second two are the tails of the original lists.

-

If the top list is non-empty then the second list must be empty so the whole function should return true

-

If the top list is empty and the second list isn't then the whole function should return false

-

If both lists are empty we start comparing uncons'd pairs until we find an un-equal pair or run out of pairs.

-
[bool]  # if the first list is non-empty
-[popop pop true]
-[
-    [pop bool]  # the second list is non-empty (the first list is empty)
-    [popop pop false]
-    [
-        # both lists are empty
-        popop
-        compare-pairs
-    ]
-    ifte
-]
-ifte
-
-

```Joy -clear -[][][1]

-

[bool] -[popop pop true] -[ - [pop bool] - [popop pop false] - [popop 23 swons] - ifte -] -ifte -```

-
true
-
-

```Joy -clear -[][1][]

-

[bool] -[popop pop true] -[ - [pop bool] - [popop pop false] - [popop 23 swons] - ifte -] -ifte -```

-
false
-
-

```Joy -clear -[1][][]

-

[bool] -[popop pop true] -[ - [pop bool] - [popop pop false] - [popop 23 swons] - ifte -] -ifte -```

-
[23 1]
-
-

compare-pairs

-

This should be a pretty simple recursive function

-
[P] [THEN] [R0] [R1] genrec
-
-

If the list is empty we return false

-
P == bool not
-THEN == pop false
-
-

On the recursive branch we have an ifte expression:

-
            pairs R0 [compare-pairs] R1
----------------------------------------------------
-   pairs [P.rec] [THEN.rec] [compare-pairs] ifte
-
-

We must compare the pair from the top of the list:

-
P.rec == first [>] infrst
-
-

```Joy -clear

-

[[1 3] [2 4] [3 5]] first [>] infrst -```

-
true
-
-

```Joy -clear

-

[[1 3] [2 4] [3 5]] [[>] infrst] map -```

-
[true true true]
+

Now we just have to write compare-pairs (and shift-pair.)

+

shift-pair

+
[pair-up unit cons] inscribe
 
-THEN.rec == pop true
+[shift-pair uncons-two [pair-up swons] dipd] inscribe
 
-

```Joy -clear

-

[compare-pairs - [bool not] - [pop false] - [ - [first [>] infrst] - [pop true] - ] - [ifte] - genrec -] inscribe -```

-

Joy -clear [[1 3] [2 4] [3 5]] compare-pairs

-
true
+

Compare Pairs

+

This function takes a list of pairs of digits (ints) and compares +them until it finds an unequal pair or runs out of pairs.

+

We are implementing "greater than" (b > a) so if we run out of digits +that means the two numbers were equal, and so we return false:

+
F == [null] [pop false] [R0] [R1] genrec
 
-

Joy -clear [[1 3] [3 3] [3 5]] compare-pairs

-
true
+

That leaves the recursive branch:

+
[[b a] ...] R0 [F] R1
 
-

Whoops! I forgot to remove the already-checked pair from the list of pairs! (Later on I discover that the logic is inverted here: >= not < d'oh!)

-

```Joy -clear

-

[compare-pairs - [bool not] - [pop false] - [ - [first [>=] infrst] - [pop true] - ] - [[rest] swoncat ifte] - genrec -] inscribe -```

-

This is clunky and inefficient but it works.

-

Joy -clear [[1 0] [2 2] [3 3]] compare-pairs

-
true
+

I figure we're going to want some sort of ifte. (But this turns out to +be a mistake!)

+
[[b a] ...] [P] [THEN] [F] ifte
 
-

Joy -clear [[1 1] [2 2] [3 3]] compare-pairs

-
true
-
-

Joy -clear [[1 2] [2 2] [3 3]] compare-pairs

-
true
-
-

Joy -clear

-

Joy -clear [[1 1] [2 1] [3 3]] compare-pairs

-
true
-
-

Joy -clear [[1 1] [2 2] [3 3]] compare-pairs

-
true
-
-

Joy -clear [[1 1] [2 3] [3 3]] compare-pairs

-
true
-
-

```Joy

-

```

-

```Joy -clear -[[1 1] [2 1] [3 3]] [] []

-

[bool] -[popop pop true] -[ - [pop bool] - [popop pop false] - [popop compare-pairs] - ifte -] -ifte -```

-
true
-
-

Joy -[BASE - [bool] - [popop pop true] - [ - [pop bool] - [popop pop false] - [popop compare-pairs] - ifte - ] - ifte -] inscribe

-
true
-
-

```Joy -clear

-

[] [3 2 1] [4 5 6] -```

-
[] [3 2 1] [4 5 6]
-
-

Joy -[xP] [BASE] [xR1] tailrec

-
true
-
-

```Joy -clear

-

[] [3 2 1] [4 5 6] swap -```

-
[] [4 5 6] [3 2 1]
-
-

Joy -[xP] [BASE] [xR1] tailrec

-
false
-
-

```Joy -clear

-

[] [3 2 1] dup -```

-
[] [3 2 1] [3 2 1]
-
-

Joy -[xP] [BASE] [xR1] tailrec

-
true
-
-

Joy -clear

-

Joy -[gt-bignum <<{} [xP] [BASE] [xR1] tailrec] inscribe

-

Joy -clear [3 2 1] [4 5 6] gt-bignum

-
true
-
-

Joy -clear [3 2 1] [4 5 6] swap gt-bignum

-
false
-
-

Joy -clear [3 2 1] dup gt-bignum

-
true
-
-

```Joy

-

```

-

Joy -clear [3 2 1] [4 5 6] [gt-bignum] [swap] [] ifte

-
[4 5 6] [3 2 1]
-
-

Joy -clear [4 5 6] [3 2 1] [gt-bignum] [swap] [] ifte

-
[4 5 6] [3 2 1]
-
-

And so it goes.

-

Now we can subtract, we just have to remember to invert the sign bit if we swap the digit lists.

-

Maybe something like:

-
check-gt == [gt-bignum] [swap true rollup] [false rollup] ifte
-
-

To keep the decision around as a Boolean flag? We can xor it with the sign bit?

-

Joy -clear -[check-gt [gt-bignum] [swap [not] dipd] [] ifte] inscribe

-

Joy -false [4 5 6] [3 2 1]

-
false [4 5 6] [3 2 1]
-
-

Joy -check-gt

-
false [4 5 6] [3 2 1]
-
-

Joy -clear

-

Subtraction, at last...

-

So now that we can compare digit lists to see if one is larger than the other we can subtract (inverting the sign if necessary) much like we did addition:

-
sub-bignums == [same-sign] [sub-like-bignums] [1 0 /] ifte
+

if b > a we can stop and report true, otherwise we discard the pair and recur.

+
P == first i >
 
-sub-like-bignums == [uncons] dip rest   sub-digits cons
-                                      ^
-                                      |
+THEN == pop true
 
-

At this point we would have the sign bit then the two digit lists.

-
sign [c b a] [z y x]
+

Note that that fails to discard the pair!

+
[[b a] ...] [first i >] [pop true] [F] ifte
 
-

We want to use check-gt here:

-
sign [c b a] [z y x] check-gt
-sign swapped? [c b a] [z y x] check-gt
+

If b <= a this would just re-run F with the same list!

+

Oops! D'oh! I didn't think it through properly.

+

We need to distinguish all three case (> = <) so we want to use cmp:

+
[[b a] ...] unswons i [G] [F] [L] cmp
 
-

It seems we should just flip the sign bit if we swap, eh?

-
check-gt == [gt-bignum] [swap [not] dipd] [] ifte
+

Becomes:

+
[...] b a [G] [F] [L] cmp
 
-

Now we subtract the digits:

-
sign [c b a] [z y x] sub-digits cons
+

Note that we recur on equality (that is our E function is just F itself).

+

If we the digits are not equal we can quit the loop with the answer:

+
[...] b a [pop true] [F] [pop false] cmp
 

So:

-
sub-like-bignums == [uncons] dip rest check-gt sub-digits cons
+
R0 == unswons i [pop true]
+
+R1 == [pop false] cmp
+
+

compare-pairs

+
[compare-pairs.R0 unswons i [pop true]] inscribe
+[compare-pairs.R1 [pop false] cmp] inscribe
+[compare-pairs [null] [pop false] [compare-pairs.R0] [compare-pairs.R1] genrec] inscribe
+
+

gt-digits

+
[gt-digits.THEN [both-null] [popop compare-pairs] [popopd null] ifte] inscribe
+[gt-digits <<{} [either-or-both-null] [gt-digits.THEN] [shift-pair] tailrec] inscribe
+
+

Almost Ready to Subtract

+

Now we can subtract, we just have to remember to invert the sign bit if we swap the digit lists.

+

Maybe something like:

+
check-gt == [gt-digits] [swap true] [false] ifte
+
+

To keep the decision around as a Boolean flag? We can xor it with the sign bit?

+

Let's start with two numbers on the stack, with the same sign:

+
[bool int int int] [bool int int int]
+
+

Then we keep one of the sign Booleans around and discard the other:

+
[bool int int int] [bool int int int] [uncons] dip rest
+[bool int int int] uncons [bool int int int] rest
+bool [int int int] [bool int int int] rest
+bool [int int int] [int int int]
+
+

So what we really want to do is swap and not:

+
check-gt == [gt-digits] [swap [not] dipd] [] ifte
+
+

extract-sign

+
[extract-sign [uncons] dip rest] inscribe
+
+

check-gt

+
[check-gt [gt-bignum] [swap [not] dipd] [] ifte] inscribe
+
+

Subtraction, at last...

+

So now that we can compare digit lists to see if one is larger than the other +we can subtract (inverting the sign if necessary) much like we did addition:

+
sub-bignums == [same-sign] [sub-like-bignums] [1 0 /] ifte
+
+sub-like-bignums == extract-sign check-gt sub-digits cons
 
 sub-digits == initial-carry sub-digits'
 
-sub-digits' ==
-    [sub-carry-from-digits]
-    [swap sub-carry]
-    [sub-with-carry]
-    build-two-list-combiner
-    genrec
+initial-carry == false rollup
+
+
+    both-empty == pop swap [] [1 swons] branch
+     one-empty == ditch-empty-list sub-carry-from-digits
+both-non-empty == uncons-two [sub-with-carry] dipd
+
+sub-digits′ == [both-empty] [one-empty] [both-non-empty] list-combiner-genrec
+
+

Which would expand into:

+
sub-digits′ == [either-or-both-null]
+               [[both-null] [both-empty] [one-empty] ifte]
+               [both-non-empty]
+               [i cons]
+               genrec
 

We just need to define the pieces.

-

sub-with-carry

-

We know we will never be subtracting a larger (absolute) number from a smaller (absolute) number (they might be equal) so the carry flag will never be true at the end of a digit list subtraction.

+

sub-with-carry

+

We know we will never be subtracting a larger (absolute) number from a smaller (absolute) number (they might be equal) +so the carry flag will never be true at the end of a digit list subtraction.

   carry a b sub-with-carry
 ------------------------------
      (a-b-carry)  new-carry
 
-_sub-with-carry0 ≡ [bool-to-int] dipd - -
-_sub-with-carry1 ≡ [base + base mod] [0 <] clop
-
-sub-with-carry ≡ _sub-with-carry0 _sub-with-carry1
+[sub-with-carry.0 - swap [] [--] branch] inscribe
+[sub-with-carry.1 [base + base mod] [0 <] cleave] inscribe
+[sub-with-carry sub-with-carry.0 sub-with-carry.1] inscribe
 
-

Joy -[_sub-with-carry0 rolldown bool-to-int [-] ii] inscribe -[_sub-with-carry1 [base + base mod] [0 <] cleave] inscribe -[sub-with-carry _sub-with-carry0 _sub-with-carry1] inscribe

-

Joy -clear false 3 base --

-
false 3 9
-
-

Joy -sub-with-carry

-
4 true
-
-

Joy -clear

-

sub-carry-from-digits

-

Should be easy to make modeled on add-carry-to-digits, another very simple recursive function. The predicate, base case, and R1 are the same:

+

sub-carry-from-digits

+

Should be easy to make modeled on add-carry-to-digits, another very simple recursive function. +The predicate, base case, and R1 are the same:

carry [n ...] sub-carry-from-digits
-carry [n ...] [pop not] [popd] [_scfd_R0] [i cons] genrec
+carry [n ...] [pop not] [popd] [R0] [i cons] genrec
 

That leaves the recursive branch:

-
true [n ...] _scfd_R0 [sub-carry-from-digits] i cons
+
true [n ...] R0 [sub-carry-from-digits] i cons
 

-or-

-
true [] _scfd_R0 [sub-carry-from-digits] i cons
+
true [] R0 [sub-carry-from-digits] i cons
 
-

Except that this should should never happen when subtracting, because we already made sure that we're only ever subtracting a number less than or equal to the, uh, number we are subtracting from (TODO rewrite this trainwreck of a sentence).

-
         true [a ...] _scfd_R0 [sub-carry-from-digits] i cons
+

Except that this latter case should should never happen when subtracting, +because we already made sure that we're only ever subtracting a number less than or equal to the, uh, +number we are subtracting from.

+
               true [a ...] R0 [sub-carry-from-digits] i cons
 ----------------------------------------------------------------
- true 0 a add-with-carry [...] [sub-carry-from-digits] i cons
+ true 0 a sub-with-carry [...] [sub-carry-from-digits] i cons
 ------------------------------------------------------------------
-             (a+1) carry [...] [sub-carry-from-digits] i cons
-
-
-true [a ...] _scfd_R0
+             (a-1) carry [...] [sub-carry-from-digits] i cons
+
+

It would work like this:

+
true [a ...] R0
 true [a ...] 0 swap uncons [sub-with-carry] dip
 true 0 [a ...] uncons [sub-with-carry] dip
 true 0 a [...] [sub-with-carry] dip
 true 0 a sub-with-carry [...]
 
-_scfd_R0 == 0 swap uncons [sub-with-carry] dip
+R0 == 0 swap uncons [sub-with-carry] dip
 

But there's a problem! This winds up subtracting a from 0 rather than the other way around:

-
_scfd_R0 == uncons 0 swap [sub-with-carry] dip
+
R0 == uncons 0 swap [sub-with-carry] dip
+
+

sub-carry-from-digits

+
[sub-carry-from-digits.R0 uncons 0 swap [sub-with-carry] dip] inscribe
+[sub-carry-from-digits [pop not] [popd] [sub-carry-from-digits.R0] [i cons] genrec] inscribe
 
-

Joy -[sub-carry-from-digits - [pop not] - [popd] - [_scfd_R0] - [i cons] - genrec -] inscribe -[_scfd_R0 uncons 0 swap [sub-with-carry] dip] inscribe

Try it out:

-

```Joy -clear

-

false [3 2 1] sub-carry-from-digits -```

-
[3 2 1]
+
joy? clear false [3 2 1] sub-carry-from-digits
+[3 2 1]
+
+joy? clear true [0 1] sub-carry-from-digits
+[9 0]
+
+joy? clear true [3 2 1] sub-carry-from-digits
+[2 2 1]
+
+joy? clear true [0 0 1] sub-carry-from-digits
+[9 9 0]
 
-

```Joy -clear

-

true [0 1] sub-carry-from-digits -```

-
[9 0]
-
-

```Joy -clear

-

true [3 2 1] sub-carry-from-digits -```

-
[2 2 1]
-
-

```Joy -clear

-

true [0 0 1] sub-carry-from-digits -```

-
[9 9 0]
-
-

Joy -clear

But what about those leading zeroes?

+

cons-but-not-leading-zeroes and sub-carry-from-digits

We could use a version of cons that refuses to put 0 onto an empty list?

-
cons-but-not-leading-zeroes == [[bool] ii | not] [popd] [cons] ifte
+
[cons-but-not-leading-zeroes [[bool] ii \/ not] [popd] [cons] ifte] inscribe
+[sub-carry-from-digits [pop not] [popd] [sub-carry-from-digits.R0] [i cons-but-not-leading-zeroes] genrec] inscribe
 
-

Joy -[cons-but-not-leading-zeroes [[bool] ii | not] [popd] [cons] ifte] inscribe

-

Joy -[sub-carry-from-digits - [pop not] - [popd] - [_scfd_R0] - [i cons-but-not-leading-zeroes] - genrec -] inscribe

-

Joy -[_scfd_R0 uncons 0 swap [sub-with-carry] dip] inscribe

-

```Joy -clear

-

true [0 0 1] sub-carry-from-digits -```

-
[9 9]
+

Good enough:

+
joy? clear true [0 1] sub-carry-from-digits
+[9]
+
+joy? clear true [0 0 1] sub-carry-from-digits
+[9 9]
 
-

Joy -clear

+

======================================================

sub-carry

sub-carry == pop
 
diff --git a/docs/source/notebooks/BigInts.md b/docs/source/notebooks/BigInts.md index e32dea9..40eea6c 100644 --- a/docs/source/notebooks/BigInts.md +++ b/docs/source/notebooks/BigInts.md @@ -794,7 +794,6 @@ Which would expand into: [i cons] genrec - It's pretty straight forward to make a functions that converts the three quotes into the expanded form (a kind of "macro") but you might want to separate that from the actual `genrec` evaluation. It would be better to @@ -823,1250 +822,400 @@ Source code: [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 - -# =================================================================================== - - ## ≡ `neg-bignum` Well, that was fun! And we'll reuse it in a moment when we derive `sub-bignums`. -But for now, let's clear our palate with a nice simple function: `neg-bignum`. +But for now let's clear our palate with a nice simple function: `neg-bignum`. To negate a Joy bignum you just invert the Boolean value at the head of the list. neg-bignum == [not] infra +## Subtraction of Like Signs -## Subtraction of Like Signs `sub-digits` +Subtraction is similar to addition in that it's a simple recursive algorithm that works digit-by-digit. +It has the same three cases as well, so we can reuse the `list-combiner-genrec` "macro" that +we specified (but did not yet derive) a moment ago. -Subtraction is similar to addition in that it's a simple recursive algorithm that works digit-by-digit. It has the same four cases as well, we can reuse `P` and `P'`. + sub-digits == initial-carry sub-digits' + sub-digits' == [both-empty] [one-empty] [both-non-empty] list-combiner-genrec - initial-carry == false rollup - sub-digits' == [P] [sub.THEN] [sub.R0] [sub.R1] genrec - sub-digits == initial-carry add-digits' - sub.THEN == [P'] [sub.THEN'] [sub.ELSE] ifte - -### Refactoring For The Win - - -We noted above that the algorithm for subtraction is similar to that for addition. Maybe we can reuse *more* than just `P` and `P'`? In fact, I think we could refactor (prematurely, two cases is one too few) something like this? - - [sub.THEN'] [sub.ELSE] [sub.R0] [sub.R1] foo - --------------------------------------------------------------------- - [P] [[P'] [sub.THEN'] [sub.ELSE] ifte] [sub.R0] [sub.R1] genrec - -or just - - [THEN] [ELSE] [R0] [R1] foo - ---------------------------------------------------- - [P] [[P'] [THEN] [ELSE] ifte] [R0] [R1] genrec - -eh? - -`foo` is something like: - - F == [ifte] ccons [P'] swons - G == [F] dipdd - - [THEN] [ELSE] [R0] [R1] [F] dipdd foo' - [THEN] [ELSE] F [R0] [R1] foo' - [THEN] [ELSE] [ifte] ccons [P'] swons [R0] [R1] foo' - [[THEN] [ELSE] ifte] [P'] swons [R0] [R1] foo' - [[P'] [THEN] [ELSE] ifte] [R0] [R1] foo' - -That leaves `[P]`... - - F == [ifte] ccons [P'] swons [P] swap - G == [F] dipdd - - [THEN] [ELSE] [ifte] ccons [P'] swons [P] swap [R0] [R1] foo' - [[THEN] [ELSE] ifte] [P'] swons [P] swap [R0] [R1] foo' - [[P'] [THEN] [ELSE] ifte] [P] swap [R0] [R1] foo' - [P] [[P'] [THEN] [ELSE] ifte] [R0] [R1] genrec - -Ergo: - - F == [ifte] ccons [P'] swons [P] swap - foo == [F] dipdd genrec - combine-two-lists == [i cons] foo - --and- - - add-digits' == [one-empty-list] - [both-empty] - [both-full] - combine-two-lists - - one-empty-list == ditch-empty-list add-carry-to-digits - both-empty == pop swap carry - both-full == uncons-two [add-with-carry] dipd - -This illustrates how refactoring creates denser yet more readable code. - -But this doesn't go quite far enough, I think. - - R0 == uncons-two [add-with-carry] dipd - -I think `R0` will pretty much always do: - - uncons-two [combine-two-values] dipd - -And so it should be refactored further to something like: - - [F] R0 - ------------------------- - uncons-two [F] dipd - -And then `add-digits'` becomes just: - - - add-digits' == [one-empty-list] - [both-empty] - [add-with-carry] - combine-two-lists - -If we factor `ditch-empty-list` out of `one-empty-list`, and `pop` from `both-empty`: - - add-digits' == [add-carry-to-digits] - [swap carry] - [add-with-carry] - combine-two-lists - - -Let's figure out the new form. - - - [ONE-EMPTY] [BOTH-EMPTY] [COMBINE-VALUES] foo - --------------------------------------------------- - [P] - [ - [P'] - [ditch-empty-list ONE-EMPTY] - [pop BOTH-EMPTY] - ifte - ] - [uncons-two [COMBINE-VALUES] dipd] - [i cons] genrec - -eh? - -Let's not over think it. - - [ONE-EMPTY] [ditch-empty-list] swoncat [BOTH-EMPTY] [pop] swoncat [COMBINE-VALUES] - [ditch-empty-list ONE-EMPTY] [pop BOTH-EMPTY] [COMBINE-VALUES] - -With: - - [C] [A] [B] sandwich - -------------------------- - [A [C] B] - - -```Joy -[sandwich swap [cons] dip swoncat] inscribe -``` - - - - -```Joy -clear [B] [A] [C] -``` - - [B] [A] [C] - - -```Joy -sandwich -``` - - [A [B] C] - -So to get from - - [A] [B] [C] - -to: - - [ditch-empty-list A] [pop B] [uncons-two [C] dipd] - -we use: - - [[[ditch-empty-list] swoncat] dip [pop] swoncat] dip [uncons-two] [dipd] sandwich - -It's gnarly, but simple: - - -```Joy -clear -[_foo0.0 [[ditch-empty-list] swoncat] dip] inscribe -[_foo0.1 [pop] swoncat] inscribe -[_foo0.3 [_foo0.0 _foo0.1] dip] inscribe -[_foo0.4 [uncons-two] [dipd] sandwich] inscribe -[_foo0 _foo0.3 _foo0.4] inscribe - -[_foo1 [ - [ifte] ccons - [P'] swons - [P] swap - ] dip -] inscribe -``` - - - - -```Joy -[A] [B] [C] _foo0 -``` - - [ditch-empty-list A] [pop B] [uncons-two [C] dipd] - - -```Joy -_foo1 -``` - - [P] [[P'] [ditch-empty-list A] [pop B] ifte] [uncons-two [C] dipd] - - -```Joy -clear -[add-carry-to-digits] -[swap carry] -[add-with-carry] -_foo0 _foo1 -``` - - [P] [[P'] [ditch-empty-list add-carry-to-digits] [pop swap carry] ifte] [uncons-two [add-with-carry] dipd] - -Compare the above with what we wanted: - - [P] - [ - [P'] - [ditch-empty-list ONE-EMPTY] - [pop BOTH-EMPTY] - ifte - ] - [uncons-two [COMBINE-VALUES] dipd] - -Allwe need to do is add: - - [i cons] genrec - - -```Joy -clear - -[3 2 1] [6 5 4] initial-carry - -[add-carry-to-digits] -[swap carry] -[add-with-carry] -_foo0 _foo1 -``` - - false [3 2 1] [6 5 4] [P] [[P'] [ditch-empty-list add-carry-to-digits] [pop swap carry] ifte] [uncons-two [add-with-carry] dipd] - - -```Joy -[i cons] genrec -``` - - [9 7 5] - - -```Joy -clear -[build-two-list-combiner _foo0 _foo1 [i cons]] inscribe -[combine-two-lists [add-carry-to-digits] [swap carry] [add-with-carry] build-two-list-combiner] inscribe -``` - - - - -```Joy -clear - -[3 2 1] [6 5 4] initial-carry -combine-two-lists -``` - - false [3 2 1] [6 5 4] [P] [[P'] [ditch-empty-list add-carry-to-digits] [pop swap carry] ifte] [uncons-two [add-with-carry] dipd] [i cons] - - -```Joy -genrec -``` - - [9 7 5] - - -```Joy -[base 10] inscribe -``` - - [9 7 5] - - -```Joy -clear - -123456 to-bignum - - - - -``` - - [true 6 5 4 3 2 1] - - -```Joy -clear -``` - - - -So that's nice. - -In order to avoid the overhead of rebuilding the whole thing each time we could pre-compute the function and store it in the dictionary. - - -```Joy -[add-carry-to-digits] -[swap carry] -[add-with-carry] -build-two-list-combiner -``` - - [P] [[P'] [ditch-empty-list add-carry-to-digits] [pop swap carry] ifte] [uncons-two [add-with-carry] dipd] [i cons] - -Now grab the definition, add the `genrec` and symbol (name) and inscribe it: - - -```Joy -[genrec] ccons ccons [add-digits'] swoncat -``` - - [add-digits' [P] [[P'] [ditch-empty-list add-carry-to-digits] [pop swap carry] ifte] [uncons-two [add-with-carry] dipd] [i cons] genrec] - - -```Joy - inscribe -``` - - - -Try it out... - - -```Joy -false [3 2 1] [4 3 2] add-digits' -``` - - [7 5 3] - - -```Joy -false swap base -- unit -``` - - false [7 5 3] [9] - - -```Joy -add-digits' -``` - - [6 6 3] - - -```Joy -clear -``` - - - -#### Demonstrate `add-bignums` - - -```Joy -1234 999 [to-bignum] ii -``` - - [true 4 3 2 1] [true 9 9 9] - - -```Joy -add-bignums -``` - - [true 3 3 2 2] - - -```Joy -from-bignum -``` - - 2233 - - -```Joy -1234 999 + -``` - - 2233 2233 - - -```Joy -clear -``` - - - -### Subtracting - -Okay, we're almost ready to implement subtraction, but there's a wrinkle! When we subtract a smaller (absolute) value from a larger (absolute) value there's no problem: +Okay, we're almost ready to implement subtraction, but there's a wrinkle! +When we subtract a smaller (absolute) value from a larger (absolute) +value there's no problem: 10 - 5 = 5 -But I don't know the algorithm to subtract a larger number from a smaller one: +But I don't know the algorithm to subtract a larger number from a smaller +one: 5 - 10 = ??? -The answer is -5, of course, but what's the algorithm? How to make the computer figure that out? We make use of the simple algebraic identity: +The answer is -5, of course, but what's the algorithm? How to make the +computer figure that out? + +We make use of the simple algebraic identity: a - b = -(b - a) -So if we want to subtract a larger number `a` from a smaller one `b` we can instead subtract the smaller from the larger and invert the sign: +So if we want to subtract a larger number `a` from a smaller one `b` we +can instead subtract the smaller from the larger and invert the sign: 5 - 10 = -(10 - 5) -To do this we need a function `gt-digits` that will tell us which of two digit lists represents the larger integer. - -#### `gt-digits` +To do this we need a function `gt-digits` that will tell us which of two +digit lists represents the larger integer. +### ≡ `length` -I just realized I don't have a list length function yet! +Gentle reader, it was at this time that I realized I don't have a list length function yet! + [length [pop ++] step_zero] inscribe -```Joy -[length [pop ++] step_zero] inscribe -``` +### Comparing Lists of Integers +We only need to compare the digits of the numbers if one list of digits is longer than the other. +We could use `length` on both lists and then `cmp`: + + a b [G] [E] [L] cmp + +If the top list is longer than the second list the function should return `true`, +and if the top list is shorter than the second list the function should return `false`, + + dup2 [length] ii [true] [E] [false] cmp + +If both lists are non-empty we have to compare digits starting with the ends. + + E == zip reverse compare-digits + +But this is inefficient! The `length` function will traverse each list once, +then the `zip` function will traverse both lists and build a new list of pairs, +then the `reverse` function will traverse that list and rebuild it, +then the `compare-digits` will traverse that list looking for unequal pairs... +It's a lot of work that we don't really want or need to do. + +### A More Efficient Comparison + +What we really want is a function that iterates through both lists together +and: + +- If the top list is empty and the second list isn't then the whole function should return `false`. +- If the top list is non-empty and the second list is empty then the whole function should return `true`. +- If both lists are empty we start checking pairs of digits (that we got from the recursive case.) +- If both lists are non-empty we `uncons-two` digits for later comparison and recur. + +Let's start designing the function. + + [...] [...] F + ------------------- + bool + +We will need a list on which to put pairs + + F == <<{} F′ + + [] [...] [...] F′ + ---------------------- + bool + +It's a recursive function: + + F′ == [P] [THEN] [R0] [R1] genrec +The predicate tests whether both of the two input lists are non-empty: + P = null-two \/ -```Joy -clear -[] length -``` +(We defined this as `either-or-both-null` above.) - 0 +Let's look at the recursive case first: + [...] [b ...] [a ...] R0 [F] R1 + ------------------------------------------- + [[b a] ...] [...] [...] F -```Joy -clear -[this is a list] length -``` +So `R0` transfers items from the source list to the pairs list, +let's call it `shift-pair`: - 4 + [...] [b ...] [a ...] shift-pair + -------------------------------------- + [[b a] ...] [...] [...] +I'll leave that as an exercise for the reader for now. -```Joy -clear -[1 2 3] [4 5] over over [length] app2 -``` +`R1` is just `i` (this is a `tailrec` function.) - [1 2 3] [4 5] 3 2 + F == <<{} [either-or-both-null] [THEN] [shift-pair] tailrec +Now let's derive `THEN`, there are three cases: -```Joy -[swap][6][7]cmp -``` + [pairs...] [] [] THEN + [pairs...] [b ...] [] THEN + [pairs...] [] [a ...] THEN - [4 5] [1 2 3] +We can model this as a pair of `ifte` expressions, one nested in the other: -what about a function that iterates through two lists until one or the other ends, or they end at the same time (same length) and we walk back through comparing the digits? + [P] [THEN′] [[P′] [THEN′′] [ELSE′] ifte] ifte +But in the event we won't need the inner `ifte`, see below. -```Joy -clear -``` +The first predicate should check if both lists are empty: - + P == null-two /\ +(We defined this as `both-null` above.) -```Joy -[1 2 3] [4 5 6] [bool] ii & -``` +If both lists are empty we check the pairs: - true + THEN′ == popop compare-pairs +Otherwise if the top list is empty we return `false`, otherwise `true`, +and since this is a destructive operation we don't have to use `ifte` here: -```Joy -clear -[1 2 3] [4 5 6] -[[bool] ii | not] -[pop] -[uncons-two] -[i [unit cons] dip cons] -genrec -``` + THEN == [both-null] [popop compare-pairs] [popopd null] ifte - [[1 4] [2 5] [3 6]] + F == <<{} [either-or-both-null] [THEN] [shift-pair] tailrec +Now we just have to write `compare-pairs` (and `shift-pair`.) -```Joy -clear -[1 2 3] [4 5 6] -[[bool] ii | not] -[pop] -[uncons-two] -[i [unit cons] dip cons] -genrec -``` +### ≡ `shift-pair` - [[1 4] [2 5] [3 6]] + [pair-up unit cons] inscribe + [shift-pair uncons-two [pair-up swons] dipd] inscribe -```Joy -clear -``` - +### Compare Pairs -So I guess that's `zip`? +This function takes a list of pairs of digits (ints) and compares +them until it finds an unequal pair or runs out of pairs. -But we want something a little different. +We are implementing "greater than" (b > a) so if we run out of digits +that means the two numbers were equal, and so we return `false`: -It's a weird function: compare lengths, if they are the same length then compare contents pairwise from the end. + F == [null] [pop false] [R0] [R1] genrec -if the first list is empty and the second list isn't then the whole function should return false +That leaves the recursive branch: -if the first list is non-empty and the second list is empty then the whole function should return true + [[b a] ...] R0 [F] R1 -if both lists are non-empty we uncons some digits for later comparison? Where to put them? Leave them on the stack? What about short-circuits? +I figure we're going to want some sort of `ifte`. (But this turns out to +be a mistake!) -if both lists are empty we start comparing uncons'd pairs until we find an un-equal pair or run out of pairs. + [[b a] ...] [P] [THEN] [F] ifte -if we run out of pairs before we find an unequal pair then the function returns true (the numbers are identical, we should try to shortcut the actual subtraction here, but let's just get it working first, eh?) +if b > a we can stop and report `true`, otherwise we discard the pair and recur. -if we find an unequal pair we return a>b and discard the rest of the pairs. Or maybe this all happens in some sort of `infra first` situation? + P == first i > + THEN == pop true +Note that that fails to discard the pair! -So the predicate will be `[bool] ii & not`, if one list is longer than the other we are done. -We postulate a third list to contain the pairs: + [[b a] ...] [first i >] [pop true] [F] ifte - [] [3 2 1] [4 5 6] [P] [BASE] [R0] [R1] genrec +If b <= a this would just re-run `F` with the same list! -The recursive branch seems simpler to figure out: +Oops! D'oh! I didn't think it through properly. - [] [3 2 1] [4 5 6] R0 [F] R1 +We need to distinguish all three case (> = <) so we want to use `cmp`: - uncons-two [unit cons swons] dipd [F] i + [[b a] ...] unswons i [G] [F] [L] cmp - [] [3 2 1] [4 5 6] [P] [BASE] [uncons-two [unit cons swons] dipd] tailrec +Becomes: + [...] b a [G] [F] [L] cmp -```Joy - [xR1 uncons-two [unit cons swons] dipd] inscribe -``` +Note that we recur on equality (that is our `E` function is just `F` itself). - +If we the digits are not equal we can quit the loop with the answer: + [...] b a [pop true] [F] [pop false] cmp -```Joy -clear +So: -[] [3 2 1] [4 5 6] -``` + R0 == unswons i [pop true] - [] [3 2 1] [4 5 6] + R1 == [pop false] cmp +### ≡ `compare-pairs` -```Joy -xR1 xR1 xR1 -``` + [compare-pairs.R0 unswons i [pop true]] inscribe + [compare-pairs.R1 [pop false] cmp] inscribe + [compare-pairs [null] [pop false] [compare-pairs.R0] [compare-pairs.R1] genrec] inscribe - [[1 6] [2 5] [3 4]] [] [] +### ≡ `gt-digits` + [gt-digits.THEN [both-null] [popop compare-pairs] [popopd null] ifte] inscribe + [gt-digits <<{} [either-or-both-null] [gt-digits.THEN] [shift-pair] tailrec] inscribe -```Joy -clear -[xP [bool] ii & not] inscribe -``` - - - -```Joy -clear - -[] [3 2 1] [5 4] [xP] [] [xR1] tailrec -``` - - [[2 4] [3 5]] [1] [] - - -```Joy -clear - -[] [3 2] [4 5 1] [xP] [] [xR1] tailrec -``` - - [[2 5] [3 4]] [] [1] - - -```Joy -clear - -[] [3 2 1] [5 4 3] [xP] [] [xR1] tailrec -``` - - [[1 3] [2 4] [3 5]] [] [] - -Now comes the tricky part, that base case: - -we have three lists. The first is a possibly-empty list of pairs to compare. - -The second two are the tails of the original lists. - -If the top list is non-empty then the second list must be empty so the whole function should return true - -If the top list is empty and the second list isn't then the whole function should return false - -If both lists are empty we start comparing uncons'd pairs until we find an un-equal pair or run out of pairs. - - [bool] # if the first list is non-empty - [popop pop true] - [ - [pop bool] # the second list is non-empty (the first list is empty) - [popop pop false] - [ - # both lists are empty - popop - compare-pairs - ] - ifte - ] - ifte - - - - -```Joy -clear -[][][1] - -[bool] -[popop pop true] -[ - [pop bool] - [popop pop false] - [popop 23 swons] - ifte -] -ifte -``` - - true - - -```Joy -clear -[][1][] - -[bool] -[popop pop true] -[ - [pop bool] - [popop pop false] - [popop 23 swons] - ifte -] -ifte -``` - - false - - -```Joy -clear -[1][][] - -[bool] -[popop pop true] -[ - [pop bool] - [popop pop false] - [popop 23 swons] - ifte -] -ifte -``` - - [23 1] - -#### `compare-pairs` - -This should be a pretty simple recursive function - - [P] [THEN] [R0] [R1] genrec - -If the list is empty we return `false` - - P == bool not - THEN == pop false - -On the recursive branch we have an `ifte` expression: - - pairs R0 [compare-pairs] R1 - --------------------------------------------------- - pairs [P.rec] [THEN.rec] [compare-pairs] ifte - -We must compare the pair from the top of the list: - - P.rec == first [>] infrst - - -```Joy -clear - -[[1 3] [2 4] [3 5]] first [>] infrst -``` - - true - - -```Joy -clear - -[[1 3] [2 4] [3 5]] [[>] infrst] map -``` - - [true true true] - - THEN.rec == pop true - - -```Joy -clear - -[compare-pairs - [bool not] - [pop false] - [ - [first [>] infrst] - [pop true] - ] - [ifte] - genrec -] inscribe -``` - - - - -```Joy -clear [[1 3] [2 4] [3 5]] compare-pairs -``` - - true - - -```Joy -clear [[1 3] [3 3] [3 5]] compare-pairs -``` - - true - -Whoops! I forgot to remove the already-checked pair from the list of pairs! (Later on I discover that the logic is inverted here: `>=` not `<` d'oh!) - - -```Joy -clear - -[compare-pairs - [bool not] - [pop false] - [ - [first [>=] infrst] - [pop true] - ] - [[rest] swoncat ifte] - genrec -] inscribe -``` - - - -This is clunky and inefficient but it works. - - -```Joy -clear [[1 0] [2 2] [3 3]] compare-pairs -``` - - true - - -```Joy -clear [[1 1] [2 2] [3 3]] compare-pairs -``` - - true - - -```Joy -clear [[1 2] [2 2] [3 3]] compare-pairs -``` - - true - - -```Joy -clear -``` - - - - -```Joy -clear [[1 1] [2 1] [3 3]] compare-pairs -``` - - true - - -```Joy -clear [[1 1] [2 2] [3 3]] compare-pairs -``` - - true - - -```Joy -clear [[1 1] [2 3] [3 3]] compare-pairs -``` - - true - - -```Joy - -``` - - -```Joy -clear -[[1 1] [2 1] [3 3]] [] [] - -[bool] -[popop pop true] -[ - [pop bool] - [popop pop false] - [popop compare-pairs] - ifte -] -ifte -``` - - true - - -```Joy -[BASE - [bool] - [popop pop true] - [ - [pop bool] - [popop pop false] - [popop compare-pairs] - ifte - ] - ifte -] inscribe -``` - - true - - -```Joy -clear - -[] [3 2 1] [4 5 6] -``` - - [] [3 2 1] [4 5 6] - - -```Joy -[xP] [BASE] [xR1] tailrec -``` - - true - - -```Joy -clear - -[] [3 2 1] [4 5 6] swap -``` - - [] [4 5 6] [3 2 1] - - -```Joy -[xP] [BASE] [xR1] tailrec -``` - - false - - -```Joy -clear - -[] [3 2 1] dup -``` - - [] [3 2 1] [3 2 1] - - -```Joy -[xP] [BASE] [xR1] tailrec -``` - - true - - -```Joy -clear -``` - - - - -```Joy -[gt-bignum <<{} [xP] [BASE] [xR1] tailrec] inscribe -``` - - - - -```Joy -clear [3 2 1] [4 5 6] gt-bignum -``` - - true - - -```Joy -clear [3 2 1] [4 5 6] swap gt-bignum -``` - - false - - -```Joy -clear [3 2 1] dup gt-bignum -``` - - true - - -```Joy - -``` - - -```Joy -clear [3 2 1] [4 5 6] [gt-bignum] [swap] [] ifte -``` - - [4 5 6] [3 2 1] - - -```Joy -clear [4 5 6] [3 2 1] [gt-bignum] [swap] [] ifte -``` - - [4 5 6] [3 2 1] - -And so it goes. +### Almost Ready to Subtract Now we can subtract, we just have to remember to invert the sign bit if we swap the digit lists. Maybe something like: - check-gt == [gt-bignum] [swap true rollup] [false rollup] ifte + check-gt == [gt-digits] [swap true] [false] ifte To keep the decision around as a Boolean flag? We can `xor` it with the sign bit? +Let's start with two numbers on the stack, with the same sign: -```Joy -clear -[check-gt [gt-bignum] [swap [not] dipd] [] ifte] inscribe -``` + [bool int int int] [bool int int int] - +Then we keep one of the sign Booleans around and discard the other: + [bool int int int] [bool int int int] [uncons] dip rest + [bool int int int] uncons [bool int int int] rest + bool [int int int] [bool int int int] rest + bool [int int int] [int int int] -```Joy -false [4 5 6] [3 2 1] -``` +So what we really want to do is `swap` and `not`: - false [4 5 6] [3 2 1] + check-gt == [gt-digits] [swap [not] dipd] [] ifte +### ≡ `extract-sign` -```Joy -check-gt -``` + [extract-sign [uncons] dip rest] inscribe - false [4 5 6] [3 2 1] +### ≡ `check-gt` + [check-gt [gt-bignum] [swap [not] dipd] [] ifte] inscribe -```Joy -clear -``` - - ### Subtraction, at last... -So now that we can compare digit lists to see if one is larger than the other we can subtract (inverting the sign if necessary) much like we did addition: +So now that we can compare digit lists to see if one is larger than the other +we can subtract (inverting the sign if necessary) much like we did addition: sub-bignums == [same-sign] [sub-like-bignums] [1 0 /] ifte - sub-like-bignums == [uncons] dip rest sub-digits cons - ^ - | - -At this point we would have the sign bit then the two digit lists. - - sign [c b a] [z y x] - -We want to use `check-gt` here: - - sign [c b a] [z y x] check-gt - sign swapped? [c b a] [z y x] check-gt - -It seems we should just flip the sign bit if we swap, eh? - - check-gt == [gt-bignum] [swap [not] dipd] [] ifte - -Now we subtract the digits: - - sign [c b a] [z y x] sub-digits cons - -So: - - sub-like-bignums == [uncons] dip rest check-gt sub-digits cons + sub-like-bignums == extract-sign check-gt sub-digits cons sub-digits == initial-carry sub-digits' - sub-digits' == - [sub-carry-from-digits] - [swap sub-carry] - [sub-with-carry] - build-two-list-combiner - genrec + initial-carry == false rollup + + + both-empty == pop swap [] [1 swons] branch + one-empty == ditch-empty-list sub-carry-from-digits + both-non-empty == uncons-two [sub-with-carry] dipd + + sub-digits′ == [both-empty] [one-empty] [both-non-empty] list-combiner-genrec + +Which would expand into: + + sub-digits′ == [either-or-both-null] + [[both-null] [both-empty] [one-empty] ifte] + [both-non-empty] + [i cons] + genrec + + 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 + + We just need to define the pieces. -#### `sub-with-carry` +### ≡ `sub-with-carry` -We know we will never be subtracting a larger (absolute) number from a smaller (absolute) number (they might be equal) so the carry flag will never be true *at the end of a digit list subtraction.* +We know we will never be subtracting a larger (absolute) number from a smaller (absolute) number (they might be equal) +so the carry flag will never be true *at the end of a digit list subtraction.* carry a b sub-with-carry ------------------------------ (a-b-carry) new-carry - _sub-with-carry0 ≡ [bool-to-int] dipd - - - _sub-with-carry1 ≡ [base + base mod] [0 <] clop - - sub-with-carry ≡ _sub-with-carry0 _sub-with-carry1 + [sub-with-carry.0 - swap [] [--] branch] inscribe + [sub-with-carry.1 [base + base mod] [0 <] cleave] inscribe + [sub-with-carry sub-with-carry.0 sub-with-carry.1] inscribe +### `sub-carry-from-digits` -```Joy -[_sub-with-carry0 rolldown bool-to-int [-] ii] inscribe -[_sub-with-carry1 [base + base mod] [0 <] cleave] inscribe -[sub-with-carry _sub-with-carry0 _sub-with-carry1] inscribe -``` - - - - -```Joy -clear false 3 base -- -``` - - false 3 9 - - -```Joy -sub-with-carry -``` - - 4 true - - -```Joy -clear -``` - - - -#### `sub-carry-from-digits` - -Should be easy to make modeled on `add-carry-to-digits`, another very simple recursive function. The predicate, base case, and `R1` are the same: +Should be easy to make modeled on `add-carry-to-digits`, another very simple recursive function. +The predicate, base case, and `R1` are the same: carry [n ...] sub-carry-from-digits - carry [n ...] [pop not] [popd] [_scfd_R0] [i cons] genrec + carry [n ...] [pop not] [popd] [R0] [i cons] genrec That leaves the recursive branch: - true [n ...] _scfd_R0 [sub-carry-from-digits] i cons + true [n ...] R0 [sub-carry-from-digits] i cons -or- - true [] _scfd_R0 [sub-carry-from-digits] i cons + true [] R0 [sub-carry-from-digits] i cons -**Except** that this should should never happen when subtracting, because we already made sure that we're only ever subtracting a number less than or equal to the, uh, number we are subtracting from (TODO rewrite this trainwreck of a sentence). +**Except** that this latter case should should never happen when subtracting, +because we already made sure that we're only ever subtracting a number less than or equal to the, uh, +number we are subtracting from. - true [a ...] _scfd_R0 [sub-carry-from-digits] i cons + true [a ...] R0 [sub-carry-from-digits] i cons ---------------------------------------------------------------- - true 0 a add-with-carry [...] [sub-carry-from-digits] i cons + true 0 a sub-with-carry [...] [sub-carry-from-digits] i cons ------------------------------------------------------------------ - (a+1) carry [...] [sub-carry-from-digits] i cons + (a-1) carry [...] [sub-carry-from-digits] i cons +It would work like this: - true [a ...] _scfd_R0 + true [a ...] R0 true [a ...] 0 swap uncons [sub-with-carry] dip true 0 [a ...] uncons [sub-with-carry] dip true 0 a [...] [sub-with-carry] dip true 0 a sub-with-carry [...] - _scfd_R0 == 0 swap uncons [sub-with-carry] dip + R0 == 0 swap uncons [sub-with-carry] dip But there's a problem! This winds up subtracting `a` from 0 rather than the other way around: - _scfd_R0 == uncons 0 swap [sub-with-carry] dip + R0 == uncons 0 swap [sub-with-carry] dip +### ≡ `sub-carry-from-digits` -```Joy -[sub-carry-from-digits - [pop not] - [popd] - [_scfd_R0] - [i cons] - genrec -] inscribe -[_scfd_R0 uncons 0 swap [sub-with-carry] dip] inscribe -``` - - + [sub-carry-from-digits.R0 uncons 0 swap [sub-with-carry] dip] inscribe + [sub-carry-from-digits [pop not] [popd] [sub-carry-from-digits.R0] [i cons] genrec] inscribe Try it out: - -```Joy -clear - -false [3 2 1] sub-carry-from-digits -``` - + joy? clear false [3 2 1] sub-carry-from-digits [3 2 1] - - -```Joy -clear - -true [0 1] sub-carry-from-digits -``` - - [9 0] - - -```Joy -clear - -true [3 2 1] sub-carry-from-digits -``` - - [2 2 1] - - -```Joy -clear - -true [0 0 1] sub-carry-from-digits -``` - - [9 9 0] - - -```Joy -clear -``` - + joy? clear true [0 1] sub-carry-from-digits + [9 0] + + joy? clear true [3 2 1] sub-carry-from-digits + [2 2 1] + + joy? clear true [0 0 1] sub-carry-from-digits + [9 9 0] But what about those leading zeroes? +### ≡ `cons-but-not-leading-zeroes` and `sub-carry-from-digits` + We could use a version of `cons` that refuses to put 0 onto an empty list? - cons-but-not-leading-zeroes == [[bool] ii | not] [popd] [cons] ifte - - -```Joy -[cons-but-not-leading-zeroes [[bool] ii | not] [popd] [cons] ifte] inscribe -``` + [cons-but-not-leading-zeroes [[bool] ii \/ not] [popd] [cons] ifte] inscribe + [sub-carry-from-digits [pop not] [popd] [sub-carry-from-digits.R0] [i cons-but-not-leading-zeroes] genrec] inscribe +Good enough: - - -```Joy -[sub-carry-from-digits - [pop not] - [popd] - [_scfd_R0] - [i cons-but-not-leading-zeroes] - genrec -] inscribe -``` - + joy? clear true [0 1] sub-carry-from-digits + [9] - - -```Joy -[_scfd_R0 uncons 0 swap [sub-with-carry] dip] inscribe -``` - - - - -```Joy -clear - -true [0 0 1] sub-carry-from-digits -``` - + joy? clear true [0 0 1] sub-carry-from-digits [9 9] -```Joy -clear -``` - + +# ====================================================== + + #### `sub-carry`