Faster floating point math with Rust's new API

birdculture1 pts0 comments

Faster floating point math with Rust’s new API

Faster floating point math with Rust’s new API

by Itamar Turner-Trauring<br>Last updated 02 Aug 2026, originally created 02 Aug 2026

Floating point math is often slower than integer math because the<br>compiler is being conservative about how it optimizes your code. While<br>some programming languages already had solutions of a sort, until now<br>Rust did not have a good stable way to deal with this limitation. But<br>now, starting in version 1.98, Rust will allow telling the compiler it<br>can optimize your code further—but with extra control so that you can<br>still write numeric algorithms with minimal rounding errors.

In this article you will learn:

Why by default the compiler won’t optimize floating point math as much<br>as it does integer math.

Rust’s new API to solve this limitation.

Examples of using this new API, its speed impact, and how you can<br>control where it is used.

Summing integers is fast

I’m going to start with an example using integers, as a baseline of what<br>sort of performance is possible.

To get the fastest code generation, I’m telling Rust that it’s not 2004<br>anymore,<br>and that it can generate CPU instructions that require modern hardware,<br>namely x86-64 machines from the past 10 years or so. Specifically, all<br>the code in this article is being compiled with<br>RUSTFLAGS="-C target-cpu=x86-64-v3". (For maximum compatibility, in<br>real-world usage you could provide a fallback implementation for older<br>computers.)

Here’s a Rust function to sum a slice of int64 numbers:

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

I’ll omit the code to expose this to Python, but it’s a variant of the<br>Rust/Python code in a previous<br>article.

To benchmark it, I’ll create an array of integers in NumPy:

import numpy as np

DATA_INT = np.ones((1_000_000,), dtype=np.int64)<br>assert naive_sum_i64(DATA_INT) == 1_000_000

And now I can measure the speed of summing this array:

Code<br>➘ Elapsed µ-seconds

➘ CPU instructions per value

naive_sum_i64(DATA_INT)<br>168.1

0.5

➘ Lower numbers are better

That’s 0.5 CPU instructions per value! How does that even work?

Probably the compiler is using specialized Single Instruction, Multiple<br>Data (SIMD) CPU instructions, that do batch operations on multiple<br>values at once. The i7-12700K CPU I’m using here has 256-bit SIMD<br>instructions, meaning it can do some specific operations on four 64-bit<br>integers at a time. If there’s a specialized SIMD summing CPU<br>instruction, the CPU would only need to loop 250,000 times and then sum<br>4 integers in each iteration.

And in fact:

Code<br>➘ Elapsed µ-seconds

➘ CPU instructions<br>256-bit SIMD integer instructions

naive_sum_i64(DATA_INT)<br>156.8

521,280<br>250,003

➘ Lower numbers are better

In short, by using a specialized SIMD instruction, my CPU can sum<br>integers very quickly.

Summing floats is slow?!

But what about floats—are they fast too?

Again, I’ll create a million floating point values:

# Array of 1M float64 values between 0 and 1.<br>DATA = np.random.random((1_000_000,))

I’ll implement a simple floating point sum function:

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

And compare the performance of summing integers and floats:

Code<br>➘ Elapsed µ-seconds<br>➘ CPU instructions<br>256-bit SIMD integer instructions<br>256-bit SIMD float instructions

naive_sum_i64(DATA_INT)<br>151.9<br>521,214<br>250,003

naive_sum(DATA)<br>595.2<br>1,458,269

➘ Lower numbers are better

The floating point sum is much slower than the integer sum, and the<br>compiler didn’t use SIMD float operations. Why the difference?

Floating point operations aren’t associative

Like most compilers, when Rust compiles your code in release mode it<br>optimizes your code, transforming it in a variety of ways to (hopefully)<br>make it faster. But there’s a promise compilers make when they do this:<br>the optimized code will behave exactly the same as the unoptimized<br>code.

If I add three integers a, b, and c, a + (b + c) == (a + b) + c. That gives the<br>compiler plenty of scope to optimize how the code runs, for example by<br>using SIMD operations that might slightly change the order of additions.

Floating point numbers are different. For example, because floating<br>point numbers span such a range of values, from tiny to huge, adding a<br>sufficiently large number to a sufficiently small number results in that<br>same large number:

print(<br>"Does adding a small number do nothing?",<br>1e16 + 1.0 == 1e16

Does adding a small number do nothing? True

More broadly, for floating point numbers, a + (b + c) is not always the same<br>as (a + b) + c, at least once you’re adding multiple numbers in a row. Let’s<br>say I have an array that starts with 1e16 followed by many 1.0<br>values, and another that is the reverse. Summing these arrays will give<br>different results:

import math

HIGH_VALUE_FIRST = np.ones((1_000_000,), dtype=np.float64)<br>HIGH_VALUE_FIRST[0] = 1e16<br>HIGH_VALUE_LAST =...

code floating point rust instructions values

Related Articles