|
Exact Fractions: the RATIONAL Mode |
Divide two whole numbers and the answer usually does not fit in sixteen decimal places: one third is .3333333333333333, and three of those add up to .9999999999999999. Every language with decimals or doubles has this trap. Sheerpower closes it: when the two operands of a divide are whole numbers and the quotient does not terminate, the result is kept as an EXACT FRACTION, and it stays exact through the arithmetic that follows. Nothing is declared and nothing new is printed — a fraction prints as the decimal it always printed as. This page covers where fractions come from (Part 1), the one rule that keeps them exact (Part 2), printing, comparing and converting them (Part 3), the functions (Part 4), arrays (Part 5), what it costs (Part 6) and the switch that turns it off (Part 7).
A divide creates a fraction only when BOTH operands are whole numbers.
The result carries the mode *Rational* in
typeof$(), prints as its decimal, and
fraction$() shows it as it is.
Two things are NOT fractions. A divide with a decimal operand is the ordinary rounded divide it always was — the whole-number rule keeps every money calculation exactly as it is today, and the test costs one tag check. And a quotient that terminates is simply a decimal.
A ratio of whole numbers stays exact among whole numbers and other ratios while its denominator is under a million. That is the whole rule. Add, subtract, multiply, divide and raise to a whole power, and the fraction is carried exactly; a result that comes out whole, or that terminates, becomes a plain decimal; a denominator that grows past a million rounds to sixteen places, the way today's divide does.
The first decimal fraction a ratio meets ends the exactness. Beside a
non-whole decimal the fraction becomes its decimal first, and the
operation is the ordinary one. This has one visible edge worth knowing:
1 / 4 is the decimal .25, so 1 / 3 + 1 / 4 is a
decimal sum, while 1 / 3 + 1 / 7 is exact. When you want
the exact seven twelfths, write 7 / 12.
Every place a value leaves as text — print,
str$(), an f$() slot, sprintf$(),
json$(), a table field — shows the sixteen-place
decimal the value printed as before fractions existed. Comparison
follows the same rule as arithmetic: exact among fractions and whole
numbers, the decimal beside a decimal. So a fraction IS equal to its
own printout, a value written to a file and read back compares equal to
the one that wrote it, and a - b = 0 always agrees with
a = b.
Four small functions read a fraction. fraction$(x) is the
reduced fraction as text; a whole value prints plain, and a decimal
shows the fraction it sits on. numerator(x) and
denominator(x) are its parts. decimal(x) is
the sixteen-place decimal by name. And fraction(x, maxden)
finds the best fraction with a denominator no larger than maxden
— the way to turn a measured decimal back into a nice ratio.
abs() and unary minus keep a fraction exact. The integer
functions — int, floor, ceil,
round, sgn — and the irrational ones
— sqr, exp, log, the trig
family, a non-whole power — work on the decimal: they demote
first, then compute. max, min and
clamp compare exactly and hand the operand back unchanged.
An element-wise divide of whole numbers fills an array with fractions,
the statistics over them are exact, sorting orders them exactly, and a
seq() with a fractional step lands on its end point.
Printing shows the decimals.
A money loop written with a decimal rate never touches fractions. One
written with rate = 1 / 240 keeps its balance exact for a
few steps, then rounds once and runs as a decimal from there, with the
factor's conversion cached. The table is one run of a million
iterations each, with the mode on and with it off.
| loop, one million iterations | fractions on | fractions off |
|---|---|---|
| amortization as written, rate = 0.05 / 12 (a decimal) | 2,066,116 steps/sec | 2,283,105 steps/sec |
| amortization with rate = 1 / 240 (a fraction, demoted each step) | 1,879,699 steps/sec | 2,463,054 steps/sec |
| i / 7 with i changing (a fraction created per divide) | 2,666,667 divides/sec | 3,048,780 divides/sec |
| 5 / 7 repeated (the recent-divides cache) | 16,129,032 divides/sec | 8,000,000 divides/sec |
| 1/3 + 1/7 then * 21, two exact operations | 11,627,907 ops/sec, and the answer is 10 | 12,820,513 ops/sec, and the answer is 10.0000000000000002 |
| str$(1 / 3) | 9,090,909 per sec | 16,129,032 per sec |
Read the table as: a divide that creates a fraction costs about fifty
nanoseconds more than a plain divide, exact fraction arithmetic runs
at the speed of ordinary decimal arithmetic, a repeated divide of the
same two whole numbers is twice as fast as a plain divide through the
cache, and the loops that never make a fraction are unchanged.
show stats at the console reports how many fractions a
run created and demoted and how the two caches fared.
option arithmetic norational at the top of a program
restores the old arithmetic byte for byte: no divide creates a
fraction, 1 / 3 * 3 is .9999999999999999 again.
option arithmetic rational turns it back on. The four
functions still work on decimals either way.
The rule in one sentence. A ratio of whole numbers stays exact among whole numbers and other ratios while its denominator is under a million; the first decimal it meets, and every print, turns it into the sixteen-place decimal it always was.
|
Hide Description
|
|
|
Enter or modify the code below, and then click on RUN |
|
Looking for the full power of Sheerpower?
Check out the Sheerpower website. Free to download. Free to use. |