▷ Home
Thun uses only integers, no floating point, so how shall we implement functions like square root?
Following tradition, we will take one step up what is called the "numerical tower" and implement rational numbers.
... the numerical tower is a set of data types that represent numbers and a logic for their hierarchical organisation. (sic) Each type in the tower conceptually "sits on" a more fundamental type, so an integer is a rational number ... but the converse is not necessarily true ...
(See also Number System.)
We will employ pairs of integers to form rational numbers (aka ℚ.) The numerator is expected to be below the denominator on the stack. E.g. one half would just be:
1 2
For example, we can represent, for all practical purposes, the number pi by the ratio between two integers:
884279719003555 281474976710656
To multiply a number by pi we can multiply by the numerator and then divide by the denominator:
joy? 10000 884279719003555 * 281474976710656 /
31415
To divide by pi we multiply by the denominator and then divide by the numerator:
joy? 31415 281474976710656 * 884279719003555 /
9999
Close enough?
scale[scale [mul] dip div] inscribe
Use scale to convert a ratio into an integer.
Any integer can be converted to a rational by putting 1 on the stack after it.
Implementing the fundamental arithmetic operations is pretty straightforward:
(a, b) + (c, d) = (ad+bc, bd)
(a, b) - (c, d) = (ad-bc, bd)
(a, b) × (c, d) = (ac, bd)
(a, b) ÷ (c, d) = (ad, bc) with c != 0
~ Fractions in abstract mathematics
Starting with a b c d on the stack, with a b representing one ratio and c d the other
as above...
For a × c we ditch b and d and then multiply:
pop popd mul
For a × d we ditch b and c and then multiply:
[popop] dip mul
For b × c we ditch a and d and then multiply:
pop mul popd
For b × d we ditch a and c and then multiply:
popd mul popd
So:
_ac pop popd mul
_ad [popop] dip mul
_bc pop mul popd
_bd popd mul popd
fork and cleftWe can compute a × d and b × c in parallel with the fork combinator:
[_ad] [_bc] fork
Then we use add or sub to add or subtract the products:
_ad_bc [_ad] [_bc] fork
_ad+bc _ad_bc add
_ad-bc _ad_bc sub
For division we could also use _ad_bc and then discard the four arguments a b c d,
but there is a variant of fork that already does that, cleft,
so instead we just use that:
_div-frac [_ad] [_bc] cleft
b × dAddition, subtraction, and multiplication all have b × d as the new denominator.
We can factor the code to compute that into a definition that includes cleft,
this new function expects the four arguments a b c d and a quoted program, it replaces these
with the result of that quoted program and b × d computed in parallel (at least in theory.
The current implementation of fork et. al. doesn't actually run in parallel. However,
there is an experimental Python version that uses the fork system call to actually run
in parallel.) Anyway, here's _/bd:
_/bd [_bd] cleft
That along with _ad+bc, _ad-bc, and _ac will allow us to implement addition, subtraction, and multiplication:
_add-frac [_ad+bc] _/bd
_sub-frac [_ad-bc] _/bd
_mul-frac [_ac] _/bd
That's really the only clever thing going on in the code.
Because the arithmetic operations involve multiplication the terms will increase in magnitude. To counteract that we can reduce the rationals after each operation.
We can employ Euclid's Algorithm to compute the greatest common divisor between two integers:
true [tuck mod ?] loop pop
Here it is in action with 30 and 20:
30 20 • true [tuck mod ?] loop pop
30 20 true • [tuck mod ?] loop pop
30 20 true [tuck mod ?] • loop pop
30 20 • tuck mod ? [tuck mod ?] loop pop
20 30 20 • mod ? [tuck mod ?] loop pop
20 10 • ? [tuck mod ?] loop pop
20 10 true • [tuck mod ?] loop pop
20 10 true [tuck mod ?] • loop pop
20 10 • tuck mod ? [tuck mod ?] loop pop
10 20 10 • mod ? [tuck mod ?] loop pop
10 0 • ? [tuck mod ?] loop pop
10 0 false • [tuck mod ?] loop pop
10 0 false [tuck mod ?] • loop pop
10 0 • pop
10 •
gcd[gcd true [tuck mod ?] loop pop] inscribe
We apply gcd with the nullary combinator so that it doesn't consume the rational number on the stack.
numerator denominator [gcd] nullary
------------------------------------------
numerator denominator divisor
Then we build a little divide-by-divisor program and apply it to the both the numerator and denominator with app2:
numerator denominator divisor [div] cons app2
---------------------------------------------------
numerator denominator [divisor div] app2
----------------------------------------------
numerator/divisor denominator/divisor
reduce-fraction[reduce-fraction [gcd] nullary [div] cons app2] inscribe
Putting it all together, here is the code for the four arithmetic operations. It's presented with the "top most" functions first followed by the supporting functions.
add-frac _add-frac reduce-fraction
sub-frac _sub-frac reduce-fraction
mul-frac _mul-frac reduce-fraction
div-frac _div-frac reduce-fraction
reduce-fraction [gcd] nullary [div] cons app2
gcd true [tuck mod ?] loop pop
_add-frac [_ad+bc] _/bd
_sub-frac [_ad-bc] _/bd
_mul-frac [_ac] _/bd
_div-frac [_ad] [_bc] cleft
_ad+bc _ad_bc add
_ad-bc _ad_bc sub
_ad_bc [_ad] [_bc] fork
_/bd [_bd] cleft
_ac pop popd mul
_ad [popop] dip mul
_bc pop mul popd
_bd popd mul popd
cmp-frac [_div-frac] dipdd cmp
eq-frac _div-frac eq
gt-frac _div-frac gt
lt-frac _div-frac lt
neq-frac _div-frac neq
le-frac _div-frac le
ge-frac _div-frac ge
Copyright © 2025 Simon Forman