4.5Floating-point types

Last updated June 11, 2026

Integers can't hold 98.6, half a pizza, or anything astronomical. For fractional and very-large-or-small values, Rust has two floating-point types: f32 (4 bytes) and f64 (8 bytes), both following the IEEE 754 standard your processor implements in hardware, both always signed.

The name "floating point" describes the trick: the decimal point floats, so the same machinery stores 0.000000012 and 12,000,000,000 by tracking the digits and the point's position separately, like scientific notation. Rust literals can use scientific notation directly, e meaning "times ten to the":

fn main() {
    let big = 1.2e4;
    let tiny = 9.1093837e-31;
    println!("{big} {tiny}");
}
12000 0.00000000000000000000000000000091093837

(That second one is the electron's mass in kilograms, which is the kind of value these types exist for.)

A float literal needs a decimal point or an exponent (5.0, not 5, which is an integer literal), and defaults to f64. Choosing between the two types is mercifully one-sided:

Best practice

Use f64. It's the default for float literals, modern hardware runs it at full speed, and its extra precision (about 15–16 significant decimal digits, versus about 7 for f32) prevents real problems. f32 is for when something external demands it: graphics APIs, giant arrays where memory halves matter, embedded targets.

The honesty problem

Now the part of the lesson that will save you a confused afternoon someday. Floating-point types store approximations. They have finite digits (those ~7 and ~15-16 figures above), and worse, they think in binary, and most decimal fractions can't be written exactly in binary, the same way 1/3 can't be written exactly in decimal. The value 0.1 in an f64 is really a number extremely close to 0.1, as printing with forced precision reveals:

fn main() {
    println!("{:.17}", 0.1);
    println!("{}", 0.1 + 0.2);
}
0.10000000000000001
0.30000000000000004

({:.17} requests 17 digits after the point; the formatting machinery gets proper coverage in lesson 5.5.) That second line is the most famous wart in all of computing, and it isn't a Rust bug: it's 0.1 + 0.2 computed faithfully on approximations of 0.1 and 0.2, in every IEEE 754 language on earth. Normal printing rounds kindly, so the wart hides until arithmetic accumulates it into view:

fn main() {
    let sum = 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1;
    println!("{sum}");
    println!("{}", sum == 1.0);
}
0.9999999999999999
false

Ten dimes don't make a dollar, says the computer, missing by one part in ten quadrillion.

Warning

Never compare floating-point values with ==. Two computations that are mathematically equal routinely differ in their final bits, as above, so equality tests on floats are coin flips weighted against you. The professional technique (compare the difference against a tolerance) is in lesson 6.4; until then, the discipline is just: don't.

Key insight

Floating-point rounding is not an occasional malfunction; it's the constant, designed behavior of types that trade exactness for range. The right mental model: an f64 gives you 15-ish trustworthy digits, and arithmetic slowly spends them. For human-money sums and modest science, that budget is enormous. Just spend it knowingly, and (chapter 18 will repeat this) never store currency in floats when exactness is legally interesting.

The edge values

Integer division by zero panics (lesson 3.1). Floats, following IEEE 754, do something stranger: they have special values for the occasion, and no panic at all.

fn main() {
    println!("{}", 5.0 / 0.0);
    println!("{}", -5.0 / 0.0);
    println!("{}", 0.0 / 0.0);
}
inf
-inf
NaN

Positive and negative infinity behave about how you'd hope (anything finite is less than inf). NaN, "not a number," is the designated result for nonsense like 0/0, and it's contagious (arithmetic with NaN yields NaN) and famously antisocial:

fn main() {
    let nan = f64::NAN;
    println!("{}", nan == nan);
}
false

NaN is the only value in Rust that isn't equal to itself, which is IEEE 754's way of saying "this isn't a value, stop comparing it" (there's a is_nan() method for asking properly). You'll meet NaN rarely, but when you do, knowing its handshake saves an hour of disbelief.

Quiz time

Question #1

What does this print, and why isn't it 2?

fn main() {
    println!("{}", 7.0 / 2.0);
    println!("{}", 7 / 2);
}
Show solution
3.5
3

Float division keeps the fraction; integer division (two integer operands) discards it, keeping only the whole part. Same symbol, two operations, chosen by the operand types (the full story is lesson 6.2).

Question #2

A colleague's loop adds 0.01 to a total ten thousand times and then checks total == 100.0, which fails. Diagnose, in one sentence each: what happened, and what should the check become?

Show solution

Each 0.01 is a binary approximation, and ten thousand additions accumulated the tiny errors, so total is extremely close to, but not exactly, 100.0. The check should test whether the difference is smaller than a tolerance (lesson 6.4's technique), not exact equality.

Question #3

True or false: f64::NAN == f64::NAN. And how should you test whether a value is NaN?

Show solution

False (uniquely, deliberately false). Use the method built for it: value.is_nan().

From numbers to the smallest type of all: two values, one byte, and most of the decision-making in every program you'll ever write.