Skip to content

Fix relative_eq for small values#102

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

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

Conversation

@kitt159
Copy link
Copy Markdown

@kitt159 kitt159 commented Feb 21, 2026

Change is described here

Fixes #65

@jonaspleyer
Copy link
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
@kitt159
Copy link
Copy Markdown
Author

kitt159 commented Mar 29, 2026

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.

I prefer to have nonredundant tests but I can keep most of the tests unchanged if you want.

@jonaspleyer
Copy link
Copy Markdown
Collaborator

I have taken another longer look at your fix and I think that I will accept it into the codebase. However I will ask you to revert the changes done to the tests. I simply feel uncomfortable to change the test suite without any need.

If you want, you can include issue #102 as a standalone test.

Once the changes are included, I will create a new release candidate and notify some folds within the ecosystem to test the new version (most notable ndarray). As long as they do not find anything that interferes with their current implementations, I will wait a bit longer and ultimately release the new version.

If you do not find the time to make the changes from above, simply let me know. I will then do them myself. Not a big deal.

And thanks again for your continued effort in this!

@jonaspleyer
Copy link
Copy Markdown
Collaborator

Can you confirm that you agree to license the contributions of this PR under the MIT license? We have an ongoing thread which is nearly resolved to add the MIT license to this crate to enable better compatibility with the Rust ecosystem:
#94

@jonaspleyer
Copy link
Copy Markdown
Collaborator

Another question that has bugged me is the following: your solution requires the additional implementation of the default_relative_eq function in the RelativeEq trait. I have two conflicting questions in mind about this:

  1. Can we circumvent this to enable better compatibility?
  2. Is it better to introduce this separate function so that the breaking API is made clear and in order to avoid users running into problems unknowingly.

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