Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does Rust only use 16 significant digits for f64 equality checks?

I have the following Rust code:

use std::f64::consts as f64;

fn main() {
    println!("Checking f64 PI...");
    // f64::PI definition: https://github.com/rust-lang/rust/blob/e1fc9ff4a794fb069d670dded1a66f05c86f3555/library/core/src/num/f64.rs#L240
    println!("Definition: pub const PI: f64 = 3.14159265358979323846264338327950288_f64;");
    println!("Print it:                       {:.35}", f64::PI);
    println!("Different after 16 significant digits ----------|                         ");
    println!("##############################################################################");
    println!("Question 1: Why do the digits differ after 16 significant digits when printed?");
    println!("##############################################################################");

    println!("PERFORM ASSERTIONS..."); 
    assert_eq!(f64::PI, 3.14159265358979323846264338327950288_f64); // 36 significant digits definition
    assert_eq!(f64::PI, 3.141592653589793_f64); // 16 significant digits (less then the 36 in definition)
    // compares up to here -------------|
    assert_eq!(f64::PI, 3.14159265358979300000000000000000000_f64); // 36 significant digits (16 used in equality comparison)
    assert_ne!(f64::PI, 3.14159265358979_f64); // 15 significant digits (not equal)

    println!("PERFORM EQUALITY CHECK..."); 
    if 3.14159265358979323846264338327950288_f64 == 3.14159265358979300000000000000000000_f64 {
        println!("BAD: floats considered equal even when they differ past 16 significant digits");
        println!("######################################################################");
        println!("Question 2: Why does equality checking use only 16 significant digits?");
        println!("They are defined using 36 significant digits so why can't we perform");
        println!("an equality check with this accuracy?");
        println!("######################################################################");
    } else {
        println!("GOOD: floats considered different when they differ past 16 significant digits");
        println!("NOTE: This block won't execute :(");
    }
}

I understand floating point arithmetic can be tricky but wanting to know if the trickiness also affects printing and performing equality checks on f64's. Here is the output from the above code:

Checking f64 PI...
Definition: pub const PI: f64 = 3.14159265358979323846264338327950288_f64;
Print it:                       3.14159265358979311599796346854418516
Different after 16 significant digits ----------|                         
##############################################################################
Question 1: Why do the digits differ after 16 significant digits when printed?
##############################################################################
PERFORM ASSERTIONS...
PERFORM EQUALITY CHECK...
BAD: floats considered equal even when they differ past 16 significant digits
######################################################################
Question 2: Why does equality checking use only 16 significant digits?
They are defined using 36 significant digits so why can't we perform
an equality check with this accuracy?
######################################################################
like image 495
JeffreyGoines Avatar asked Oct 17 '25 13:10

JeffreyGoines


1 Answers

An f64, as the name suggests, is stored in 64 bits. In this fixed amount of storage we can only encode a fixed amounts of digits (specifically, 52 of those bits are dedicated to the significand). If you use more digits in your floating-point literal the number stored in your f64 variable will be rounded to the nearest number that is representable in the amount of bits available. For f64 this means we can always exactly represent 15 decimal digits, sometimes 16. This explains why sometimes numbers appear equal even though you used different floating-point literals in your source code: it's because after rounding to the nearest representable number, they are the same.

The reason for different digits being printed is the same. The number is rounded to the nearest representable number when stored, and converted back to decimal again when printed. The additional digits originate from the binary-to-decimal conversion, but the digits after 15 or 16 decimal places are mostly meaningless – they don't carry any additional information about the number being represented.

Note that none of this is specific to Rust. Most modern programming languages use the IEEE 754-1985 standard to represent floating-point numbers, so they will behave identically. If you want arbitrary-precision arithmetic, you generally need to use some library, e.g. the rug crate.

like image 157
Sven Marnach Avatar answered Oct 22 '25 04:10

Sven Marnach



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!