When I format a float out to 5 decimal places, I'm sort of making a statement that anything beyond that doesn't matter to me.
When I format a float out to 5 decimal places, I'm sort of making a statement that anything beyond that doesn't matter to me.
One that comes to mind for me is the old game Kohan: Immortal Sovereigns. There was a native Linux port for the game that could play online with the Windows machines. However, the pathfinding solution used floating point numbers to make the decisions. A very small difference in the way Linux and Windows rounded the numbers meant that an online game between a Linux and Windows host would de-synchronize after 15-30 minutes because a model that zigged on the Linux host would zag on the Windows host, and the game would detect the difference in state after a bit and kick one of the players out. There was no way to rejoin the game at this point either.
This bug was never fixed, and the company that made the port (Loki) went out of business.
Yes, it's true the vast majority of the time it doesn't actually matter. However decimal formatting does such a good job at giving us the impression that these errors are merely edge-cases, and calculators automatically formatting to 10sf etc further that illusion. If people are not aware it's only an illusion (or just how far the rabbit hole goes) it can be dangerous when they go on to create or use things where that fact matters.
I'd be curious to hear some of the problems programmers have run into from this conceptual discrepancy. We've got probably billions of running instances of web frameworks build atop double precision IEEE 754 to choose from. Are there any obvious examples you know?
These are an easy target to find issues that _matter_ because users are essentially fuzzing the inputs, so they are bound to find an error if it exists, and they will also care when they do!
When these oversights become a problem is very context sensitive. I suppose mine is quite biased.
That is far inside the range of 64bit double precision. For error to propagate up to that range of significance depends on the math, but i doubt the aggregation you are describing would cause it... provided nothing silly happens to subtotals like intermediate rounding to precision (you'd be surprised).
Something like compounding as the parent was describing are far more prone to significant error propagation.
In a real-life transaction where pennies are not exchanged this could mean a difference of a nickel on a $20 purchase which isn't a meaningful difference but certainly not insignificant.
What does this mean to you? It's very easy to get horrible rounding error with real-life sized things. For instance
document.writeln(1.0 % 0.2);
The right answer is 0.0, and the most it can be wrong is 0.2. It's nearly as wrong as possible. These are real-life sized numbers.btw: I think IEEE-754 is really great, but it's also important to understand your tools.
The types of errors being discussed by others are all in the realm of non-integer rationals where limitations in either precision or representation introduce error and then compound in operations no matter the order of magnitude... and btw _real_ life tends to contain _real_ numbers, that commonly includes rationals in use of IEEE 754.
> The right answer is 0.0, and the most it can be wrong is 0.2. It's nearly as wrong as possible.
Just to clarify for others, you're implicitly contriving that to mean: you care about the error being positive. The numerical error in 0.1 % 0.2 is actually fairly ordinarily tiny (on the order of x10^-17), but using modulo may create sensitivity to these tiny errors by introducing discontinuity where it matters.
Not sure why you changed it from "1.0 % 0.2" to "0.1 % 0.2". The error on the one I showed was near 0.2, not 1e-17. Did I miss your point?
I'm not arguing against you just clarifying the difference between propagation of error into significant numerical error through something like compounding; and being sensitive to very tiny errors by to depending on discontinuities such as those introduced by modulo.
x = 1e-20 + 1e20 - 1e20
y = 1e20 - 1e20 + 1e-20
assert x == y* Never mind that "millions" isn't large by current standards...
x = 1e20 + 1 - 1e20
y = 1e20 - 1e20 + 1
assert x == y
There is only 1 digit, and it's wrong. You don't even need 6-8. I probably should've used this as my example in the first place. class Time {
uint32 m_CycleCount;
float m_CyclesPerSec;
float m_Time;
public:
Time() {
m_CyclesPerSec = CPU_GetCyclesPerSec();
m_CycleCount = CPU_GetCurCycleCount();
m_Time = 0.0f;
}
float GetTime() { return m_Time; }
void Update() {
// note that this is expected to wrap
// during the lifetime of the game --
// modular math works correctly in that case
// as long as Update() is called at least once
// every 2^32-1 cycles.
uint32 curCycleCount = CPU_GetCurCycleCount();
float dt = (m_CycleCount - curCycleCount) / m_CyclesPerSec;
m_CycleCount = curCycleCount;
m_Time += dt;
}
};
void GAME_MainLoop() {
Timer t;
while( !GAME_HasQuit() ) {
t.Update();
GAME_step( t.GetTime() );
}
}
The problem is that m_Time will become large relative to dt, the longer the game is running. Worse, as your CPU/GPU gets faster and the game's framerate rises, dt becomes smaller. So something that looks completely fine during development (where m_Time stays small and dt is large due to debug builds) turns into a literal time bomb as users play and upgrade their hardware.At 300fps, time will literally stop advancing after the game has been running for around 8 hours, and in-game things that depend on framerate can become noticably jittery well before then.
https://randomascii.wordpress.com/category/floating-point/
https://stackoverflow.com/questions/46028336/inconsistent-be...
The root problem, of course, was that 0.2 + 0.1 <= 0.3 isn't actually true in floating point arithmetic.
It wasn't immediately obvious where the problem was since there were a lot of moving parts in play, and it did not immediately occur to us to doubt the seemingly simple arithmetic.
One thing along similar lines that I've always hated to see is when the floating-point type in a programming language is called "real".
Whereas, even between their minimum and maximum values, and even subject to their numeric precision, there are still so many rational numbers that an IEEE float can't represent. So it's not even a rational. Nor can it represent a single irrational number, thereby failing to capture one iota of what qualitatively distinguishes the real numbers. . . if Hacker News supported gifs, I'd be inserting one that features Mandy Patinkin right here.
Thanks for the read! More fun than the original article to my taste :)
Here's the link, since Googling "floating bar" nets you very different results:
http://www.iquilezles.org/www/articles/floatingbar/floatingb...
My favorite post by him is where he explains that you never need to have trigonometry calls in your 3D engine. Because everyone now and then you still see "educational" article in the spirit of "learn trig to spin a cube in 3D!" :/
I guess the smart people writing a lot of this stuff just assume everyone will derive them as they go. That's why we need more Inigo Quilez's :D to lay it out for us mere mortals and encourage it's use more widely.
You can't say the same about most real numbers, all the ones that we postulate must exist but that have infinite complexity. You can't ever construct a single one of them.
Or in other words: It can store all the non-computable numbers you give it. That should suffice.
Of course any self-respecting modern language should also provide arbitrary-precision integers at least as an option.
Don't knock the impact of cumulative loss of precision.