Nearly equal with relative and absolute tolerance
Hi,
This initial commit is intended for exploration of #2838. It overwrites the current nearlyEqual completely to be as similar to python's Math.isclose as possible (according to the description in the documentation)
It currently fails 6 tests:
- 2 tests in
rotationMatrix - 1 test in
nearlyEqual(for bigNumber) - 3 tests in
number / nearlyEqual
No effort was made to either adapt the tests or fix the function. Everything is left as is to review the failing tests and aid in the decision making.
Pleas note that the functions are changed abruptly only for exploration and can be returned to their original form upon review.
Also no calls for nearlyEqual were modified, thus only relative tolerance is being used. If the default values are changed to relTol = Number.EPSILON, absTol = 1e-12 then a few more tests fail.
Thanks, this is a great start!
Some thoughts:
- Only 8 failing unit tests is not that much :). I don't see failing tests related to
rotationMatrixthough, I see 4 tests foreigsfailing? - I expect more tests to fail as soon as we update all places where
nearlyEqual(...)is invoked to pass bothrelTolandabsTol. - I think we may need to add some additional unit tests to test
relTolandabsTolindividually. - We should decide upon good default values
relTolandabsTol, and how we map values for both when a user configuresepsilon(for backward compatibility). Would it make sense to default torelTol = epsilon ?? 1e-12andabsTol = epsilon ?? 1e-12? - In general I like to use full words rather than abbreviations, like
relativeToleranceandabsoluteTolerance. However in this case the words are quite long. Still thinking about that. - I see the tests where run on node.js 16, I don't understand why, it should only run node 18 and 20 (see config)? Is something in this branch not up to date?
- That's odd, I don't see
eigsfailing, maybe this is not up to date. - I agree
- I agree
- I think the current behavior is similar to having an
absTolabout 1000 times smaller thanrelTol. I see on the julia docs for isaprox some ways to calculate those default values. - I agree these names don't match mathjs very well just took the python names and got a warning related to the camel case. I'm sure there are more appropriate names. Regarding abbreviations it could be argued that
configis an abbreviation. - At some point I saw that line trying to update in my PR but I returned it to what it was. Probably it was my mistake.
A quick comparison for names and values.
| language | function | relative tolerance | absolute tolerance |
|---|---|---|---|
| python | isclose | rel_tol = 1e-09 | abs_tol = 0.0 |
| numpy | isclose | rtol = 1e-05 | atol = 1e-08 |
| julia | isapprox | rtol = atol>0 ? 0 : √eps | atol = 0 |
(1) You can see the test results by clicking them in the list with checks in this PR, for example https://github.com/josdejong/mathjs/actions/runs/7853061106/job/21432241083?pr=3152. I guess indeed that you somehow have an old version or so (that may also explain why the PR is still testing on Node 16).
(5) ha ha, that's true. I'm ok with either absTol / relTol or absoluteTolerance / relativeTolerance, I don't have a very strong opinion in this regard (in general, I like the non abbreviated name, but in this case it is quite long).
(1) I was at v 11.9.1 (sorry about that). Should be updated to v12.4.0 by now. (5) Ok
Now that it's updated, I see the following errors:
-
roundas predicted at #3136 -
eigs -
round
What would be the next steps?
I think the next step is to work out this PR in detail: update all places where nearlyEqual(...) is invoked, and then fix all failing unit tests.
Ok, thanks! I will do that and report back.
Hi, just reporting back.
Progress has been slower than what I originally expected but I'm still on it.
Thanks for the update David, good to know.
Hi Jos
I have some good news!
I found a good fit with a relative tolerance the size of the current config.epsilon 1e-12 and an absolute tolerance of 1e-15. I did some empirical tests and works similarly in most cases.
In the latest submit I made all calls for nearlyEqual to use relTol = config.epsilon and absTol = config.epsilon * 1e-3 and this eliminates all the failing tests except for the nearlyEqual tests.
I would like your advise with the following:
- Shall I remove
config.epsilonin favor ofconfig.relTolandconfig.absTol? (naming to be defined) - Shall I change the failing
nearlyEqualtests to accommodate for the proposednearlyEqualor make a compromise between the proposednearlyEqualand the current tests? - Shall I make a mathjs function
nearlyEqualavailable so that the user can chooseabsTolandrelTolwithout changing config or shall it be a different topic outside this PR?
Appendix
Here is the mathjs code I used to test for the tolerances: (this works on the current version 12.4.1
config({epsilon:1e-12});
# Define function nearly equal
nearlyEqual(a, b, relTol, absTol) = abs(a-b) <= max(relTol * max(abs(a), abs(b)), absTol);
# Define rtol and atol
rtol = config().epsilon;
atol = rtol * 1e-3;
# Tested various values of x
X = [0, 0.1, 0.5, 1, 10, 1e3, 1e6, 1e12];
x = X[1];
# Ys
powers = -2:-1:-16;
Y = x + map(powers, _(d) = pow(10, d));
# Proposed
nearlyEqualResults = map(Y, _(y) = nearlyEqual(x, y, rtol, atol));
# Current
equalResults = x == Y;
# These are the powers where the current nearly equals differ from the proposed one
differences = nearlyEqualResults != equalResults;
result = {
powersFailing:powers[differences],
x:x,
current:equalResults[differences],
proposed:nearlyEqualResults[differences],
rtol:rtol,
atol:atol
};
print("Testing with:\n
rtol: $rtol
atol: $atol
x: $x\n
yields:
powers faling: $powersFailing
proposal: $proposed
current: $current
", result)
I read the following document to realize that nearlyEqual might need to be one of the mathjs functions.
Yay, that sounds promising!
- Yes, though we do need backward compatibility in the function
config(). When people are still using.epsilon, we can translate it internally into the new optionsrelTolandabstoland output a console warning about the deprecation. - I think we have to adjust the failing
nearlyEqualtests. This will be a breaking change, and I suppose the failing tests are due to the behavior now being different, so the tests should be adapted accordingly. (please correct me if I'm wrong) - I'm not sure if we should expose the
nearlyEqualfunction, it may make sense 🤔. I prefer to discuss that in a separate topic to keep this PR focused, OK?
Looking at the code example, you could definitely use #2953 🤣
Thanks!
- OK: sounds good, I was worried it was too much change.
-
OK: most are due to the new formula. There is one that might be debatable that says
nearlyEqual: should do exact comparison when epsilon is null or undefined. In my opinion it shouldresort to default values of relTol and absTol when they are null or undefined - OK: Will make a separate discussion.
Absolutely! I was trying to change my point of view about the shorthand and I did.
PD: Also could have used #2701 to cycle through the values of X
👍
I see you're pushing new commits. Please let me know when the PR is ready for review (or if you hit new challenges).
Hi Jos,
Thanks for the follow up.
I'm still tweaking some stuff and will let you know when it's ready for review.
Hi Jos, this is ready for review
Thanks, that is good news! I'll look into it asap.
Hi Jos, thanks for the thorough review!
I'm also glad there where not so many errors. The reason might be that the values for the new tolerances were picked to be close to the existing behavior. Maybe at some point in the future the values might change and there will be more discrepancies but at least there is the option to adjust as needed case by case.
I will work on the inline comments and report back probably during the weekend.
Hi Jos, it's ready for review again. I included some inline comments with a few questions.
Hi Jos, the two left comments were addressed.
Please review.
Thanks David, I'm really happy with how this PR worked out, it's much cleaner than I had expected.
Merging this PR now in the v13 branch. I expect to publish v13 somewhere the coming weeks with the breaking changes implemented so far.
Thanks Jos!
Looking forward to v13!
Published now in v13.0.0 🎉