Can someone explain why I would see precision drift for simple floating operations that do not produce repeating decimals?
I tried these both in Windows and WSL (does that count as Linux?). I can understand precision drift of doing operations on numbers like 0.33 or 0.66, but simply adding 0.1 produces the below.
f1: f16
for i in 0..<10 {
f1 += 0.1
fmt.println("f16", f1)
}
f2: f32
for i in 0..<10 {
f2 += 0.1
fmt.println("f32", f2)
}
f3: f64
for i in 0..<10 {
f3 += 0.1
fmt.println("f64", f3)
}
Floating point numbers have a host of odd behavior depending on what standard of representation you are dealing with, the operations you are performing with them, and the hardware that is performing said operations. Most modern hardware have optimizations, lookup tables, etc. that attempt to minimize the inaccuracy you are experiencing, but intentions are not always easily expressed and not all operations can be performed as precisely or accurately as one might expect.
Here is an excerpt from Wikipedia that highlights a specific issue with the addition of 0.1:
The fact that floating-point numbers cannot accurately represent all real numbers, and that floating-point operations cannot accurately represent true arithmetic operations, leads to many surprising situations. This is related to the finite precision with which computers generally represent numbers.
For example, the decimal numbers 0.1 and 0.01 cannot be represented exactly as binary floating-point numbers. In the IEEE 754 binary32 format with its 24-bit significand, the result of attempting to square the approximation to 0.1 is neither 0.01 nor the representable number closest to it.
The general rule of thumb I was taught for floating point value accuracy is exactly what you have highlighted in your post; the greater the desire for accuracy, the larger the byte size for the representation.
That explains it very well. After reading that, it’s clear I’ve forgotten many details over years. I’ve always been aware of floating precision problems, but forgot how fundamental a problem it is, with even the most basic of representations. Admittedly it is humbling to relearn these things.
Floating point numbers come with a handful of (minor) footguns like this. I find it’s also generally best practice to pretend that == and != are undefined for floats because of these precision issues.
Floats trip up so many beginner programmers and even some senior developers. The greatest temptation of all is to use floats to represent money. Great way to get the accounting department mad at you.
It’s funny. After posting this, I realized I’ve done this many times before.
Good thing to, as you say, stay away from == and != for floats. Fortunately, that best practice has stuck over the years. It’s always been a reflex to use <= or >= when working with numbers, just in case a condition jumps past the narrow bounds of == or !=. Same good habit to not use floats for money, etc. It seems my mathy side refuses to except such a fundamental problem exists with floats.