aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPaul Eggert2019-08-20 17:34:03 -0700
committerPaul Eggert2019-08-20 17:36:46 -0700
commit396ed88a50fba95cd3b989965defef0130a42c42 (patch)
tree95b03c537acf8d65b6d894a283eccf9f1d31a1e8
parent7e2090ee80c9099ee953392444e1d73d10e973d4 (diff)
downloademacs-396ed88a50fba95cd3b989965defef0130a42c42.tar.gz
emacs-396ed88a50fba95cd3b989965defef0130a42c42.zip
Avoid some excess precision in time arithmetic
* doc/misc/emacs-mime.texi (time-date): Adjust example to match new behavior. * etc/NEWS: Mention this. * lisp/calendar/time-date.el (decoded-time-add) (decoded-time--alter-second): Don’t lose underestimate precision of seconds component. * src/bignum.c (mpz): Grow by 1. * src/timefns.c (trillion_factor): New function. (timeform_sub_ps_p): Remove. (time_arith): Avoid unnecessarily-large hz, by reducing the hz to a value no worse than the worse hz of the two arguments. The result is always exact unless an error is signaled. * test/src/timefns-tests.el (timefns-tests--decode-time): New function. (format-time-string-with-zone): Test (decode-time LOOK ZONE t) resolution as well as its numeric value.
-rw-r--r--doc/misc/emacs-mime.texi2
-rw-r--r--etc/NEWS4
-rw-r--r--lisp/calendar/time-date.el32
-rw-r--r--src/bignum.c5
-rw-r--r--src/bignum.h2
-rw-r--r--src/timefns.c106
-rw-r--r--test/src/timefns-tests.el44
7 files changed, 135 insertions, 60 deletions
diff --git a/doc/misc/emacs-mime.texi b/doc/misc/emacs-mime.texi
index eb829b06124..131a358ba59 100644
--- a/doc/misc/emacs-mime.texi
+++ b/doc/misc/emacs-mime.texi
@@ -1568,7 +1568,7 @@ Here's a bunch of time/date/second/day examples:
1568 1568
1569(time-subtract '(905595714000000 . 1000000) 1569(time-subtract '(905595714000000 . 1000000)
1570 '(905595593000000000 . 1000000000)) 1570 '(905595593000000000 . 1000000000))
1571@result{} (121000000000 . 1000000000) 1571@result{} (121000000 . 1000000)
1572 1572
1573(days-between "Sat Sep 12 12:21:54 1998 +0200" 1573(days-between "Sat Sep 12 12:21:54 1998 +0200"
1574 "Sat Sep 07 12:21:54 1998 +0200") 1574 "Sat Sep 07 12:21:54 1998 +0200")
diff --git a/etc/NEWS b/etc/NEWS
index 9f25cf4af51..3fdc185af4f 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -2166,7 +2166,9 @@ end and duration).
2166+++ 2166+++
2167*** 'time-add', 'time-subtract', and 'time-less-p' now accept 2167*** 'time-add', 'time-subtract', and 'time-less-p' now accept
2168infinities and NaNs too, and propagate them or return nil like 2168infinities and NaNs too, and propagate them or return nil like
2169floating-point operators do. 2169floating-point operators do. If both arguments are finite, these
2170functions now return exact results instead of rounding in some cases,
2171and they also avoid excess precision when that is easy.
2170 2172
2171+++ 2173+++
2172*** New function 'time-equal-p' compares time values for equality. 2174*** New function 'time-equal-p' compares time values for equality.
diff --git a/lisp/calendar/time-date.el b/lisp/calendar/time-date.el
index f3d252f03c6..11bd469ae3b 100644
--- a/lisp/calendar/time-date.el
+++ b/lisp/calendar/time-date.el
@@ -421,10 +421,13 @@ changes in daylight saving time are not taken into account."
421 ;; Do the time part, which is pretty simple (except for leap 421 ;; Do the time part, which is pretty simple (except for leap
422 ;; seconds, I guess). 422 ;; seconds, I guess).
423 ;; Time zone adjustments are basically the same as time adjustments. 423 ;; Time zone adjustments are basically the same as time adjustments.
424 (setq seconds (time-add (+ (* (or (decoded-time-hour delta) 0) 3600) 424 (setq seconds (time-convert (or (decoded-time-second delta) 0) t))
425 (* (or (decoded-time-minute delta) 0) 60) 425 (setq seconds
426 (or (decoded-time-zone delta) 0)) 426 (time-add seconds
427 (or (decoded-time-second delta) 0))) 427 (time-convert (+ (* (or (decoded-time-hour delta) 0) 3600)
428 (* (or (decoded-time-minute delta) 0) 60)
429 (or (decoded-time-zone delta) 0))
430 (cdr seconds))))
428 431
429 (decoded-time--alter-second time seconds) 432 (decoded-time--alter-second time seconds)
430 time)) 433 time))
@@ -461,11 +464,16 @@ changes in daylight saving time are not taken into account."
461 464
462(defun decoded-time--alter-second (time seconds) 465(defun decoded-time--alter-second (time seconds)
463 "Increase the time in TIME by SECONDS." 466 "Increase the time in TIME by SECONDS."
464 (let* ((secsperday 86400) 467 (let* ((time-sec (time-convert (or (decoded-time-second time) 0) t))
465 (old (time-add (+ (* 3600 (or (decoded-time-hour time) 0)) 468 (time-hz (cdr time-sec))
466 (* 60 (or (decoded-time-minute time) 0))) 469 (old (time-add time-sec
467 (or (decoded-time-second time) 0))) 470 (time-convert
468 (new (time-add old seconds))) 471 (+ (* 3600 (or (decoded-time-hour time) 0))
472 (* 60 (or (decoded-time-minute time) 0)))
473 time-hz)))
474 (new (time-convert (time-add old seconds) t))
475 (new-hz (cdr new))
476 (secsperday (time-convert 86400 new-hz)))
469 ;; Hm... DST... 477 ;; Hm... DST...
470 (while (time-less-p new 0) 478 (while (time-less-p new 0)
471 (decoded-time--alter-day time nil) 479 (decoded-time--alter-day time nil)
@@ -474,8 +482,10 @@ changes in daylight saving time are not taken into account."
474 (decoded-time--alter-day time t) 482 (decoded-time--alter-day time t)
475 (setq new (time-subtract new secsperday))) 483 (setq new (time-subtract new secsperday)))
476 (let ((sec (time-convert new 'integer))) 484 (let ((sec (time-convert new 'integer)))
477 (setf (decoded-time-second time) (time-add (% sec 60) 485 (setf (decoded-time-second time) (time-add
478 (time-subtract new sec)) 486 (time-convert (% sec 60) new-hz)
487 (time-subtract
488 new (time-convert sec new-hz)))
479 (decoded-time-minute time) (% (/ sec 60) 60) 489 (decoded-time-minute time) (% (/ sec 60) 60)
480 (decoded-time-hour time) (/ sec 3600))))) 490 (decoded-time-hour time) (/ sec 3600)))))
481 491
diff --git a/src/bignum.c b/src/bignum.c
index 3883d3a3944..90b1ebea876 100644
--- a/src/bignum.c
+++ b/src/bignum.c
@@ -31,9 +31,10 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */
31 storage is exhausted. Admittedly this is not ideal. An mpz value 31 storage is exhausted. Admittedly this is not ideal. An mpz value
32 in a temporary is made permanent by mpz_swapping it with a bignum's 32 in a temporary is made permanent by mpz_swapping it with a bignum's
33 value. Although typically at most two temporaries are needed, 33 value. Although typically at most two temporaries are needed,
34 time_arith, rounddiv_q and rounding_driver each need four. */ 34 rounddiv_q and rounding_driver both need four and time_arith needs
35 five. */
35 36
36mpz_t mpz[4]; 37mpz_t mpz[5];
37 38
38static void * 39static void *
39xrealloc_for_gmp (void *ptr, size_t ignore, size_t size) 40xrealloc_for_gmp (void *ptr, size_t ignore, size_t size)
diff --git a/src/bignum.h b/src/bignum.h
index a9c7a0a09a8..9a32ffb0374 100644
--- a/src/bignum.h
+++ b/src/bignum.h
@@ -41,7 +41,7 @@ struct Lisp_Bignum
41 mpz_t value; 41 mpz_t value;
42} GCALIGNED_STRUCT; 42} GCALIGNED_STRUCT;
43 43
44extern mpz_t mpz[4]; 44extern mpz_t mpz[5];
45 45
46extern void init_bignum (void); 46extern void init_bignum (void);
47extern Lisp_Object make_integer_mpz (void); 47extern Lisp_Object make_integer_mpz (void);
diff --git a/src/timefns.c b/src/timefns.c
index 3b686eb2265..3c4c15b6576 100644
--- a/src/timefns.c
+++ b/src/timefns.c
@@ -99,6 +99,22 @@ mpz_t ztrillion;
99# endif 99# endif
100#endif 100#endif
101 101
102/* True if the nonzero Lisp integer HZ divides evenly into a trillion. */
103static bool
104trillion_factor (Lisp_Object hz)
105{
106 if (FASTER_TIMEFNS)
107 {
108 if (FIXNUMP (hz))
109 return TRILLION % XFIXNUM (hz) == 0;
110 if (!FIXNUM_OVERFLOW_P (TRILLION))
111 return false;
112 }
113 verify (TRILLION <= INTMAX_MAX);
114 intmax_t ihz;
115 return integer_to_intmax (hz, &ihz) && TRILLION % ihz == 0;
116}
117
102/* Return a struct timeval that is roughly equivalent to T. 118/* Return a struct timeval that is roughly equivalent to T.
103 Use the least timeval not less than T. 119 Use the least timeval not less than T.
104 Return an extremal value if the result would overflow. */ 120 Return an extremal value if the result would overflow. */
@@ -681,18 +697,10 @@ enum timeform
681 TIMEFORM_HI_LO_US, /* seconds plus microseconds (HI LO US) */ 697 TIMEFORM_HI_LO_US, /* seconds plus microseconds (HI LO US) */
682 TIMEFORM_NIL, /* current time in nanoseconds */ 698 TIMEFORM_NIL, /* current time in nanoseconds */
683 TIMEFORM_HI_LO_US_PS, /* seconds plus micro and picoseconds (HI LO US PS) */ 699 TIMEFORM_HI_LO_US_PS, /* seconds plus micro and picoseconds (HI LO US PS) */
684 /* These two should be last; see timeform_sub_ps_p. */
685 TIMEFORM_FLOAT, /* time as a float */ 700 TIMEFORM_FLOAT, /* time as a float */
686 TIMEFORM_TICKS_HZ /* fractional time: HI is ticks, LO is ticks per second */ 701 TIMEFORM_TICKS_HZ /* fractional time: HI is ticks, LO is ticks per second */
687 }; 702 };
688 703
689/* True if Lisp times of form FORM can express sub-picosecond timestamps. */
690static bool
691timeform_sub_ps_p (enum timeform form)
692{
693 return TIMEFORM_FLOAT <= form;
694}
695
696/* From the valid form FORM and the time components HIGH, LOW, USEC 704/* From the valid form FORM and the time components HIGH, LOW, USEC
697 and PSEC, generate the corresponding time value. If LOW is 705 and PSEC, generate the corresponding time value. If LOW is
698 floating point, the other components should be zero and FORM should 706 floating point, the other components should be zero and FORM should
@@ -1080,9 +1088,14 @@ time_arith (Lisp_Object a, Lisp_Object b, bool subtract)
1080 else 1088 else
1081 { 1089 {
1082 /* The plan is to decompose ta into na/da and tb into nb/db. 1090 /* The plan is to decompose ta into na/da and tb into nb/db.
1083 Start by computing da and db. */ 1091 Start by computing da and db, their minimum (which will be
1092 needed later) and the iticks temporary that will become
1093 available once only their minimum is needed. */
1084 mpz_t const *da = bignum_integer (&mpz[1], ta.hz); 1094 mpz_t const *da = bignum_integer (&mpz[1], ta.hz);
1085 mpz_t const *db = bignum_integer (&mpz[2], tb.hz); 1095 mpz_t const *db = bignum_integer (&mpz[2], tb.hz);
1096 bool da_lt_db = mpz_cmp (*da, *db) < 0;
1097 mpz_t const *hzmin = da_lt_db ? da : db;
1098 mpz_t *iticks = &mpz[da_lt_db + 1];
1086 1099
1087 /* The plan is to compute (na * (db/g) + nb * (da/g)) / lcm (da, db) 1100 /* The plan is to compute (na * (db/g) + nb * (da/g)) / lcm (da, db)
1088 where g = gcd (da, db). Start by computing g. */ 1101 where g = gcd (da, db). Start by computing g. */
@@ -1090,34 +1103,83 @@ time_arith (Lisp_Object a, Lisp_Object b, bool subtract)
1090 mpz_gcd (*g, *da, *db); 1103 mpz_gcd (*g, *da, *db);
1091 1104
1092 /* fa = da/g, fb = db/g. */ 1105 /* fa = da/g, fb = db/g. */
1093 mpz_t *fa = &mpz[1], *fb = &mpz[3]; 1106 mpz_t *fa = &mpz[4], *fb = &mpz[3];
1094 mpz_tdiv_q (*fa, *da, *g); 1107 mpz_tdiv_q (*fa, *da, *g);
1095 mpz_tdiv_q (*fb, *db, *g); 1108 mpz_tdiv_q (*fb, *db, *g);
1096 1109
1097 /* FIXME: Maybe omit need for extra temp by computing fa * db here? */ 1110 /* ihz = fa * db. This is equal to lcm (da, db). */
1098 1111 mpz_t *ihz = &mpz[0];
1099 /* hz = fa * db. This is equal to lcm (da, db). */ 1112 mpz_mul (*ihz, *fa, *db);
1100 mpz_mul (mpz[0], *fa, *db); 1113
1101 hz = make_integer_mpz (); 1114 /* When warning about obsolete timestamps, if the smaller
1115 denominator comes from a non-(TICKS . HZ) timestamp and could
1116 generate a (TICKS . HZ) timestamp that would look obsolete,
1117 arrange for the result to have a higher HZ to avoid a
1118 spurious warning by a later consumer of this function's
1119 returned value. */
1120 verify (1 << LO_TIME_BITS <= ULONG_MAX);
1121 if (WARN_OBSOLETE_TIMESTAMPS
1122 && (da_lt_db ? aform : bform) == TIMEFORM_FLOAT
1123 && (da_lt_db ? bform : aform) != TIMEFORM_TICKS_HZ
1124 && mpz_cmp_ui (*hzmin, 1) > 0
1125 && mpz_cmp_ui (*hzmin, 1 << LO_TIME_BITS) < 0)
1126 {
1127 mpz_t *hzmin1 = &mpz[2 - da_lt_db];
1128 mpz_set_ui (*hzmin1, 1 << LO_TIME_BITS);
1129 hzmin = hzmin1;
1130 }
1102 1131
1103 /* ticks = (fb * na) OPER (fa * nb), where OPER is + or -. 1132 /* iticks = (fb * na) OP (fa * nb), where OP is + or -. */
1104 OP is the multiply-add or multiply-sub form of OPER. */ 1133 mpz_t const *na = bignum_integer (iticks, ta.ticks);
1105 mpz_t const *na = bignum_integer (&mpz[0], ta.ticks); 1134 mpz_mul (*iticks, *fb, *na);
1106 mpz_mul (mpz[0], *fb, *na);
1107 mpz_t const *nb = bignum_integer (&mpz[3], tb.ticks); 1135 mpz_t const *nb = bignum_integer (&mpz[3], tb.ticks);
1108 (subtract ? mpz_submul : mpz_addmul) (mpz[0], *fa, *nb); 1136 (subtract ? mpz_submul : mpz_addmul) (*iticks, *fa, *nb);
1137
1138 /* Normalize iticks/ihz by dividing both numerator and
1139 denominator by ig = gcd (iticks, ihz). However, if that
1140 would cause the denominator to become less than hzmin,
1141 rescale the denominator upwards from its ordinary value by
1142 multiplying numerator and denominator so that the denominator
1143 becomes at least hzmin. This rescaling avoids returning a
1144 timestamp that is less precise than both a and b, or a
1145 timestamp that looks obsolete when that might be a problem. */
1146 mpz_t *ig = &mpz[3];
1147 mpz_gcd (*ig, *iticks, *ihz);
1148
1149 if (!FASTER_TIMEFNS || mpz_cmp_ui (*ig, 1) > 0)
1150 {
1151 mpz_tdiv_q (*iticks, *iticks, *ig);
1152 mpz_tdiv_q (*ihz, *ihz, *ig);
1153
1154 if (!FASTER_TIMEFNS || mpz_cmp (*ihz, *hzmin) < 0)
1155 {
1156 /* Rescale straightforwardly. Although this might not
1157 yield the minimal denominator that preserves numeric
1158 value and is at least hzmin, calculating such a
1159 denominator would be too expensive because it would
1160 require testing multisets of factors of lcm (da, db). */
1161 mpz_t *rescale = &mpz[3];
1162 mpz_cdiv_q (*rescale, *hzmin, *ihz);
1163 mpz_mul (*iticks, *iticks, *rescale);
1164 mpz_mul (*ihz, *ihz, *rescale);
1165 }
1166 }
1167 hz = make_integer_mpz ();
1168 mpz_swap (mpz[0], *iticks);
1109 ticks = make_integer_mpz (); 1169 ticks = make_integer_mpz ();
1110 } 1170 }
1111 1171
1112 /* Return an integer if the timestamp resolution is 1, 1172 /* Return an integer if the timestamp resolution is 1,
1113 otherwise the (TICKS . HZ) form if !CURRENT_TIME_LIST or if 1173 otherwise the (TICKS . HZ) form if !CURRENT_TIME_LIST or if
1114 either input form supports timestamps that cannot be expressed 1174 either input used (TICKS . HZ) form or the result can't be expressed
1115 exactly in (HI LO US PS) form, otherwise the (HI LO US PS) form 1175 exactly in (HI LO US PS) form, otherwise the (HI LO US PS) form
1116 for backward compatibility. */ 1176 for backward compatibility. */
1117 return (EQ (hz, make_fixnum (1)) 1177 return (EQ (hz, make_fixnum (1))
1118 ? ticks 1178 ? ticks
1119 : (!CURRENT_TIME_LIST 1179 : (!CURRENT_TIME_LIST
1120 || timeform_sub_ps_p (aform) || timeform_sub_ps_p (bform)) 1180 || aform == TIMEFORM_TICKS_HZ
1181 || bform == TIMEFORM_TICKS_HZ
1182 || !trillion_factor (hz))
1121 ? Fcons (ticks, hz) 1183 ? Fcons (ticks, hz)
1122 : ticks_hz_list4 (ticks, hz)); 1184 : ticks_hz_list4 (ticks, hz));
1123} 1185}
diff --git a/test/src/timefns-tests.el b/test/src/timefns-tests.el
index 48d964d129c..3a18a4a24dd 100644
--- a/test/src/timefns-tests.el
+++ b/test/src/timefns-tests.el
@@ -19,6 +19,12 @@
19 19
20(require 'ert) 20(require 'ert)
21 21
22(defun timefns-tests--decode-time (look zone decoded-time)
23 (should (equal (decode-time look zone t) decoded-time))
24 (should (equal (decode-time look zone 'integer)
25 (cons (time-convert (car decoded-time) 'integer)
26 (cdr decoded-time)))))
27
22;;; Check format-time-string and decode-time with various TZ settings. 28;;; Check format-time-string and decode-time with various TZ settings.
23;;; Use only POSIX-compatible TZ values, since the tests should work 29;;; Use only POSIX-compatible TZ values, since the tests should work
24;;; even if tzdb is not in use. 30;;; even if tzdb is not in use.
@@ -40,31 +46,29 @@
40 (7879679999900 . 100000) 46 (7879679999900 . 100000)
41 (78796799999999999999 . 1000000000000))) 47 (78796799999999999999 . 1000000000000)))
42 ;; UTC. 48 ;; UTC.
43 (let ((sec (time-add 59 (time-subtract (time-convert look t) 49 (let* ((look-ticks-hz (time-convert look t))
44 (time-convert look 'integer))))) 50 (hz (cdr look-ticks-hz))
51 (look-integer (time-convert look 'integer))
52 (sec (time-add (time-convert 59 hz)
53 (time-subtract look-ticks-hz
54 (time-convert look-integer hz)))))
45 (should (string-equal 55 (should (string-equal
46 (format-time-string "%Y-%m-%d %H:%M:%S.%3N %z" look t) 56 (format-time-string "%Y-%m-%d %H:%M:%S.%3N %z" look t)
47 "1972-06-30 23:59:59.999 +0000")) 57 "1972-06-30 23:59:59.999 +0000"))
48 (should (equal (decode-time look t 'integer) 58 (timefns-tests--decode-time look t
49 '(59 59 23 30 6 1972 5 nil 0))) 59 (list sec 59 23 30 6 1972 5 nil 0))
50 (should (equal (decode-time look t t)
51 (list sec 59 23 30 6 1972 5 nil 0)))
52 ;; "UTC0". 60 ;; "UTC0".
53 (should (string-equal 61 (should (string-equal
54 (format-time-string format look "UTC0") 62 (format-time-string format look "UTC0")
55 "1972-06-30 23:59:59.999 +0000 (UTC)")) 63 "1972-06-30 23:59:59.999 +0000 (UTC)"))
56 (should (equal (decode-time look "UTC0" 'integer) 64 (timefns-tests--decode-time look "UTC0"
57 '(59 59 23 30 6 1972 5 nil 0))) 65 (list sec 59 23 30 6 1972 5 nil 0))
58 (should (equal (decode-time look "UTC0" t)
59 (list sec 59 23 30 6 1972 5 nil 0)))
60 ;; Negative UTC offset, as a Lisp list. 66 ;; Negative UTC offset, as a Lisp list.
61 (should (string-equal 67 (should (string-equal
62 (format-time-string format look '(-28800 "PST")) 68 (format-time-string format look '(-28800 "PST"))
63 "1972-06-30 15:59:59.999 -0800 (PST)")) 69 "1972-06-30 15:59:59.999 -0800 (PST)"))
64 (should (equal (decode-time look '(-28800 "PST") 'integer) 70 (timefns-tests--decode-time look '(-28800 "PST")
65 '(59 59 15 30 6 1972 5 nil -28800))) 71 (list sec 59 15 30 6 1972 5 nil -28800))
66 (should (equal (decode-time look '(-28800 "PST") t)
67 (list sec 59 15 30 6 1972 5 nil -28800)))
68 ;; Negative UTC offset, as a Lisp integer. 72 ;; Negative UTC offset, as a Lisp integer.
69 (should (string-equal 73 (should (string-equal
70 (format-time-string format look -28800) 74 (format-time-string format look -28800)
@@ -73,18 +77,14 @@
73 (if (eq system-type 'windows-nt) 77 (if (eq system-type 'windows-nt)
74 "1972-06-30 15:59:59.999 -0800 (ZZZ)" 78 "1972-06-30 15:59:59.999 -0800 (ZZZ)"
75 "1972-06-30 15:59:59.999 -0800 (-08)"))) 79 "1972-06-30 15:59:59.999 -0800 (-08)")))
76 (should (equal (decode-time look -28800 'integer) 80 (timefns-tests--decode-time look -28800
77 '(59 59 15 30 6 1972 5 nil -28800))) 81 (list sec 59 15 30 6 1972 5 nil -28800))
78 (should (equal (decode-time look -28800 t)
79 (list sec 59 15 30 6 1972 5 nil -28800)))
80 ;; Positive UTC offset that is not an hour multiple, as a string. 82 ;; Positive UTC offset that is not an hour multiple, as a string.
81 (should (string-equal 83 (should (string-equal
82 (format-time-string format look "IST-5:30") 84 (format-time-string format look "IST-5:30")
83 "1972-07-01 05:29:59.999 +0530 (IST)")) 85 "1972-07-01 05:29:59.999 +0530 (IST)"))
84 (should (equal (decode-time look "IST-5:30" 'integer) 86 (timefns-tests--decode-time look "IST-5:30"
85 '(59 29 5 1 7 1972 6 nil 19800))) 87 (list sec 29 5 1 7 1972 6 nil 19800))))))
86 (should (equal (decode-time look "IST-5:30" t)
87 (list sec 29 5 1 7 1972 6 nil 19800)))))))
88 88
89(ert-deftest decode-then-encode-time () 89(ert-deftest decode-then-encode-time ()
90 (let ((time-values (list 0 -2 1 0.0 -0.0 -2.0 1.0 90 (let ((time-values (list 0 -2 1 0.0 -0.0 -2.0 1.0