Rust 1.98 Adds Algebraic Float Methods, Bringing -ffast-math Style Optimization Without Undefined Behavior

Rust 1.98 Adds Algebraic Float Methods, Bringing -ffast-math Style Optimization Without Undefined Behavior

Rust 1.98.0 was released on August 20, and its headline feature solves a problem numerical programmers have complained about since Rust became viable for that work.

The problem with floating-point associativity

Floating-point addition is not associative. (a + b) + c and a + (b + c) can produce different results, because each intermediate value gets rounded.

This means a compiler seeing a + b + c + d must preserve the left-associative evaluation order. It cannot compute (a + b) and (c + d) in parallel and combine them, even though that would be dramatically faster on any modern vector unit.

C and C++ have addressed this with -ffast-math, which tells the compiler to pretend floating point is associative. It works, and it is also notorious, because it applies globally, changes results in ways that are hard to predict, and in some configurations permits genuinely undefined behavior around NaN and infinity handling.

Rust’s answer

Rust 1.98 adds algebraic methods on f32 and f64: algebraic_add, algebraic_sub, algebraic_mul, algebraic_div, and algebraic_rem.

// Compiler must preserve this order
let sum = a + b + c + d;

// Compiler is free to reassociate and vectorize this
let sum = a.algebraic_add(b).algebraic_add(c).algebraic_add(d);

The design decisions here are the interesting part.

It is opt-in per operation, not per compilation unit. You mark the arithmetic where reassociation is acceptable, and everything else keeps strict semantics. That is a much better default than a compiler flag that silently changes every float operation in your program.

The result is non-deterministic but never undefined. You may get a slightly different answer between runs or between targets. You will not get undefined behavior. That distinction is the entire reason this could be added to safe Rust at all, and it is what -ffast-math does not guarantee.

For anything doing large sums, dot products, or reductions where you already accept floating-point error, this unlocks vectorization that was previously unavailable without dropping to intrinsics.

Faster integer formatting

Rust 1.98 also adds a format_into method on all primitive integer types, working with the newly stabilized NumBuffer type, which provides storage sized for the decimal representation of any value of that type.

This effectively brings the itoa crate’s approach into the standard library. If you format a lot of integers, which is most logging and serialization code, this removes an allocation from the hot path.

Other changes

ManuallyDrop<Box<_>> behavior now has a stable guarantee, along with several newly stabilized APIs.

rustup update stable
rustc --version

This lands the day after the arrayref supply-chain attack, which is unrelated but a reminder that cargo update deserves the same scrutiny as rustup update.

Background reading

Explainers for the concepts behind this story.