-
Notifications
You must be signed in to change notification settings - Fork 104
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
High-resolution timer gets tripped up by floating point arithmetic #207
Comments
An alternative would be to have I can look into this when I get back from holiday at the beginning of October |
|
@fatso83 is it acceptable to add a dependency here to handle the maths of it? floating point arithmetics sucks :( EDIT: I do doubt it (seeing as lolex is dep free now), but is just a diff --git c/src/lolex-src.js w/src/lolex-src.js
index 14f55ca..4300371 100644
--- c/src/lolex-src.js
+++ w/src/lolex-src.js
@@ -101,7 +101,7 @@ function withGlobal(_global) {
* % operator that also works for negative numbers
*/
function fixedModulo(n, m) {
- return ((n % m) + m) % m;
+ return Math.round(((n % m) + m) % m);
}
/**
diff --git c/test/lolex-test.js w/test/lolex-test.js
index 6684870..9fe56b2 100644
--- c/test/lolex-test.js
+++ w/test/lolex-test.js
@@ -2361,6 +2361,14 @@ describe("lolex", function () {
assert.same(result[0], 1);
assert.same(result[1], 0);
});
+
+ it("should handle floating point", function () {
+ var clock = lolex.createClock();
+ clock.tick(1022.7791);
+ var result = clock.hrtime([0, 20000000]);
+
+ assert.equals(result, [1, 2779100]);
+ });
});
}
if (nextTickPresent) { |
I'd say lets go with the simplest thing that seems to work and if people find edge cases we haven't thought of, we will fix it in time. |
This is still a problem. From #210:
👋 Starting from Try in your REPL: ❯ node -pe "742 + 123.493" 8.12.0
865.4929999999999 A different approach may be to do the computations in nanoseconds directly. I think that should be fine, integer wise, unless you start doing ticks larger than a 100 days worth of nanoseconds. |
$ node -p '(742 * 1e9 + 123.493 * 1e9) / 1e9'
865.493 maybe?
We can check the size before multiplying. And either do |
@novemberborn I am not sure I see the problem wrt lolex. We round off the numbers, meaning it should round up in your case. Can you demonstrate that the problem mentioned in this issue at the top isn't solved? Your demo only shows how floating points are representing. Since we don't truncate that value, rather rounding it, how does the problem manifest? |
My example came from using lolex:
Note the value is |
That's not a verification step. I made a runnable example using the 2.7.5 release and it shows the correct result: So, no, I am not convinced we have a bug 😸 Can you make sure you are running 2.7.5, not 2.7.4? I don't like to have bugs in our code, so of course, prove me wrong if I am! |
Closing, but please reopen if you can demonstrate a bug using the latest release. |
Here you go: https://runkit.com/novemberborn/5ba363ba0f73fb001265cb0f The difference should be |
Verified. The good thing is that we know have verifiable test cases 😁 One thing I noticed is that I see two paths forwards:
The first version should "obviously" be correct and most round off cases will fall away by removing all floating point at The second approach is what we currently do and require fewer changes, but it's hard to get exact when dealing with inexact numbers. There's some minor issues relating to keeping the high resolution and "normal" time in sync. Personally I like the first best, and think the changes can be done in less than an hour. What do you think? |
You'd have to separate the seconds from the nanoseconds in your internal representation though, right? |
@novemberborn I was thinking why, but then after doing some math I realized you are right. I am not sure if this is what you meant, but it has to do with the maximum safe integer that can be represented. The MAX_SAFE_INTEGER constant has a value of 9007199254740991. This is 9e15. The current value for the number of milliseconds is about 1.5e12. If we should store that as nanos, then that would mean a number around 1.5e18 (as it is), which is a thousand times higher than the max safe integer representable using Javascript. So yeah, that would mean storing seconds and nanos separately, which is not the elegant solution I thought of. Argh. |
It's probably OK to keep the storage in milliseconds, and then have separate storage for the remainder down to the nanosecond. If ticks are presented as integers, then you don't need to touch the remainder. If it's a float then you adjust the remainder, and if it ticks over (positively or negatively) adjust the milliseconds. The remainder should be truncated to always be an integer. E.g. if I tick by |
@novemberborn So, in general, you propose we go ahead with (a modified of) the first approach? Store integers internally (even if it means a tad more logic for two numbers). |
@fatso83 yes, but you only need to use the second value if the tick amount is fractional. If we're ticking by seconds or milliseconds then it doesn't impact the nanosecond values at all. |
I think this would be a valuable upgrade to Reading the discussion is interesting, but it's easy to overlook things. @novemberborn @fatso83 could you write up a little proposal (perhaps just in the original entry above) and specify exactly what changes need to be made, how we verify that the work is correct and complete? |
I think we want this:
|
Awesome, now we just need someone to do the work :) |
Argh, I started this in a fork of mine (see above ref), but I have used three hours trying to make my initial test cases pass. I tried several variations after finding there are so many corner cases when we support negative ticks and negative clock values! The essence of the trouble is this: I am not totally sure what the "right" integer value for millisecond is at times, as it depends on whether we are using Cases that needs to be agreed uponThese all revolve around the integer value of now=0.1, tick=-1Pre: Date.now() == 0? now=1.1, tick=-1Pre: Date.now() == 1 now=-0.3, tick=0.4Pre: Date.now() == 0 or -1? now=-1.3, tick=0.4Pre: Date.now() == -1? I think the best solution is simply going for |
In a conversation with Morgan we agreed on
That made an implementation much simpler and easier to reason about, but I hit a snag with regards to handling the one test that deals with
|
Perhaps we should spend some time refactoring the affected parts of the code, write better tests for the existing code ... all to make it easier to work with? |
I'm absolutely fine with even throwing on negative ticks, I had no idea it was supported to begin with. |
I managed to fix the remaining test in a simple manner, so I'll submit a PR shortly. I'll add a test for throwing on negative ticks as well. |
hrNow was an internal detail that was exposed for no apparent reason. Its content is available through clock.hrtime() and/or clock.performance.now().
@novemberborn I think #214 should itch your scratch. Would you mind checking it out and seeing if it solves your issues? See the "Verifying" section for how to test this version locally. |
Add negative test case refs sinonjs#207
hrNow was an internal detail that was exposed for no apparent reason. Its content is available through clock.hrtime() and/or clock.performance.now().
What did you expect to happen?
Advancing the clock with fractional milliseconds should give me an integer nanosecond value when I call
process.hrtime(prev)
.What actually happens
The nanosecond value is a float, not an integer.
How to reproduce
The reproduction advanced the clock by a fraction of a nanosecond. I suppose it'd be fine to round the computed nanosecond value, but I'm not sure if it should be rounded up or down. Alternatively something like https://www.npmjs.com/package/decimal.js may help to avoid the floating point arithmetic. Perhaps
tick()
should round down to the closest nanosecond, but I'm not sure if that'll help or not.The text was updated successfully, but these errors were encountered: