Rust 1.98.0 introduces algebraic_add, algebraic_sub, algebraic_mul, algebraic_div, and algebraic_rem for f32/f64. The important distinction is that these operations allow the compiler to reassociate/reorder floating-point calculations, potentially enabling vectorization and optimizations similar in spirit to -ffast-math. ([Rust Blog][1])
Here’s a practical example showing why this matters.
Example: summing a large array
fn normal_sum(values: &[f64]) -> f64 {
values.iter().fold(0.0, |sum, &x| {
sum + x
})
}
fn algebraic_sum(values: &[f64]) -> f64 {
values.iter().fold(0.0, |sum, &x| {
sum.algebraic_add(x)
})
}
fn main() {
let values = vec![
1.0e16,
1.0,
-1.0e16,
2.0,
3.0,
4.0,
];
let normal = normal_sum(&values);
let algebraic = algebraic_sum(&values);
println!("normal: {normal}");
println!("algebraic: {algebraic}");
}
The key difference is:
sum + x
versus:
sum.algebraic_add(x)
With ordinary floating-point addition, Rust must preserve the order of operations. For example:
(((a + b) + c) + d)
because floating-point addition is not associative.
With:
a.algebraic_add(b)
.algebraic_add(c)
.algebraic_add(d)
the compiler is permitted to treat the operation algebraically and potentially transform it into something equivalent to:
(a + b) + (c + d)
or another mathematically equivalent grouping. That can make parallel execution and SIMD/vectorization easier. ([Rust Blog][1])
A better example: dot product
This is where the feature becomes more interesting for performance-oriented code:
fn dot_product(a: &[f64], b: &[f64]) -> f64 {
a.iter()
.zip(b)
.fold(0.0, |sum, (&x, &y)| {
sum.algebraic_add(x.algebraic_mul(y))
})
}
fn main() {
let a = vec![1.0, 2.0, 3.0, 4.0];
let b = vec![5.0, 6.0, 7.0, 8.0];
let result = dot_product(&a, &b);
println!("dot product = {result}");
}
Conceptually, the calculation is:
1×5 + 2×6 + 3×7 + 4×8
Normally, the compiler has to be conservative about changing the order of those floating-point operations.
Using:
x.algebraic_mul(y)
and:
sum.algebraic_add(...)
gives the optimizer permission to exploit the algebraic properties of multiplication and addition.
For a large dataset, this could potentially allow something closer to:
┌─ x0*y0 ─┐
├─ x1*y1 ─┤
│ ├─ partial sum
├─ x2*y2 ─┤
└─ x3*y3 ─┘
instead of strictly performing every operation sequentially.
All five new methods
Rust 1.98.0 provides:
let a = 10.0_f64;
let b = 3.0_f64;
let add = a.algebraic_add(b);
let sub = a.algebraic_sub(b);
let mul = a.algebraic_mul(b);
let div = a.algebraic_div(b);
let rem = a.algebraic_rem(b);
println!("add = {add}");
println!("sub = {sub}");
println!("mul = {mul}");
println!("div = {div}");
println!("rem = {rem}");
The important thing is not that these produce different mathematical results under normal circumstances. The interesting part is the optimization contract: you’re telling the compiler that you’re willing to give up strict IEEE-style operation ordering so it can optimize more aggressively. The Rust release notes explicitly describe these methods as potentially similar to -ffast-math-style optimization, while still guaranteeing that they don’t introduce undefined behavior. ([Rust Blog][1])
One important caveat: don’t use these blindly for financial, numerical-analysis, or other code where reproducible floating-point results are important. The release notes describe the operations as non-deterministic because the compiler is free to choose different valid optimizations. ([Rust Blog][1])
Rust 1.98.0 release announcement
| [1]: https://blog.rust-lang.org/2026/08/20/Rust-1.98.0/ “Announcing Rust 1.98.0 | Rust Blog” |