Popup YouTube Video
Sheerpower Logo

Exact Fractions: the RATIONAL Mode


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).

Part 1: Where a Fraction Comes From

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.

a = 1 \ b = 3 third = a / b print third ! .3333333333333333 print third * 3 ! 1 print 2 / 3 + 1 / 3 ! 1 print 100 / 3 * 3 ! 100 print typeof$(third) ! Name:THIRD, Dtype:Real, *Rational* print fraction$(third) ! 1/3 each_way = 100 / 3 print each_way, each_way * 3, fraction$(each_way) ! 33.3333333333333333 100 100/3

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.

rate = 0.05 / 12 print rate ! .0041666666666667 print typeof$(rate) ! Name:RATE, Dtype:Real, *Narrow* print 7 / 8, 1 / 4, typeof$(1 / 4) ! .875 .25 Name:*Literal*, Dtype:Real, *Narrow*

Part 2: The One Rule

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.

print fraction$(1 / 3 + 1 / 7) ! 10/21 print (1 / 3) * (1 / 3) * 9 ! 1 print fraction$((2 / 3) ^ 3) ! 8/27 print fraction$((1 / 3) / 7), 1 / (1 / 3) ! 1/21 3 print 10 / 254 * 254 ! 10 x = (1 / 3) ^ 13 print x, typeof$(x) ! .0000006272254744 Name:X, Dtype:Real, *Narrow* -- 3^13 is past a million

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.

print 0.25 + 1 / 3 ! .5833333333333333 print 1 / 3 + 1 / 4 ! .5833333333333333 print fraction$(7 / 12) ! 7/12

Part 3: Printing, Comparing, Converting

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.

print third = 0.3333333333333333 ! 1 print third > 0.3333333333333333 ! 0 print third - 0.3333333333333333 ! 0 print val(str$(third)) = third ! 1 print third = 2 / 6, third > 3 / 10 ! 1 1 print decimal(third), str$(third), f$("[[third]]") ! .3333333333333333 .3333333333333333 .3333333333333333 print sprintf$("%.4m", third), sprintf$("%.2r", 22 / 7) ! 0.3333 3.14 n% = 7 / 3 print n% ! 2

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.

print numerator(2 / 3), denominator(2 / 3), fraction$(0.25), fraction$(3) ! 2 3 1/4 3 print fraction$(fraction(3.14159265358979, 7)), fraction$(fraction(3.14159265358979, 1000)) ! 22/7 355/113

Part 4: The Functions

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.

print fraction$(abs(-1 / 3)), int(7 / 3), floor(-7 / 3), ceil(7 / 3), round(1 / 3, 2), sgn(-1 / 3) ! 1/3 2 -3 3 .33 -1 print sqr(4 / 9), sqr(0.4444444444444444) ! .6666666666666666 .6666666666666666 print fraction$(max(1 / 3, 1 / 4)), fraction$(clamp(1 / 3, 0, 1)) ! 1/3 1/3

Part 5: Arrays

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.

dim v(*), w(*) fill v with 1, 2, 3 w = v / 3 print w ! .3333333333333333 .6666666666666667 1 print fraction$(w(1)); " "; fraction$(w(2)); " "; w(3) ! 1/3 2/3 1 print stats$sum(w), stats$mean(w) ! 2 .6666666666666667 dim s(*) s = seq(0, 1, 1 / 3) print s ! 0 .3333333333333333 .6666666666666667 1 print size(s), s(4) ! 4 1 dim q(*) fill q with 1 / 3, 1 / 7, 1 / 2 q = sort(q) print fraction$(q(1)); " "; fraction$(q(2)); " "; q(3) ! 1/7 1/3 .5

Part 6: What It Costs

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.

balance = 1000 rate = 1 / 240 for month = 1 to 4 balance = balance * (1 + rate) print month, balance, typeof$(balance) next month ! 1 1004.1666666666666667 Name:BALANCE, Dtype:Real, *Rational* ! 2 1008.3506944444444444 Name:BALANCE, Dtype:Real, *Rational* ! 3 1012.5521556712962963 Name:BALANCE, Dtype:Real, *Rational* ! 4 1016.7711229865933642 Name:BALANCE, Dtype:Real, *Narrow*
loop, one million iterationsfractions onfractions off
amortization as written, rate = 0.05 / 12 (a decimal)2,066,116 steps/sec2,283,105 steps/sec
amortization with rate = 1 / 240 (a fraction, demoted each step)1,879,699 steps/sec2,463,054 steps/sec
i / 7 with i changing (a fraction created per divide)2,666,667 divides/sec3,048,780 divides/sec
5 / 7 repeated (the recent-divides cache)16,129,032 divides/sec8,000,000 divides/sec
1/3 + 1/7 then * 21, two exact operations11,627,907 ops/sec, and the answer is 1012,820,513 ops/sec, and the answer is 10.0000000000000002
str$(1 / 3)9,090,909 per sec16,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.

Part 7: Turning It Off

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.