R8 — Exact arithmetic

Goal: get answers that are exactly right rather than nearly right, and understand the two different numbers both called “precision”.

Answering R1’s question

R1 noted that 2 + 3 * 4 comes back as 14.0 and promised this lesson would explain it. Here is the short version:

> 2 + 3 * 4
14.0
> exact on
exact = on
> 2 + 3 * 4
14

run this session

By default every number is a decimal — a floating-point value, which is why a whole result still carries .0. Turn exact mode on and 14 is an exact integer, so it prints as one.

That is not a display setting. It changes what the numbers are.

Fractions instead of decimals

> 1/3
1/3
> 1/3 + 1/6
1/2
> (2/3)^10
1024/59049
> harmonic(4)
25/12

run this session

1/3 is now a fraction rather than 0.33333, and it stays one through arithmetic. No rounding happened anywhere above, so nothing accumulated.

Exactness survives a fold, too — this is a sum of ten fractions:

> sum(1/k, k, 1, 10)
7381/2520

run this session

Why the switch has to come first

> 0.1 + 0.2
3/10

run this session

Exactly three tenths — where floating point cannot represent one tenth at all.

That is why exact on is a parse-time switch: it changes what a literal becomes as you type it. Turning it on afterwards could not help, because by then the tenth you meant is already gone. Set it before you enter the numbers.

The error you cannot see

Turn it off again and look at the same sum:

> exact off
exact = off
> precision 15
precision = 15
> 0.1 + 0.2
0.3
> (0.1 + 0.2) - 0.3
0.0

run this session

0.1 + 0.2 is genuinely not three tenths, and yet it prints as 0.3 — and the difference prints as 0.0 — at the highest precision the REPL will display.

That is the honest shape of the problem. The error is real, about 5.6e-17, and below anything you can be shown. You cannot audit it by looking, and it accumulates in long computations where nobody is looking. Removing it is more reliable than inspecting for it.

Irrationals, and the working precision

An irrational is not a fraction, so there is nothing exact to store. Leonardo computes one to a working precision and carries on:

> exact on
exact = on
> exact precision 40
exact precision = 40
> pi * e
8.539734222673568

run this session

The value carries forty digits; the display shows fifteen, because that is the most a printed decimal here will claim.

The two precisions are different quantities

> exact off
exact = off
> precision 3
precision = 3
> 1/3
0.333
> precision 12
precision = 12
> 1/3
0.333333333333

run this session

  • precision is how many digits are shown. The same value twice above, displayed two ways — nothing about the computation changed.
  • exact precision is how many digits an irrational is computed to before exact arithmetic continues.

They are separate deliberately: if raising the one you read also raised the one you compute with, then asking to see more digits would silently change your results.

Either reports itself when typed bare:

> exact
exact = off, working precision = 40

run this session

Where it earns its keep

A Double holds about fifteen significant digits. Past that it stops counting and starts estimating:

> precision 15
precision = 15
> 2^100
1.2676506002282294E30
> fact(25)
1.5511210043330986E25

run this session

Those are approximations in scientific notation — the true values are integers with 31 and 26 digits. Exactly:

> exact on
exact = on
> 2^100
1267650600228229401496703205376
> fact(25)
15511210043330985984000000

run this session

Every digit, because nothing was ever rounded. This is the clearest case for the tier: whenever an answer needs more than fifteen digits, floating point cannot give it and exact arithmetic can.

What it is not for

It is not a substitute for a well-chosen algorithm. This quadratic has roots near -1e8 and -1e-8, and the small one is the classic casualty of the textbook formula:

> exact off
exact = off
> solve(x^2 + 1e8*x + 1 = 0, x)
x_1 := -1.0E8
x_2 := -1.0E-8
> unset x_1
x_1 unset
> unset x_2
x_2 unset

run this session

The small root is right, in ordinary floating point, because Leonardo already uses the stable rearrangement of the quadratic formula rather than the one that cancels. Turning exact mode on would not have rescued you had it used the naive form, and it was not needed given it does not. Exact arithmetic buys digits; it does not buy numerical judgement.

Turning it off

> exact off
exact = off
> precision 5
precision = 5
> 1/3
0.33333

run this session

Exact mode is off by default, and a session that never touches it behaves exactly as before, so there is no cost to leaving it alone.

Try it yourself

Is (1/7) * 7 exactly one? Check it both ways.

Answers ```text > (1/7) * 7 1.0 > exact on exact = on > (1/7) * 7 1 > exact off exact = off ``` *[run this session](https://lycogrypho.github.io/Leonardo/app/#s=precision%205%0Acolors%20dark%0Apretty%20off%0Alatex%20off%0Anames%20off%0Alogic%20minmax%0Alogic%20symmetric%20off%0Aexact%20precision%2040%0Aexact%20off)* Both print `1` — floating point is lucky here, because the two roundings happen to cancel. **Exactness is about the guarantee, not about whether a particular case gets away with it**, which is the whole argument for having the tier at all.

What you learned

  • exact on makes literals exact — fractions stay fractions, and 14 is an integer. It is a parse-time switch, so set it first.
  • Irrationals are computed to exact precision digits and then carried on with.
  • precision shows, exact precision computes. Separate on purpose.
  • The floating-point error is usually too small to display and too real to ignore.
  • It earns its keep past fifteen significant digits, where a Double stops counting — but it is no substitute for a well-conditioned algorithm.
  • Off by default; leaving it off changes nothing.

Previous: R7 — Drawing and sampling · Next: R9 — Your session · All lessons


This site uses Just the Docs, a documentation theme for Jekyll.