Sling Academy
Home/Rust/Comparing Floating-Point Values Safely in Rust

Comparing Floating-Point Values Safely in Rust

Last updated: January 03, 2025

In the Rust programming language, comparing floating-point numbers for equality or inequality directly can lead to unexpected results due to the nature of how floating points are handled by computers. This is because floating-point numbers can have rounding errors, which might cause two seemingly identical numbers to be considered unequal when compared directly. In this article, we will explore safe ways to compare floating-point values in Rust.

Understanding Floating-Point Comparison Challenges

Floating-point numbers are represented as a base and an exponent, which allows them to cover a broad range of values. However, because of this representation, precision loss occurs, which results in minimal errors that aren’t apparent when you print the values, but can cause issues in logical conditions.

Direct Comparison

Let’s start by looking at how direct comparison in Rust can lead to problems. You might have two seemingly identical floating-point values:

fn main() {
    let a = 0.1 + 0.2;
    let b = 0.3;

    if a == b {
        println!("a equals b");
    } else {
        println!("a does not equal b");
    }
}

Although you might expect the output to be "a equals b", it will actually print "a does not equal b". This happens because 0.1 + 0.2 doesn’t exactly match 0.3 due to floating-point arithmetic imprecision.

Using the Approximate Equality Approach

A better method of comparing floating-point numbers in Rust is to use an approximate equality check within a small threshold or delta.

fn approx_equal(a: f64, b: f64, epsilon: f64) -> bool {
    (a - b).abs() < epsilon
}

fn main() {
    let a = 0.1 + 0.2;
    let b = 0.3;
    let epsilon = 0.0000001;

    if approx_equal(a, b, epsilon) {
        println!("a is approximately equal to b");
    } else {
        println!("a is not approximately equal to b");
    }
}

In this example, we define a function approx_equal that accepts two floating-point numbers and an epsilon value. The function checks if the absolute difference between a and b is less than epsilon. Here, epsilon is a small value indicating the allowed tolerance.

Utilizing Libraries for Enhanced Comparisons

For more robust and less error-prone floating-point comparisons, you can utilize external crates such as float-cmp, which is specifically designed for these scenarios.

use float_cmp::approx_eq;

fn main() {
    let a = 0.1 + 0.2;
    let b = 0.3;

    if approx_eq!(f64, a, b, ulps = 2) {
        println!("a is approximately equal to b with float-cmp");
    } else {
        println!("a is not approximately equal to b with float-cmp");
    }
}

In this example, we use the approx_eq! macro from the float-cmp crate, which provides a more accurate comparison using units in the last place (ULPs).

Conclusion

Comparing floating-point values directly in Rust can often lead to incorrect results due to their underlying binary representation. By using methods such as the epsilon approach or utilizing dedicated libraries like float-cmp, you can safely compare floating-point numbers with confidence. Understanding and properly handling these nuances can help you avoid subtle bugs in your programs and improve the robustness of your floating-point related logic.

Next Article: Handling Underflow and Denormalized Floats in Rust

Previous Article: Performing Numerical Integration and Differentiation in Rust

Series: Math and Numbers in Rust

Rust

You May Also Like

  • E0557 in Rust: Feature Has Been Removed or Is Unavailable in the Stable Channel
  • Network Protocol Handling Concurrency in Rust with async/await
  • Using the anyhow and thiserror Crates for Better Rust Error Tests
  • Rust - Investigating partial moves when pattern matching on vector or HashMap elements
  • Rust - Handling nested or hierarchical HashMaps for complex data relationships
  • Rust - Combining multiple HashMaps by merging keys and values
  • Composing Functionality in Rust Through Multiple Trait Bounds
  • E0437 in Rust: Unexpected `#` in macro invocation or attribute
  • Integrating I/O and Networking in Rust’s Async Concurrency
  • E0178 in Rust: Conflicting implementations of the same trait for a type
  • Utilizing a Reactor Pattern in Rust for Event-Driven Architectures
  • Parallelizing CPU-Intensive Work with Rust’s rayon Crate
  • Managing WebSocket Connections in Rust for Real-Time Apps
  • Downloading Files in Rust via HTTP for CLI Tools
  • Mocking Network Calls in Rust Tests with the surf or reqwest Crates
  • Rust - Designing advanced concurrency abstractions using generic channels or locks
  • Managing code expansion in debug builds with heavy usage of generics in Rust
  • Implementing parse-from-string logic for generic numeric types in Rust
  • Rust.- Refining trait bounds at implementation time for more specialized behavior