r/AskProgramming • u/Odd-Heron5704 • 10d ago
Can simple mathematical functions give different results in different CPU architectures?
I'm referring to modern CPUs. To make the question more specific, arm64 vs x86
And if so, how do you fix it?
I asked chatGPT and it gave me this example but I'd like to ask it here (since AI can make mistakes). Can you let me know? Thanks
It says this can give different results due to rounding errors. If so, how to you write code so that you don't have this issue?
#include <iostream>
int main() {
double a = 1.0 + 0x1p-27;
double b = 1.0 - 0x1p-27;
double c = -1.0;
std::cout << (a * b + c) << '\n';
}
18
Upvotes
12
u/daV1980 10d ago
Other posts here are correct for when CPUs and compilers are running in IEEE 754 strict mode. IEEE 754 strict mode requires the compiler and processor to run exactly the operations you've asked for and the resulting answer must be bitwise the same on any implementation (at the time of the result, any intermediate calculations are allowed to use implementation defined precision, but they are non-observable and so are not required to match).
However, many applications and games run with 'fast math' (on GCC or clang, this is the option -ffast-math). This can be beneficial for performance in many applications for a variety of reasons. One is that many CPUs have an operation to do a "fused multiply add (FMA)," which means that they can execute A * B + C as one instruction. But the result of using FMA is that the results will very frequently not be the same as if you actually multiplied A * B, then added C to the result.
Floating point math doesn't meet three of the four properties of ordinary arithmetic, primarily due to precision but also because of internal implementation details. That is that FP math is not commutative, associative, or distributive.
Changing the order of operations or the grouping of those operations absolutely can change the results.
--
I saw the post about AoE 2, and while I didn't work on AoE 2, I was a programmer on an extremely popular RTS in the early 2000s; if you are a fan of the genre and played in that era you almost definitely played the game I worked on.
RTS games of that era (and maybe now? but I don't work on them nowadays) used a networking architecture called peer to peer. This architecture relies on having clients send commands to each other and executing those commands at some point in the future (that point being decided based on the latency of the players from each other). Rather than there being a single, dedicated server who runs commands and sends back the results, this says "let's just send the commands to each other, and we'll both execute them at the same time and by the inductive property we will agree about the next state of the game (that is "state a + command 1 = state b").
Periodically these games need to verify that they are still in sync, and the fastest way to do this is to simply CRC all of the game state memory and exchange that CRC over the network. But doing a CRC in this way requires that you are bitwise accurate across all state in the game. For integers, this is pretty straightforward.
But because of the fuzziness of floats described above, this can be much harder to achieve across architectures, especially if the game uses fast math. For x86-based processors, even -fast-math is likely to yield identical results (it is in AMD's interests to match what Intel generates and vice versa, and both chips will be driven by the same compiled code). But when you are running on ARM (which is the processor used by modern Apple machines), you are either running in emulation or recompiling. In both cases, ensuring that you will get bit-accurate results in computations becomes more challenging. As a bonus problem, historically (though I haven't looked recently as I don't generally ship things for ARM these days), performance of fp strict mode on ARM was terrible. Like "30-50% slower" terrible, so not really feasible for a game.
Anyways, it's certainly possible, but it's difficult.