logo
Kurashizu Blogwhere ideas flow
  • Home
  • News
  • Blog
  • About
  • Home
  • News
  • Blog
  • About

© 2026 Kurashizu. All rights reserved.

Admin·New Post·Service Status
Powered by Cloudflare·01.11.26
← Back to news

Faster floating point math with Rust's new API

Original article
pythonspeed.com|91 points|30 comments|by subset|Aug 6, 2026

Accelerating Floating Point Calculations via Rust's Latest API

By Itamar Turner-Trauring Updated: August 2, 2026

Generally, floating point arithmetic lags behind integer math in terms of speed. This happens because compilers typically adopt a conservative stance when optimizing your code to ensure precision.

While other languages have implemented workarounds, Rust lacked a stable, integrated method to bypass this limitation—until now.

Starting with version 1.98, Rust introduces a mechanism to signal to the compiler that more aggressive optimizations are permissible. This provides developers with granular control, allowing for high-performance numeric algorithms without accidentally introducing unacceptable rounding errors.

🎯 Learning Objectives

  • Understand why compilers are hesitant to optimize floating point math.
  • Explore the new API with practical examples.
  • Analyze the performance gains and how to implement targeted optimizations.

The Efficiency of Integer Summation

To establish a performance baseline, let's look at how Rust handles integers. To ensure the most efficient machine code, I am instructing the compiler to target modern hardware (specifically x86-64 machines from the last decade) rather than legacy 2004-era CPUs.

I am using the following configuration: RUSTFLAGS="-C target-cpu=x86-64-v3"

(Note: For production software, you should implement a fallback for older architectures to maintain compatibility.)

Here is a basic Rust function designed to sum a slice of 64-bit integers:

fn naive_sum_i64(values: &[i64]) -> i64 {
    let mut total = 0;
    for value in values {
        total += value;
    }
    total
}

To test this, I used NumPy to generate a dataset:

import numpy as np
# Create an array of 1M integers
DATA_INT = np.arange(1_000_000, dtype=np.int64) 
assert naive_sum_i64(DATA_INT) == 499_999_500_000 # Example sum

Performance Results: Integers

ImplementationElapsed μs\mu sμsCPU Instructions / Value
naive_sum_i64(DATA_INT)168.10.5 ↓\downarrow↓

↓\downarrow↓ Lower values indicate better performance.

Achieving 0.5 instructions per value suggests the compiler is utilizing SIMD (Single Instruction, Multiple Data). On my i7-12700K, 256-bit SIMD instructions allow the CPU to process four 64-bit integers simultaneously. Instead of 1,000,000 iterations, the CPU only needs to loop 250,000 times.

Detailed Instruction Breakdown:

ImplementationElapsed μs\mu sμsTotal CPU Instructions256-bit SIMD Int Instructions
naive_sum_i64(DATA_INT)156.8521,280250,003

The Floating Point Bottleneck

Now, let's apply the same logic to floating point numbers. First, we generate a million float64 values:

# Array of 1M float64 values between 0 and 1
DATA_FLOAT = np.random.random((1_000_000,))

And the corresponding Rust implementation:

fn naive_sum(values: &[f64]) -> f64 {
    let mut total = 0.0;
    for value in values {
        total += value;
    }
    total
}

Comparison: Integers vs. Floats

CodeElapsed μs\mu sμsCPU InstructionsSIMD IntSIMD Float
naive_sum_i64151.9521,214250,0030
naive_sum595.21,458,26900

The floating point version is significantly slower, and crucially, the compiler refused to use SIMD float operations.


Why the Compiler Hesitates: The Associativity Problem

When Rust optimizes code for release, it transforms the logic to increase speed. However, it adheres to a strict contract:

"The optimized code must behave exactly the same as the unoptimized code."

For integers, addition is associative: Let a,b,c∈Z  ⟹  a+(b+c)=(a+b)+c\text{Let } a, b, c \in \mathbb{Z} \implies a + (b + c) = (a + b) + cLet a,b,c∈Z⟹a+(b+c)=(a+b)+c

This allows the compiler to reorder operations to fit SIMD registers. However, floating point numbers are different. Because they represent a massive range of scales, adding a tiny number to a massive one can result in the tiny number being "swallowed."

print("Does adding a small number do nothing?", 1e16 + 1.0 == 1e16)
# Output: Does adding a small number do nothing? True

In the world of f64, the order of operations changes the result: a+(b+c)≠(a+b)+ca + (b + c) \neq (a + b) + ca+(b+c)=(a+b)+c

Proof of Order Dependency

If we sum an array where the large value is at the start versus the end, the results differ:

import numpy as np

# Large value at the beginning
HIGH_VALUE_FIRST = np.ones(1_000_000, dtype=np.float64)
HIGH_VALUE_FIRST[0] = 1e16

# Large value at the end
HIGH_VALUE_LAST = np.ones(1_000_000, dtype=np.float64)
HIGH_VALUE_LAST[-1] = 1e16

print("Is the sum the same?", naive_sum(HIGH_VALUE_FIRST) == naive_sum(HIGH_VALUE_LAST))
# Output: Is the sum the same? False

Because the compiler cannot guarantee that reordering won't change your result, it stays conservative and avoids optimizations like SIMD.


The Solution: Rust's Algebraic Operators

Sometimes, the programmer knows that a slight change in rounding or order is acceptable for the sake of massive speed gains. Rust 1.98 introduces algebraic operators to handle this.

These operators explicitly tell the compiler: "You may treat these floating point numbers as if they follow the standard algebraic properties of real numbers."

Compiler Decision Logic

By using these new operators, you can selectively opt-in to high-performance math where precision loss is negligible, while keeping the standard operators where exact ordering is critical.

Floating Point Optimization Concept

Summary of the new approach:

  • Standard Operators →\rightarrow→ Strict adherence to IEEE 754 order.
  • Algebraic Operators →\rightarrow→ Permission to optimize using real-number algebra.