You should have seen my face when debugging this.
Here's what I'm seeing:
---
JavaScript
(function() {
let inc = 0.37;
let times = 5000;
let total = 0;
for (let i = 0; i < 5000; i++) {
total += inc;
}
console.log('expected:', inc \* times);
console.log('actual:', total);
})();
expected: 1850actual: 1849.9999999997679
---
Java
public class MyClass {
public static void main(String args[]) {
double inc = 0.37;
double times = 5000;
double total = 0;
for (double i = 0; i < 5000; i++) {
total += inc;
}
System.out.println("expected: " + (inc \* times));
System.out.println("actual: " + total);
}
}
expected: 1850.0actual: 1849.9999999997679
---
update: Changing `double` to `float` in Java yields:
expected: 1850.0
actual: 1849.9778
and maybe that lines up with what you meant by "the differences should be a LOT farther than 3 digits", though it's hard to tell what you mean by "differences on the order of 10-20".
declare @i float =0.37,@times int = 0,@total float = 0
while @times < 5000
begin
set @total+= @i
set @times +=1
end
select @total
1849.99999999977or
select sum(t) from(
select top 5000 convert(float, 0.37) t
from sysobjects a cross join sysobjects b) z
1849.99999999977Do the sum two ways:
- export them in excel and do a sum
- select sum(value) from t
and you get two different values, with a difference of something like 15.
Replace double(10,2) with decimal(10,2), and the sum(value) works correctly.
I still have no idea why this happens. I could expect a tiny difference, and code like your above will indeed give differences like this, well under 1.0, but my particular scenario had differences many orders of magnitude higher. Still can't explain.
Oh, that's something entirely different. If some of your other numbers are much larger than 0.37, you will run into a different kind of problem.
You know about exponent and mantissa? Adding a value with a small exponent to a value with a big exponent will cause imprecisions. In extreme cases the result is just the bigger number.
But actually, since fixed point decimals are not all ways available use the smallest unit of account, not smallest legal tender, and integer arithmetic
And learn how to round
If you really don’t have access to a decimal type I think the best solution is to convert it to micro-units (price * 10^6). This is what Android does in its billing library.
What people use as cash is a distraction, generally
1. Decimal math
2. Binary floating point math
3. Domain knowledge trumps all; it makes no difference
The cardinal sin isn't using doubles for currency; it's using them without understanding either the tool or the job that you're asking the tool to perform.
I guess there are some contexts where floats are fine for monetary amounts, for example if you make economic forecasts or simulations.
Then that's your epsilon. Or, more reasonably, one or two powers of 10 further down. Absolutely nobody cares about a millionth of a cent. A few people care about a thousandth, though. Most people get antsy if you can't pin a calculation down to the nearest cent.
Floats are pretty much never fine for currency, but doubles are usually OK. There's a world of difference between a 24-bit mantissa and a 53-bit mantissa.
This is exactly what the article describes and explains. 10 cent plus 20 cent does not equal 30 cent, when using binary floating point. This is not acceptable in accounting, since at one point the error may accumulate and cause an error at the size of a cent (or more).
Eh, doubles are fine for (most) currencies. Just don't do comparisons without appropriate epsilons.
People who compoare floating-point numbers for equality are going to make other fundamental mistakes with whatever data type you force them to work with.