Subtracting large floating point numbers in different languages
geocar.sdf1.org
geocar.sdf1.org
For example, in Racket this also gives 2.0, but if you are using exact numbers (#e), then it gives exact and correct answer 1.0.
Same for Java (and any other language): if you are using Float/Double numbers, then you are loosing precision. Use BigDecimal (or something similar for other languages) if you want exact calculations.
Take, for example, Java: no one is using BigInteger/BigDecimal by default because they are waaay much slower.
You can't have precision for free, can you?
I don't know how much slower, but in theory such hybrid approaches can allow the common case to be just as fast or fast enough (tm) while still returning the correct results for the uncommon case (depending on how much the runtime or compiler can prove about the range of your numbers to enable the fast path.)
EDIT: Poor phrasing...
Also, BigDecimal doesn't give "exact calculations" unless you stick to numbers with finite decimal expansions. BigDecimal can't do 1/3 without rounding.
IEEE 754 double-precision (64-bit) can only represent even numbers in the range 2^53 to 2^54.
Haskell lets us look inside them fairly easily:
> exponent 9999999999999999.0
54
> significand 9999999999999999.0
0.5551115123125783
> exponent 9999999999999998.0
54
> significand 9999999999999998.0
0.5551115123125782
> 2^54 * 0.5551115123125783
1.0e16
> 2^54 * 0.5551115123125782
9.999999999999998e15 >>> 9999999999999999.0
1e+16The color coding of results suggests the author thinks that 2 is wrong and 1 is right, but he's going out of his way to specify floating point numbers, and when subtracting those two floating point numbers the correct answer is 1 and NOT 2.
Eg, Ruby thinks 9999999999999999.0 - 9999999999999998.0 = 2, but 9999999999999999 - 9999999999999998 = 1. Which is...correct. Right? Unless you don't think IEE 754 should be the default?
I feel like the author is trying to make a clever point, but if so, I'm not getting it.
(- 9999999999999999.0l0 9999999999999998.0l0)
However, this will only work (either way) in Lisps where long floats are arbitrary precision, like CLISP. In most Lisps (e.g. SBCL, CCL), long floats are just double floats.
BigDecimal b2 = new BigDecimal("9999999999999998.0");
System.out.println(b1.subtract(b2));
prints 1.0 >>> import decimal
>>> decimal.Decimal('9999999999999999.0')-decimal.Decimal('9999999999999998.0')
Decimal('1.0') >>> 9999999999999999 - 9999999999999998
1 S('9999999999999999.0') - S('9999999999999998.0')
1.0 In [1]: from decimal import Decimal as D
In [2]: D('9999999999999999.0') - D('9999999999999998.0')
Out[2]: Decimal('1.0')
From docs: Decimal “is based on a floating-point model which was designed with people in mind, and necessarily has a paramount guiding principle – computers must provide an arithmetic that works in the same way as the arithmetic that people learn at school.” perl -Mbignum -e 'print 9999999999999999.0-9999999999999998.0;print "\n";'
1Because it evaluates the expression using the Calculator App.