aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorPaul Eggert2019-08-20 17:34:03 -0700
committerPaul Eggert2019-08-20 17:36:46 -0700
commit396ed88a50fba95cd3b989965defef0130a42c42 (patch)
tree95b03c537acf8d65b6d894a283eccf9f1d31a1e8 /src
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.
Diffstat (limited to 'src')
-rw-r--r--src/bignum.c5
-rw-r--r--src/bignum.h2
-rw-r--r--src/timefns.c106
3 files changed, 88 insertions, 25 deletions
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}