Gauche Devlog

< Running prebuilt Gauche on GitHub workflow |

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

Tags: Numbers, Syntax

Post a comment

Name: