diff options
| author | Paul Eggert | 2019-08-16 16:25:02 -0700 |
|---|---|---|
| committer | Paul Eggert | 2019-08-16 16:27:27 -0700 |
| commit | f9fd12a30b3d94eb48f7b309907d136d7b2682ac (patch) | |
| tree | d00aa93beec6eb1013127ee251342f39f135a994 /src | |
| parent | e82923c817159c751aa9c902093a46b9457e8499 (diff) | |
| download | emacs-f9fd12a30b3d94eb48f7b309907d136d7b2682ac.tar.gz 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')
| -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 | |||
| 661 | TIMEFORM_HI_LO_US, /* seconds plus microseconds (HI LO US) */ | 661 | TIMEFORM_HI_LO_US, /* seconds plus microseconds (HI LO US) */ |
| 662 | TIMEFORM_NIL, /* current time in nanoseconds */ | 662 | TIMEFORM_NIL, /* current time in nanoseconds */ |
| 663 | TIMEFORM_HI_LO_US_PS, /* seconds plus micro and picoseconds (HI LO US PS) */ | 663 | TIMEFORM_HI_LO_US_PS, /* seconds plus micro and picoseconds (HI LO US PS) */ |
| 664 | /* These two should be last; see timeform_sub_ps_p. */ | ||
| 664 | TIMEFORM_FLOAT, /* time as a float */ | 665 | TIMEFORM_FLOAT, /* time as a float */ |
| 665 | TIMEFORM_TICKS_HZ /* fractional time: HI is ticks, LO is ticks per second */ | 666 | TIMEFORM_TICKS_HZ /* fractional time: HI is ticks, LO is ticks per second */ |
| 666 | }; | 667 | }; |
| 667 | 668 | ||
| 669 | /* True if Lisp times of form FORM can express sub-picosecond timestamps. */ | ||
| 670 | static bool | ||
| 671 | timeform_sub_ps_p (enum timeform form) | ||
| 672 | { | ||
| 673 | return TIMEFORM_FLOAT <= form; | ||
| 674 | } | ||
| 675 | |||
| 668 | /* From the valid form FORM and the time components HIGH, LOW, USEC | 676 | /* From the valid form FORM and the time components HIGH, LOW, USEC |
| 669 | and PSEC, generate the corresponding time value. If LOW is | 677 | and PSEC, generate the corresponding time value. If LOW is |
| 670 | floating point, the other components should be zero and FORM should | 678 | 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) | |||
| 1016 | 1024 | ||
| 1017 | /* Given Lisp operands A and B, add their values, and return the | 1025 | /* Given Lisp operands A and B, add their values, and return the |
| 1018 | result as a Lisp timestamp that is in (TICKS . HZ) form if either A | 1026 | result as a Lisp timestamp that is in (TICKS . HZ) form if either A |
| 1019 | or B are in that form, (HI LO US PS) form otherwise. Subtract | 1027 | or B are in that form or are floats, (HI LO US PS) form otherwise. |
| 1020 | instead of adding if SUBTRACT. */ | 1028 | Subtract instead of adding if SUBTRACT. */ |
| 1021 | static Lisp_Object | 1029 | static Lisp_Object |
| 1022 | time_arith (Lisp_Object a, Lisp_Object b, bool subtract) | 1030 | time_arith (Lisp_Object a, Lisp_Object b, bool subtract) |
| 1023 | { | 1031 | { |
| @@ -1077,7 +1085,7 @@ time_arith (Lisp_Object a, Lisp_Object b, bool subtract) | |||
| 1077 | otherwise the (HI LO US PS) form for backward compatibility. */ | 1085 | otherwise the (HI LO US PS) form for backward compatibility. */ |
| 1078 | return (EQ (hz, make_fixnum (1)) | 1086 | return (EQ (hz, make_fixnum (1)) |
| 1079 | ? ticks | 1087 | ? ticks |
| 1080 | : aform == TIMEFORM_TICKS_HZ || bform == TIMEFORM_TICKS_HZ | 1088 | : timeform_sub_ps_p (aform) || timeform_sub_ps_p (bform) |
| 1081 | ? Fcons (ticks, hz) | 1089 | ? Fcons (ticks, hz) |
| 1082 | : ticks_hz_list4 (ticks, hz)); | 1090 | : ticks_hz_list4 (ticks, hz)); |
| 1083 | } | 1091 | } |