Subtle floating-point differences between C library and its Rust re-write

Yes: Rust's std float operations will use the platform implementations where conventional, and this exposes you to variations in libm accuracy. If you want enhanced determinism, using the pure Rust libm implementation is both necessary and sufficient (plus avoiding the std implementations).


I wonder if some of the "nondeterministic floating point" mythos also comes from people cargo culting -ffast-math flags without understanding exactly what that's doing.

3 Likes

The default for -mfpmath on 32-bit x86 is -mfpmath=387; on 64-bit x86, it's -mfpmath=sse2. Unless you've taken steps to change this, you'll be using -mfpmath=387, and thus have results that are non-deterministic between compiles - a given binary will still be deterministic (the x87 FPU is fully deterministic), but the compiler may choose different spills to memory for the same source each time it's run.

SSE2 (the default on 64-bit x86) avoids this, because it sticks to your chosen IEEE format throughout the calculation - this is the norm on most platforms defined since IEEE 754 came out in 1985, but x87 has history that predates IEEE 754.

If you're converting floating point code from C to Rust, I would strongly recommend using a fully IEEE 754 compliant platform, and expecting exactly the same answers from C and from Rust - on x86, this means using a 64 bit platform, not a 32 bit platform, and making sure that nobody sets -mfpmath=387 or -mfpmath=387,sse2 or -mfpmath=sse2,387 in their C compiler flags.

1 Like

#define ERFA_TURNAS 1296000.0
#define ERFA_DAS2R 4.848136811095359935899141e-6

const ERFA_TURNAS: f64 = 1296000.0;
const ERFA_DAS2R: f64 = 4.848136811095359935899141e-6;

printf("%.16f\n", eraFaom03(1.2));

$ gcc -Wall -O2 -s erfa.c -lm -o erfa && ./erfa
and cargo run --release

The result is equal (...398361 at the end).

Most likely FMA is used if found.

FMA is critical for precise calculations, but can lead to the same issue as use of 8087 instructions if used by compiler for optimizations.

1 Like

You can use -ffp-contract=off to disable this optimization in C (it's on by default in most dialects when compiling for a target that supports FMA). It's off by default in Rust (even for targets that support the instruction) and we currently don't have a way to enable contraction without enabling a host of other (unsafe) optimizations. That is, you can explicitly use the nightly fadd_fast and fmul_fast, which will enable contractions (actually improving the accuracy except in special cases), but it also enables assuming arguments are finite and other stuff that's generally unsafe. There is a need for Rust to expose more conservative subsets of the LLVM math flags.

3 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.