How to deal with inexact floating point arithmetic results in Rust? [closed]
Asked Answered
G

1

3

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]
Gosse answered 16/5, 2018 at 1:16 Comment(7)
I believe you can round the numbers to the nearest 1/10,000 and you should be fine.Thickness
There are literal books written on the topic of "handling" floating point math. This question is far too broad to answer in a useful manner, especially as you don't even know what direction you want to go in to solve it. Check out What Every Programmer Should Know About Floating-Point Arithmetic as well.Gan
Either don’t use floating point (since you know why this is happening I won’t go in to that), or round the answer to the precision you need. That’s usually enough.Toleration
@Toleration Is there a way to identify precision of inputs at runtime? This post looks like a good solution and I just need to understand what power to raise the 10 to at runtime: #28655862Gosse
Print your output to 15 significant digits or less. -0.010000000000000009 is 17 significant digits.Accordance
Possible duplicate of Is floating point math broken?Dysphemia
Using powers of 10 is the only way to be sure there are no errors in the math. Even rounding the answer is a risk. Watch that you’re not running to the limits of what an integer can hold though. I’m sure it’s documented somewhere what the error margin is in floating points, but with the right (wrong) calculations it could add up. You decide the precision you need...Toleration
M
3

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:

Moneychanger answered 16/5, 2018 at 6:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.