How does one round a floating point number to a specified number of digits?
Asked Answered
T

5

131

How does one round a f64 floating point number in Rust to a specified number of digits?

Tahoe answered 22/2, 2015 at 6:55 Comment(0)
U
179

If you want this just for display purposes, use the formatting syntax built into println!(). For example, to print a number rounded to 2 decimal places use the {:.2} format specifier:

fn main() {
    let x = 12.34567;
    println!("{:.2}", x);
}

If you want to put the rounded number in a string, use the format!() macro.

If you want to round a number and get the result back as another number, then multiply the number by the given power of 10, call round, and divide by the same power, e.g. to round to 2 decimal places, use 102 = 100.

fn main() {
    let x = 12.34567_f64;
    let y = (x * 100.0).round() / 100.0;

    println!("{:.5} {:.5}", x, y);
}

playground

This prints 12.34567 12.35000.

If the number of decimal places isn't known at compile time, one could use powi to efficiently compute the relevant power.

Note that this will breakdown for very large numbers; specifically, numbers larger than std::f64::MAX / power (where power is the power of ten, e.g. 100 in the example above) will become infinity in the multiplication, and remain infinity after. However, f64 cannot represent any fractional places for numbers larger than 253 (i.e. they're always integers), so one can special case such large numbers to just return themselves.

Uncleanly answered 22/2, 2015 at 10:31 Comment(7)
I wonder why there is no parameter for that in .round() - I guess because rust does not have defaults/overloaded functions?Granny
Okay, well, what are we supposed to do if we aren't trying to print it?Glasswort
@JeffDavenport either format! if you want a string, or multiply/round/divide as used to compute y in the second code block (the println!s in that example are just to show that the rounding worked).Uncleanly
Your (x * 100.0).round() / 100.0 method not only breaks down for very large numbers. It is for example also incorrect for let x: f64 = 0.01499999999999999944488848768742172978818416595458984375, which is exactly representable by f64, should round to 0.01 but rounds to 0.02 instead.Embayment
fmt doesn't really "round", it's unreliable as this example illustrates. Sometimes it truncates, sometimes it rounds. It's quite obvious for a value like 0.25, which is 0.01 in binary and thus codes well in IEEE-754. Yet {:.1} yields the erroneous "0.2" instead of "0.3"...Declarer
round is only marginally better, see this example comparing both.Declarer
I think it would be good to emphasize in the second snippit that y still isn't exactly 12.35 -- perhaps also show {y:.20} so it displays as 12.34999999999999964473 to demonstrate that getting 12.35 in the output is still depending on cosmetic rounding, and that the value is the closest representable value to 12.35, but not 12.35 itself.Succinct
K
55

To add to @huon's great answer, if you want to round a floating point number for display purposes, but you don't know the precision at compile time, you can use the precision formatting syntax as follows:

fn main() {
    let precision = 3;
    let x = 12.34567;
    println!("{:.1$}", x, precision); // prints 12.346 and works with `format!` as well
}

The documentation of std::fmt has more examples on the syntax.

Kannry answered 8/4, 2020 at 13:11 Comment(0)
R
5

Example round function with decimal count as a parameter:

fn round(x: f32, decimals: u32) -> f32 {
    let y = 10i32.pow(decimals) as f32;
    (x * y).round() / y
}
Ress answered 21/6, 2022 at 20:52 Comment(1)
assert_eq!(round(4.365, 2), 4.37); failsEck
V
5

In some applications, e.g. science, it makes more sense to round to significant digits rather than digits after the decimal. This is an implementation along with test code and documentation:

fn main() {
  println!("1230000.0 {} {}", 1230000.0, precision_f64(1230000.0, 2));
  println!("123000.0 {} {}", 123000.0, precision_f64(123000.0, 2));
  println!("12300.0 {} {}", 12300.0, precision_f64(12300.0, 2));
  println!("1230.0 {} {}", 1230.0, precision_f64(1230.0, 2));
  println!("123.00 {} {}", 123.00, precision_f64(123.00, 2));
  println!("12.300 {} {}", 12.300, precision_f64(12.300, 2));
  println!("1.2300 {} {}", 1.2300, precision_f64(1.2300, 2));
  println!(".12300 {} {}", 0.12300, precision_f64(0.12300, 2));
  println!(".01230 {} {}", 0.01230, precision_f64(0.01230, 2));
  println!(".00123 {} {}", 0.00123, precision_f64(0.00123, 2));
  println!("1.0000 {} {}", 1.00000, precision_f64(1.00000, 2));
}

/// Round to significant digits (rather than digits after the decimal).
///
/// Not implemented for `f32`, because such an implementation showed precision
/// glitches (e.g. `precision_f32(12300.0, 2) == 11999.999`), so for `f32`
/// floats, convert to `f64` for this function and back as needed.
///
/// Examples:
/// ```
///   precision_f64(1.2300, 2)                      // 1.2<f64>
///   precision_f64(1.2300_f64, 2)                  // 1.2<f64>
///   precision_f64(1.2300_f32 as f64, 2)           // 1.2<f64>
///   precision_f64(1.2300_f32 as f64, 2) as f32    // 1.2<f32>
/// ```
fn precision_f64(x: f64, decimals: u32) -> f64 {
  if x == 0. || decimals == 0 {
    0.
  } else {
    let shift = decimals as i32 - x.abs().log10().ceil() as i32;
    let shift_factor = 10_f64.powi(shift);

    (x * shift_factor).round() / shift_factor
  }
}
Valarievalda answered 28/6, 2023 at 10:31 Comment(0)
T
2

Here's an example which builds on the format! macro, as explained by @huon:

fn more_info(name: &str, height: f32) -> String {
    let response = format!("My name is {name}, and I'm {height:.2} meters tall");
    response
}

fn less_info(name: &str, height: f32) -> String {
    let response = format!("My name is {name}, and I'm {height:.1} meters tall");
    response
}

#[test]
fn test_more_info() {
    let data = &[
        (
            "Bob",
            1.586,
            "My name is Bob, and I'm 1.59 meters tall",
            "round up and truncate to two decimals",
        ),
        (
            "Robert",
            1.7824,
            "My name is Robert, and I'm 1.78 meters tall",
            "no change",
        ),
    ];

    for (person, height, message, description) in data {
        assert_eq!(more_info(*person, *height), *message, "{}", description)
    }
}

#[test]
fn test_less_info() {
    let data = &[
        (
            "John",
            1.46,
            "My name is John, and I'm 1.5 meters tall",
            "no change",
        ),
        (
            "Jane",
            1.64,
            "My name is Jane, and I'm 1.6 meters tall",
            "round up and truncate to one decimal",
        ),
    ];

    for (person, height, message, description) in data {
        assert_eq!(less_info(*person, *height), *message, "{}", description)
    }
}
Titus answered 20/10, 2022 at 23:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.