2025/04/13
Exact and repeating decimals
Novice programmers are often perplexed by most programming languages being not able to add 0.1 ten times ``correctly'':
s = 0 for i in range(10): s += 0.1 print(s) # prints: 0.9999999999999999
"Floating point numbers are inexact, that's why," tells a tutor. "You should expect some errors."
Gauche isn't an exception, for decimal notation is read as inexact numbers:
gosh> (apply + (make-list 10 0.1)) 0.9999999999999999
However, Scheme also has exact numbers. Numbers without a decimal
point or exponent, or rational numbers, are read as exact numbers.
You can also prefix decimal numbers with #e
to make them exact.
Using exact numbers, you can have an exact result.
gosh> (apply + (make-list 10 #e0.1)) 1
The trick is that Gauche reads #e0.1
as an exact rational number
1/10
, and perform computation as exact rationals.
It is revealed when the result is not a whole number:
gosh> (+ #e0.1 #e0.1) 1/5
It is incovenient, though, when you want to perform exact computation
with decimal numbers, i.e. adding prices with dollars and cents.
If you add $15.15 and $8.91, you want to see the result as
24.06
instead of 1203/50
.
;; Inexact gosh> (+ 15.15 8.91) 24.060000000000002 ;; Exact gosh> (+ #e15.15 #e8.91) 1203/50
So, we added a new REPL print mode, exact-decimal. If you set it
to #t
, Gauche tries to print exact non-integer result as
decimal notation whenever possible.
gosh> ,pm exact-decimal #t Current print mode: length : 50 level : 10 pretty : #t width : 79 base : 10 radix : #f string-length : 256 bytestring : #f exact-decimal : #t
Let's see:
gosh> (+ #e15.15 #e8.91) #e24.06
We can always have exact decimal notation of rational numbers whose denominator's factor contains only 2 and 5.
gosh> 1/65536 #e0.0000152587890625
As far as we use addition, subtraction, and multiplication of exact decimal notated numbers, the result is always representable with exact decimal notation.
But what if division is involved? Isn't it a shame that we have an exact value (as a rational number), but can't print it as a decimal exactly?
Decimal notation of rational numbers whose denominator contains factors other than 2 and 5 becomes repeating decimals. Hence if we have a notation of repeating decimals, we can cover such cases.
So, here it is. If a numeric literal contains #
followed
by one or more digits, we understand the digits after #
repeating infinitely.
gosh> 0.#3 0.3333333333333333 gosh> 0.0#123 0.012312312312312312 gosh> 0.#5 0.5555555555555556 gosh> 0.1#9 0.2
(Note: If no digits follows #
, it is "insignificant digit"
notation in R5RS.)
The above examples have limited number of digits because
they're inexact numbers (note that we didn't
prefix them with #e
). For exact numbers, we can represent
any rational numbers exactly with this notation:
gosh> 1/3 #e0.#3 gosh> 1/7 #e0.#142857 gosh> (* 1/7 2) #e0.#285714 gosh> (* #e0.#3 #e0.#142857) #e0.#047619
Note that the length of repetition can be arbitrarily long, so there are numbers that can't practically be printed in this notation. For the time being, we have a hard limit of 1024 for the length of repetition. If the result exceeds this limitation, we fall back to rational notation.
;; 1/2063 has repeating cycle of 1031 digits gosh> (/ 1 2063) 1/2063
Post a comment