Skip to content

Fix relative_eq for small values#102

Open
kitt159 wants to merge 2 commits intobrendanzab:masterfrom
kitt159:relative_eq-small-values-fix
Open

Fix relative_eq for small values#102
kitt159 wants to merge 2 commits intobrendanzab:masterfrom
kitt159:relative_eq-small-values-fix

Conversation

@kitt159
Copy link

@kitt159 kitt159 commented Feb 21, 2026

Change is described here

Fixes #65

@jonaspleyer
Copy link
Collaborator

I am so sorry. I must have missed this PR completely.

I am a bit concerned about all the tests that were rewritten in this PR. What is the reason behind that?

@kitt159
Copy link
Author

kitt159 commented Mar 12, 2026

I am a bit concerned about all the tests that were rewritten in this PR. What is the reason behind that?

The behavior changed, which required some tests to be modified. I also find the current implementation of the tests cleaner - there is less duplicate code. Some tests were written in such a way that they didn't actually test anything, for example because they tested the equality of identical numbers (implementation using == would also pass the test).

Comment on lines -107 to -115
assert_relative_eq!(0.0f32, 1e-40f32, epsilon = 1e-40f32);
assert_relative_eq!(1e-40f32, 0.0f32, epsilon = 1e-40f32);
assert_relative_eq!(0.0f32, -1e-40f32, epsilon = 1e-40f32);
assert_relative_eq!(-1e-40f32, 0.0f32, epsilon = 1e-40f32);
assert_relative_eq!(0.0f32, 1e-20f32, epsilon = 1e-20f32);
assert_relative_eq!(1e-20f32, 0.0f32, epsilon = 1e-20f32);
assert_relative_eq!(0.0f32, -1e-20f32, epsilon = 1e-20f32);
assert_relative_eq!(-1e-20f32, 0.0f32, epsilon = 1e-20f32);

assert_relative_ne!(1e-40f32, 0.0f32, epsilon = 1e-41f32);
assert_relative_ne!(0.0f32, 1e-40f32, epsilon = 1e-41f32);
assert_relative_ne!(-1e-40f32, 0.0f32, epsilon = 1e-41f32);
assert_relative_ne!(0.0f32, -1e-40f32, epsilon = 1e-41f32);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like a precision regression to me.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1e-40 is denormalized number, it is not safe to compare denormalized numbers using relative comparison. Therefore they are marked equal by absolute comparison.

}

fn compare_near_numbers(number: f32) {
let rel_eq_number = number * 1.0000001f32;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this multiplication has an internal error associated with it. Consider this little program:

fn main() {
    let a = 1E-20;
    let b = 1E-10;
    println!("{}", a * b);
    println!("{}", 1E-30);
}

Output

0.0000000000000000000000000000009999999999999999
0.000000000000000000000000000001

How do we ensure that this multiplicative error does not invalidate our test (either via false positive or false negative).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dont understand.

  1. How can you tell if the result is correct? We are talking about approximate comparisons, there is no strict border between correct and incorrect behavior.

  2. Multiplication is defined by IEEE754 to return always correctly rounded result, so with given numbers it is just different way how to "hardcode" a number. In this case the result seems to be not correct but that is because input numbers are rounded.

1e-20 is stored as 9.9999999999999994515327145421e-21
1e-10 is stored as 1.0000000000000000364321973155e-10
Their product is most accurately represented as 9.99999999999999908174112566984e-31, not by 1e-30 which is stored as 1.00000000000000008333642060759E-30.

Copy link
Collaborator

@jonaspleyer jonaspleyer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have left some comments which I would like to discuss.

@jonaspleyer
Copy link
Collaborator

I am a bit concerned about all the tests that were rewritten in this PR. What is the reason behind that?

The behavior changed, which required some tests to be modified. I also find the current implementation of the tests cleaner - there is less duplicate code. Some tests were written in such a way that they didn't actually test anything, for example because they tested the equality of identical numbers (implementation using == would also pass the test).

I think that there is nothing wrong with testing cases which would be true via ==. This is of course a sanity check which should be satisfied trivially but nothing wrong with having tests for that.

Your implementation of the tests is different since it involves a multiplication to calculate the numbers that should be compared.

@kitt159 kitt159 force-pushed the relative_eq-small-values-fix branch from 1f4d408 to bc35d0f Compare March 14, 2026 10:13
@kitt159 kitt159 force-pushed the relative_eq-small-values-fix branch from bc35d0f to 2b9646e Compare March 14, 2026 10:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

relative_eq() has incorrect behavior for small values

2 participants