How to compare floating-point values safely in Rust

2 Answers

0 votes
/*
====================================================================
 Title: Safe Floating-Point Comparison in Rust

 Purpose:
     Demonstrates the recommended way to compare floating-point
     values safely.

 Why not use == ?
     Floating-point values (f32 and f64) are stored as binary
     approximations. Many decimal values cannot be represented
     exactly, resulting in small rounding errors.

 Example:
     0.1 + 0.2 != 0.3

     Although mathematically equal, the stored values differ
     slightly due to floating-point precision limitations.
====================================================================
*/

// Recommended tolerance values.
// Adjust them according to your application's requirements.
const ABS_TOLERANCE: f64 = 1e-12;
const REL_TOLERANCE: f64 = 1e-9;

/*
 * Function: approximately_equal
 *
 * Compares two floating-point values using:
 *   1. Absolute tolerance
 *   2. Relative tolerance
 *
 * Why both?
 *   - Absolute tolerance works well near zero.
 *   - Relative tolerance works well for large values.
 *
 * Returns:
 *   true  -> Values are considered equal.
 *   false -> Values are different.
 */
fn approximately_equal(a: f64, b: f64) -> bool {
    // Calculate the absolute difference.
    let diff: f64 = (a - b).abs();

    /*
     * Compare against the larger of:
     *   1. The absolute tolerance
     *   2. A relative tolerance scaled by magnitude
     *
     * This makes the comparison robust for both
     * very small and very large numbers.
     */
    diff <= f64::max(
        ABS_TOLERANCE,
        f64::max(a.abs(), b.abs()) * REL_TOLERANCE,
    )
}

fn main() {
    /*
     * Classic floating-point precision example.
     */
    let x: f64 = 0.1 + 0.2;
    let y: f64 = 0.3;

    println!("================================================");
    println!(" Safe Floating-Point Comparison in Rust");
    println!("================================================");
    println!();

    /*
     * Display additional precision so the tiny
     * difference becomes visible.
     */
    println!("x = {:.17}", x);
    println!("y = {:.17}", y);
    println!();

    /*
     * BAD PRACTICE:
     * Direct comparison using ==.
     *
     * This may fail because of tiny rounding errors.
     */
    if x == y {
        println!("Direct comparison (==): Equal");
    } else {
        println!("Direct comparison (==): Not Equal");
    }

    println!();

    /*
     * GOOD PRACTICE:
     * Compare using approximately_equal().
     */
    if approximately_equal(x, y) {
        println!("approximately_equal(): Equal");
    } else {
        println!("approximately_equal(): Not Equal");
    }

    println!();

    /*
     * Additional examples.
     */
    let large_a: f64 = 1_000_000.000_000_001;
    let large_b: f64 = 1_000_000.000_000_002;

    let near_zero_a: f64 = 1e-12;
    let near_zero_b: f64 = 0.0;

    println!("Additional Tests");
    println!("----------------");

    println!(
        "Large values equal: {}",
        approximately_equal(large_a, large_b)
    );

    println!(
        "Near-zero values equal: {}",
        approximately_equal(near_zero_a, near_zero_b)
    );

    println!(
        "1.5 and 2.0 equal: {}",
        approximately_equal(1.5, 2.0)
    );
}


    
/*
run:

================================================
 Safe Floating-Point Comparison in Rust
================================================

x = 0.30000000000000004
y = 0.29999999999999999

Direct comparison (==): Not Equal

approximately_equal(): Equal

Additional Tests
----------------
Large values equal: true
Near-zero values equal: true
1.5 and 2.0 equal: false

*/

 



answered Oct 8 by avibootz
0 votes
fn approximately_equal(
    a: f64,
    b: f64,
    epsilon: f64,
) -> bool {
    (a - b).abs() <= epsilon
}

fn main() {
    /*
     * Classic floating-point precision example.
     */
    let x: f64 = 0.1 + 0.2;
    let y: f64 = 0.3;

    println!("================================================");
    println!(" Safe Floating-Point Comparison in Rust");
    println!("================================================");
    println!();

    /*
     * Display additional precision so the tiny
     * difference becomes visible.
     */
    println!("x = {:.17}", x);
    println!("y = {:.17}", y);
    println!();

    /*
     * BAD PRACTICE:
     * Direct comparison using ==.
     *
     * This may fail because of tiny rounding errors.
     */
    if x == y {
        println!("Direct comparison (==): Equal");
    } else {
        println!("Direct comparison (==): Not Equal");
    }

    println!();

    /*
     * GOOD PRACTICE:
     * Compare using approximately_equal().
     */
    if approximately_equal(x, y, 1e-9) {
        println!("approximately_equal(): Equal");
    } else {
        println!("approximately_equal(): Not Equal");
    }

    println!();

    /*
     * Additional examples.
     */
    let large_a: f64 = 1_000_000.000_000_001;
    let large_b: f64 = 1_000_000.000_000_002;

    let near_zero_a: f64 = 1e-12;
    let near_zero_b: f64 = 0.0;

    println!("Additional Tests");
    println!("----------------");

    println!(
        "Large values equal: {}",
        approximately_equal(large_a, large_b, 1e-9)
    );

    println!(
        "Near-zero values equal: {}",
        approximately_equal(near_zero_a, near_zero_b, 1e-9)
    );

    println!(
        "1.5 and 2.0 equal: {}",
        approximately_equal(1.5, 2.0, 1e-9)
    );
}


    
/*
run:

================================================
 Safe Floating-Point Comparison in Rust
================================================

x = 0.30000000000000004
y = 0.29999999999999999

Direct comparison (==): Not Equal

approximately_equal(): Equal

Additional Tests
----------------
Large values equal: true
Near-zero values equal: true
1.5 and 2.0 equal: false

*/

 



answered Oct 8 by avibootz
...