Skip to content
Select theme

How to round a float to a number of decimals

Short answer: round() rounds to the nearest whole number and hands it back as the same float type — 2.5_f64.round() is 3.0, an f64, not an integer. For “two decimal places” there are two different operations, and they give different answers: scale, round, and unscale ((x * 100.0).round() / 100.0) produces a new float value, while format!("{:.2}", x) renders the value you already have. If the number is about to be printed, the second one is usually what you wanted.

From a fresh project — cargo new round-demo, then replace src/main.rs with each block below.

2 3 4 -3

Halfway cases go away from zero: 2.5 rounds up to 3, 3.5 rounds up to 4, and -2.5 rounds down to -3. That part matches most people’s school rounding.

The return type is the surprise. round() does not give you an integer — it gives the nearest whole number as the same float type. There are two reasons for that, and both are about what an f64 can hold. First, whole numbers are exactly representable as floats (every f64 is an integer above 2⁵³), so the result needs no new type. Second, a float can sit far outside any integer type: 1e300_f64.round() is 1e300 — printing it produces a 301-digit number — and no i64 could carry that result.

Terminal window
cargo run -q
error[E0308]: mismatched types
--> src/main.rs:2:18
|
2 | let n: i64 = 2.5_f64.round();
| --- ^^^^^^^^^^^^^^^ expected `i64`, found `f64`
| |
| expected due to this
For more information about this error, try `rustc --explain E0308`.
error: could not compile `round-demo` (bin "round-demo") due to 1 previous error

The compiler will not guess a target integer type for you. If an integer is what you need, round first and convert explicitly — 2.5_f64.round() as i64 is 3 — or use try_from for a checked conversion. The trade-offs of that step are the subject of What is the difference between as and From for casting?.

There is no built-in “round to n decimals”. The classic recipe multiplies by 10ⁿ, rounds, and divides back:

3.14
2.67499999999999982236
x * 100.0 = 267.5
2.68

The first line is the recipe working as advertised: 3.14159 in, 3.14 out. The rest is the trap. 2.675 prints as 2.675, but that is a lie of formatting — the value actually stored is 2.67499999999999982236, because binary floats cannot represent most decimal fractions exactly. Multiplying by 100 does not help: the true product is 267.499999999999982236, and that product itself gets rounded to the nearest representable f64, which happens to be exactly 267.5. Then .round() sees a clean halfway case and gives 268. The scale trick rounds three times, and the extra roundings can push the result either way.

2.67
2.675
0.12 vs 0.13

{:.2} prints 2.67 — a correctly rounded decimal picture of the value that is really stored — while the scale trick on the same number gives 2.68. The stored value does not change either: plain {} still shows 2.675, because formatting only decides how the number prints. The two operations even break ties differently: 0.125 lands exactly halfway in binary, and formatting rounds it toward the even digit (0.12) while round() goes away from zero (0.13).

So they are different tools, not two spellings of one thing. Format when the number is about to be shown — one rounding, no mutation, and the digits match the value you actually have. Scale and unscale only when a float value itself must stay in calculations on a coarser grid. The full formatting spec — width, fill, sign, precision — lives in How to format strings with println! and format!.

One more boundary: if the number is money, neither approach fixes the real problem. No binary float represents 0.01 exactly, so cents should live in an integer (let cents: i64 = 1499;) and get a decimal point at the edge — by formatting, which is now the only place one exists.