diff options
author | Paul Eggert <eggert@cs.ucla.edu> | 2019-08-16 16:25:02 -0700 |
---|---|---|
committer | Paul Eggert <eggert@cs.ucla.edu> | 2019-08-16 16:27:27 -0700 |
commit | f9fd12a30b3d94eb48f7b309907d136d7b2682ac (patch) | |
tree | d00aa93beec6eb1013127ee251342f39f135a994 /src/timefns.c | |
parent | e82923c817159c751aa9c902093a46b9457e8499 (diff) | |
download | emacs-f9fd12a30b3d94eb48f7b309907d136d7b2682ac.tar.gz emacs-f9fd12a30b3d94eb48f7b309907d136d7b2682ac.tar.bz2 emacs-f9fd12a30b3d94eb48f7b309907d136d7b2682ac.zip |
Fix time-add rounding bug
Without this fix, time arithmetic yielded results that were not
mathematically accurate, even though the exact results were
representable; for example, (time-add 0 1e-13) yielded a timestamp
equal to 0 instead of to 1e-13.
* lisp/timezone.el (timezone-time-from-absolute):
Let time-add do its thing rather than using floating point
internally, which has rounding errors. We now have bignums and so
don’t need floating point to avoid overflow issues.
* src/timefns.c (timeform_sub_ps_p): New function.
(time_arith): If either argument is a float, represent the
result exactly instead of discarding sub-ps info.
* test/lisp/timezone-tests.el (timezone-tests-time-from-absolute):
Don’t assume (HI LO US PS) timestamp format.
* test/src/emacs-module-tests.el (mod-test-add-nanosecond/valid):
Don’t assume that time-add discards sub-ns info.
* test/src/timefns-tests.el (time-rounding-tests):
Add regression test to detect time-add rounding bug.
Diffstat (limited to 'src/timefns.c')
-rw-r--r-- | src/timefns.c | 14 |
1 files changed, 11 insertions, 3 deletions
diff --git a/src/timefns.c b/src/timefns.c index e9d1a9bf64b..a4c1c4cb284 100644 --- a/src/timefns.c +++ b/src/timefns.c @@ -661,10 +661,18 @@ enum timeform TIMEFORM_HI_LO_US, /* seconds plus microseconds (HI LO US) */ TIMEFORM_NIL, /* current time in nanoseconds */ TIMEFORM_HI_LO_US_PS, /* seconds plus micro and picoseconds (HI LO US PS) */ + /* These two should be last; see timeform_sub_ps_p. */ TIMEFORM_FLOAT, /* time as a float */ TIMEFORM_TICKS_HZ /* fractional time: HI is ticks, LO is ticks per second */ }; +/* True if Lisp times of form FORM can express sub-picosecond timestamps. */ +static bool +timeform_sub_ps_p (enum timeform form) +{ + return TIMEFORM_FLOAT <= form; +} + /* From the valid form FORM and the time components HIGH, LOW, USEC and PSEC, generate the corresponding time value. If LOW is floating point, the other components should be zero and FORM should @@ -1016,8 +1024,8 @@ lispint_arith (Lisp_Object a, Lisp_Object b, bool subtract) /* Given Lisp operands A and B, add their values, and return the result as a Lisp timestamp that is in (TICKS . HZ) form if either A - or B are in that form, (HI LO US PS) form otherwise. Subtract - instead of adding if SUBTRACT. */ + or B are in that form or are floats, (HI LO US PS) form otherwise. + Subtract instead of adding if SUBTRACT. */ static Lisp_Object time_arith (Lisp_Object a, Lisp_Object b, bool subtract) { @@ -1077,7 +1085,7 @@ time_arith (Lisp_Object a, Lisp_Object b, bool subtract) otherwise the (HI LO US PS) form for backward compatibility. */ return (EQ (hz, make_fixnum (1)) ? ticks - : aform == TIMEFORM_TICKS_HZ || bform == TIMEFORM_TICKS_HZ + : timeform_sub_ps_p (aform) || timeform_sub_ps_p (bform) ? Fcons (ticks, hz) : ticks_hz_list4 (ticks, hz)); } |