Search code examples
floating-pointrustprecisionfloating-accuracy

How to deal with inexact floating point arithmetic results in Rust?


How to deal with floating point arithmetic in Rust?

For example:

fn main() {
    let vector = vec![1.01_f64, 1.02, 1.03, 1.01, 1.05];

    let difference: Vec<f64> = vector.windows(2).map(|slice| slice[0] - slice[1]).collect();
    println!("{:?}", difference);
}

Returns:

[-0.010000000000000009, -0.010000000000000009, 0.020000000000000018, -0.040000000000000036]

Expected output:

[-0.01, -0.01, 0.02, -0.04]

I understand the reason for this happening but have never had to address it.

Update:

This post came about because results in other languages such as Python appeared to be exact. The plan was to replicate Python's approach in Rust however upon further investigation, Python, numpy and pandas all deal with numbers in the same way. That is, the inaccuracies are still present however not always visible/shown. Rust on the other hand made these innacuracies obvious which was confusing at first.

Example:

l = [1.01, 1.02, 1.03, 1.01, 1.05]

for i in l:
    print('%.18f' % i)

Prints:

1.010000000000000009
1.020000000000000018
1.030000000000000027
1.010000000000000009
1.050000000000000044

whereas print(l) prints:

[1.01, 1.02, 1.03, 1.01, 1.05]

Solution

  • Since you know why this happens, I assume you want to format the output.

    As in the official docs:

    Precision

    For non-numeric types, this can be considered a "maximum width". If the resulting string is longer than this width, then it is truncated down to this many characters and that truncated value is emitted with proper fill, alignment and width if those parameters are set.

    For integral types, this is ignored.

    For floating-point types, this indicates how many digits after the decimal point should be printed.

    There are three possible ways to specify the desired precision:

    1. An integer .N:

      the integer N itself is the precision.

    2. An integer or name followed by dollar sign .N$:

      use format argument N (which must be a usize) as the precision.

    3. An asterisk .*:

      .* means that this {...} is associated with two format inputs rather than one: the first input holds the usize precision, and the second holds the value to print. Note that in this case, if one uses the format string {<arg>:<spec>.*}, then the <arg> part refers to the value to print, and the precision must come in the input preceding <arg>.

    So in your case, one of those does the job:

    println!("{0:.2?}", difference);
    println!("{1:.0$?}", 2, difference);
    println!("{:.*?}", 2, difference);
    println!("{1:.*?}", 2, difference);
    println!("{number:.prec$?}", prec = 2, number = difference);
    

    Playground

    However, if you want to continue with this precision, you may round the results:

    .map(|x| (x * 100.0).round() / 100.0)
    

    Playground


    See also: