DevNews

Rust 1.98 algebraic floats: accuracy and regrouping

On this page
  1. The operation opts into different numerical semantics
  2. One small example explains why order matters
  3. Measure the workload and its error together
  4. Prefer the corrected compiler in the 1.98 line

Rust 1.98 introduced algebraic floating-point methods that permit additional optimization. Their value depends on whether the numerical changes are acceptable for a particular calculation. The later 1.98.1 maintenance release also matters when choosing the actual compiler.

Ordinary binary64 regrouping example, checked locally in JavaScript: with a=10^16, b=-10^16 and c=1, (a+b)+c gives 1, while a+(b+c) gives 0. This is not a Rust optimization or speed measurement.
Ordinary binary64 regrouping example, checked locally in JavaScript: with a=10^16, b=-10^16 and c=1, (a+b)+c gives 1, while a+(b+c) gives 0. This is not a Rust optimization or speed measurement. Chart : PeopleAreGeek. Data source.
View full-size image

The operation opts into different numerical semantics

The 1.98.0 announcement introduces algebraic addition, subtraction, multiplication, division and remainder for f32 and f64. These methods permit transformations that ordinary floating-point expressions cannot assume, potentially enabling regrouping and vectorization.

The library's algebraic-operator documentation does not specify an exact optimization set or a fixed error bound. It describes nondeterministic results, special-value caveats and the requirement that unsafe code not rely on return-value properties for soundness. The operations themselves do not introduce undefined behavior; that is not a guarantee that every downstream unsafe assumption is valid.

One small example explains why order matters

Consider ordinary binary64 addition rounded to the nearest representable value, with ties to even. Let a = 10¹⁶, b = −10¹⁶ and c = 1.

  • Grouping (a + b) + c first cancels the large values, then returns 1.
  • Grouping a + (b + c) rounds the small increment away in the inner sum, then returns 0.

We checked this arithmetic locally with JavaScript's binary64 numbers. It is an illustration of regrouping, not a Rust compiler benchmark or a promise that algebraic_add chooses either particular form. Both expressions have the same result in exact real arithmetic; finite representation makes the order observable.

That is why a blanket statement that the numerical error is negligible would be unhelpful. The application must define what accuracy and reproducibility it needs before relaxing the calculation.

Measure the workload and its error together

For a proposed experiment, preserve the original implementation as a reference and change only the selected hot operation. Use representative inputs, including cancellation and magnitude differences, and state the acceptable absolute or relative error. Then measure elapsed work under the same build and hardware conditions.

An isolated eightfold gap reported elsewhere does not establish an eightfold improvement for every Rust program. Nor does permission to vectorize guarantee that the optimizer will do so for a specific loop.

Prefer the corrected compiler in the 1.98 line

The September 3 release of Rust 1.98.1 fixes a separate 1.98.0 miscompilation involving a null entry where a trait-object vtable needed a function pointer. The team describes undefined behavior in the emitted code. This is unrelated to the intended relaxed semantics of algebraic arithmetic.

The family also adds buffered integer formatting through NumBuffer and format_into. That can replace some specialized formatting uses, but compatibility with an application's API and performance requirements still needs checking. Upgrading the compiler and opting a numerical loop into algebraic methods are separate changes.

Explain relaxed arithmetic with a binary64 regrouping example, remove universal 8x claim, and include September 3 1.98.1 miscompilation fix.