-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Description
In certain situations the DateAxisItem runs into an infinite loop because of floating point errors.
To reproduce this,
- Run the "DateAxisItem" example,
- zoom in to around 1970-00-00 00:01:04.100 and a zoom level, such that you get tick labels in the format "ss.fff" (s=seconds, f=fraction).
- As soon as the tick label "04.100" is going to be calculated, the application freezes.
Reason
The program gets stuck in the while loop in TickSpec.makeTicks (DateAxisItem.py line 104). This happens, because the function step in line 106 returns the same value as it gets.
In the specific situation, described above, step is the function stepper returned by makeMSStepper (line 32).
stepper is called with val=64.1, which is a float in seconds. In line 37, val *= 1000 turns this into a milliseconds value, but due to floating point errors, the actual result is something like 64099.99999999999. This possibility isn't considered in line 39, where // does an floor operation and we get a value which is one integer too small.
Possible solutions
I first thought some rounding within stepper should solve the problem, but this seems not possible, because we need to do the floor operation at least in the first call of the function.
To understand this, one needs to take a close look:
Clearly, the combination of while loop and stepper function builds up all the tick values which shall be shown, for a specific level.
When stepper is called the first time (x = self.step(minVal, n), line 104), minVal is start of the current visible axis range (float in seconds as unix timestamp, i.e. relative to 1970-00-...). Here, we need to do some kind of rounding, to get the first tick value. The rounding is currently achived by the // operation. But any rounding can go into the wrong direction, due to floating point errors.
Important is, that for the subsequent calls of stepper (x = self.step(x, n), line 107), x is always a previous tick value and therefore it shall simply add the current tick-step. (Which can be not so simple in case of months or years.)
So the best idea, I can currently come up with, would be to separate the first and the subsequent calls of stepper and to simplify the code for the subsequent calls, to be able to ensure, that the returned value is always greater than the input value.
This could be something like this here bbc131@4fff10f
I would appreciate some feedback on this.
PS: A solution, which would get rid of the error-prone while loop would be nice. For the cases of the MS-stepper and S-stepper, we could use something like numpy.linspace. But the M-stepper and Y-stepper need a custom solution.