diff options
| author | Paul Eggert | 2019-08-15 10:40:11 -0700 |
|---|---|---|
| committer | Paul Eggert | 2019-08-15 10:41:40 -0700 |
| commit | af82a6248ce77f1b14f89cfee677250ff024c2c4 (patch) | |
| tree | f93ac7f0f37c16ad1cbf1292d3427d7357ac3e52 /src | |
| parent | f6ae51c71d69b4d1a02fc8f6536f3f8cc0dc1009 (diff) | |
| download | emacs-af82a6248ce77f1b14f89cfee677250ff024c2c4.tar.gz emacs-af82a6248ce77f1b14f89cfee677250ff024c2c4.zip | |
Fix rounding errors with float timestamps
When converting from float to (TICKS . HZ) form, do the
conversion exactly. When converting from (TICKS . HZ) form to
float, round to even precisely. This way, successfully
converting a float to (TICKS . HZ) and back yields a value
numerically equal to the original.
* src/timefns.c (flt_radix_power_size): New constant.
(flt_radix_power): New static var.
(decode_float_time): Convert the exact numeric value rather
than guessing TIMESPEC_HZ resolution.
(s_ns_to_double): Remove; no longer needed.
(frac_to_double): New function.
(decode_ticks_hz): It is now the caller’s responsibility to
pass a valid TICKS and HZ. All callers changed.
Use frac_to_double to round (TICKS . HZ) precisely.
(decode_time_components): When decoding nil, use
decode_ticks_hz since it rounds precisely.
(syms_of_timefns): Initialize flt_radix_power.
* test/src/timefns-tests.el (float-time-precision): New test.
Diffstat (limited to 'src')
| -rw-r--r-- | src/timefns.c | 221 |
1 files changed, 143 insertions, 78 deletions
diff --git a/src/timefns.c b/src/timefns.c index 953e246a9ae..e9d1a9bf64b 100644 --- a/src/timefns.c +++ b/src/timefns.c | |||
| @@ -368,31 +368,56 @@ lo_time (time_t t) | |||
| 368 | return make_fixnum (t & ((1 << LO_TIME_BITS) - 1)); | 368 | return make_fixnum (t & ((1 << LO_TIME_BITS) - 1)); |
| 369 | } | 369 | } |
| 370 | 370 | ||
| 371 | /* When converting a double to a fraction TICKS / HZ, HZ is equal to | ||
| 372 | FLT_RADIX * P where 0 <= P < FLT_RADIX_POWER_SIZE. The tiniest | ||
| 373 | nonzero double uses the maximum P. */ | ||
| 374 | enum { flt_radix_power_size = DBL_MANT_DIG - DBL_MIN_EXP + 1 }; | ||
| 375 | |||
| 376 | /* A integer vector of size flt_radix_power_size. The Pth entry | ||
| 377 | equals FLT_RADIX**P. */ | ||
| 378 | static Lisp_Object flt_radix_power; | ||
| 379 | |||
| 371 | /* Convert T into an Emacs time *RESULT, truncating toward minus infinity. | 380 | /* Convert T into an Emacs time *RESULT, truncating toward minus infinity. |
| 372 | Return zero if successful, an error number otherwise. */ | 381 | Return zero if successful, an error number otherwise. */ |
| 373 | static int | 382 | static int |
| 374 | decode_float_time (double t, struct lisp_time *result) | 383 | decode_float_time (double t, struct lisp_time *result) |
| 375 | { | 384 | { |
| 376 | if (!isfinite (t)) | 385 | Lisp_Object ticks, hz; |
| 377 | return isnan (t) ? EINVAL : EOVERFLOW; | 386 | if (t == 0) |
| 378 | /* Actual hz unknown; guess TIMESPEC_HZ. */ | 387 | { |
| 379 | mpz_set_d (mpz[1], t); | 388 | ticks = make_fixnum (0); |
| 380 | mpz_set_si (mpz[0], floor ((t - trunc (t)) * TIMESPEC_HZ)); | 389 | hz = make_fixnum (1); |
| 381 | mpz_addmul_ui (mpz[0], mpz[1], TIMESPEC_HZ); | 390 | } |
| 382 | result->ticks = make_integer_mpz (); | 391 | else |
| 383 | result->hz = timespec_hz; | 392 | { |
| 393 | int exponent = ilogb (t); | ||
| 394 | if (exponent == FP_ILOGBNAN) | ||
| 395 | return EINVAL; | ||
| 396 | |||
| 397 | /* An enormous or infinite T would make SCALE < 0 which would make | ||
| 398 | HZ < 1, which the (TICKS . HZ) representation does not allow. */ | ||
| 399 | if (DBL_MANT_DIG - 1 < exponent) | ||
| 400 | return EOVERFLOW; | ||
| 401 | |||
| 402 | /* min so we don't scale tiny numbers as if they were normalized. */ | ||
| 403 | int scale = min (DBL_MANT_DIG - 1 - exponent, flt_radix_power_size - 1); | ||
| 404 | |||
| 405 | double scaled = scalbn (t, scale); | ||
| 406 | eassert (trunc (scaled) == scaled); | ||
| 407 | ticks = double_to_integer (scaled); | ||
| 408 | hz = AREF (flt_radix_power, scale); | ||
| 409 | if (NILP (hz)) | ||
| 410 | { | ||
| 411 | mpz_ui_pow_ui (mpz[0], FLT_RADIX, scale); | ||
| 412 | hz = make_integer_mpz (); | ||
| 413 | ASET (flt_radix_power, scale, hz); | ||
| 414 | } | ||
| 415 | } | ||
| 416 | result->ticks = ticks; | ||
| 417 | result->hz = hz; | ||
| 384 | return 0; | 418 | return 0; |
| 385 | } | 419 | } |
| 386 | 420 | ||
| 387 | /* Compute S + NS/TIMESPEC_HZ as a double. | ||
| 388 | Calls to this function suffer from double-rounding; | ||
| 389 | work around some of the problem by using long double. */ | ||
| 390 | static double | ||
| 391 | s_ns_to_double (long double s, long double ns) | ||
| 392 | { | ||
| 393 | return s + ns / TIMESPEC_HZ; | ||
| 394 | } | ||
| 395 | |||
| 396 | /* Make a 4-element timestamp (HI LO US PS) from TICKS and HZ. | 421 | /* Make a 4-element timestamp (HI LO US PS) from TICKS and HZ. |
| 397 | Drop any excess precision. */ | 422 | Drop any excess precision. */ |
| 398 | static Lisp_Object | 423 | static Lisp_Object |
| @@ -520,69 +545,111 @@ timespec_to_lisp (struct timespec t) | |||
| 520 | return Fcons (timespec_ticks (t), timespec_hz); | 545 | return Fcons (timespec_ticks (t), timespec_hz); |
| 521 | } | 546 | } |
| 522 | 547 | ||
| 523 | /* From what should be a valid timestamp (TICKS . HZ), generate the | 548 | /* Return NUMERATOR / DENOMINATOR, rounded to the nearest double. |
| 524 | corresponding time values. | 549 | Arguments must be Lisp integers, and DENOMINATOR must be nonzero. */ |
| 550 | static double | ||
| 551 | frac_to_double (Lisp_Object numerator, Lisp_Object denominator) | ||
| 552 | { | ||
| 553 | intmax_t intmax_numerator; | ||
| 554 | if (FASTER_TIMEFNS && EQ (denominator, make_fixnum (1)) | ||
| 555 | && integer_to_intmax (numerator, &intmax_numerator)) | ||
| 556 | return intmax_numerator; | ||
| 557 | |||
| 558 | verify (FLT_RADIX == 2 || FLT_RADIX == 16); | ||
| 559 | enum { LOG2_FLT_RADIX = FLT_RADIX == 2 ? 1 : 4 }; | ||
| 560 | mpz_t *n = bignum_integer (&mpz[0], numerator); | ||
| 561 | mpz_t *d = bignum_integer (&mpz[1], denominator); | ||
| 562 | ptrdiff_t nbits = mpz_sizeinbase (*n, 2); | ||
| 563 | ptrdiff_t dbits = mpz_sizeinbase (*d, 2); | ||
| 564 | eassume (0 < nbits); | ||
| 565 | eassume (0 < dbits); | ||
| 566 | ptrdiff_t ndig = (nbits + LOG2_FLT_RADIX - 1) / LOG2_FLT_RADIX; | ||
| 567 | ptrdiff_t ddig = (dbits + LOG2_FLT_RADIX - 1) / LOG2_FLT_RADIX; | ||
| 568 | |||
| 569 | /* Scale with SCALE when doing integer division. That is, compute | ||
| 570 | (N * FLT_RADIX**SCALE) / D [or, if SCALE is negative, N / (D * | ||
| 571 | FLT_RADIX**-SCALE)] as a bignum, convert the bignum to double, | ||
| 572 | then divide the double by FLT_RADIX**SCALE. */ | ||
| 573 | ptrdiff_t scale = ddig - ndig + DBL_MANT_DIG + 1; | ||
| 574 | if (scale < 0) | ||
| 575 | { | ||
| 576 | mpz_mul_2exp (mpz[1], *d, - (scale * LOG2_FLT_RADIX)); | ||
| 577 | d = &mpz[1]; | ||
| 578 | } | ||
| 579 | else | ||
| 580 | { | ||
| 581 | /* min so we don't scale tiny numbers as if they were normalized. */ | ||
| 582 | scale = min (scale, flt_radix_power_size - 1); | ||
| 583 | |||
| 584 | mpz_mul_2exp (mpz[0], *n, scale * LOG2_FLT_RADIX); | ||
| 585 | n = &mpz[0]; | ||
| 586 | } | ||
| 587 | |||
| 588 | mpz_t *q = &mpz[2]; | ||
| 589 | mpz_t *r = &mpz[3]; | ||
| 590 | mpz_tdiv_qr (*q, *r, *n, *d); | ||
| 591 | |||
| 592 | /* The amount to add to the absolute value of *Q so that truncating | ||
| 593 | it to double will round correctly. */ | ||
| 594 | int incr; | ||
| 595 | |||
| 596 | /* Round the quotient before converting it to double. | ||
| 597 | If the quotient is less than FLT_RADIX ** DBL_MANT_DIG, | ||
| 598 | round to the nearest integer; otherwise, it is less than | ||
| 599 | FLT_RADIX ** (DBL_MANT_DIG + 1) and round it to the nearest | ||
| 600 | multiple of FLT_RADIX. Break ties to even. */ | ||
| 601 | if (mpz_sizeinbase (*q, 2) < DBL_MANT_DIG * LOG2_FLT_RADIX) | ||
| 602 | { | ||
| 603 | /* Converting to double will use the whole quotient so add 1 to | ||
| 604 | its absolute value as per round-to-even; i.e., if the doubled | ||
| 605 | remainder exceeds the denominator, or exactly equals the | ||
| 606 | denominator and adding 1 would make the quotient even. */ | ||
| 607 | mpz_mul_2exp (*r, *r, 1); | ||
| 608 | int cmp = mpz_cmpabs (*r, *d); | ||
| 609 | incr = cmp > 0 || (cmp == 0 && (FASTER_TIMEFNS && FLT_RADIX == 2 | ||
| 610 | ? mpz_odd_p (*q) | ||
| 611 | : mpz_tdiv_ui (*q, FLT_RADIX) & 1)); | ||
| 612 | } | ||
| 613 | else | ||
| 614 | { | ||
| 615 | /* Converting to double will discard the quotient's low-order digit, | ||
| 616 | so add FLT_RADIX to its absolute value as per round-to-even. */ | ||
| 617 | int lo_2digits = mpz_tdiv_ui (*q, FLT_RADIX * FLT_RADIX); | ||
| 618 | eassume (0 <= lo_2digits && lo_2digits < FLT_RADIX * FLT_RADIX); | ||
| 619 | int lo_digit = lo_2digits % FLT_RADIX; | ||
| 620 | incr = ((lo_digit > FLT_RADIX / 2 | ||
| 621 | || (lo_digit == FLT_RADIX / 2 && FLT_RADIX % 2 == 0 | ||
| 622 | && ((lo_2digits / FLT_RADIX) & 1 | ||
| 623 | || mpz_sgn (*r) != 0))) | ||
| 624 | ? FLT_RADIX : 0); | ||
| 625 | } | ||
| 626 | |||
| 627 | /* Increment the absolute value of the quotient by INCR. */ | ||
| 628 | if (!FASTER_TIMEFNS || incr != 0) | ||
| 629 | (mpz_sgn (*n) < 0 ? mpz_sub_ui : mpz_add_ui) (*q, *q, incr); | ||
| 630 | |||
| 631 | return scalbn (mpz_get_d (*q), -scale); | ||
| 632 | } | ||
| 633 | |||
| 634 | /* From a valid timestamp (TICKS . HZ), generate the corresponding | ||
| 635 | time values. | ||
| 525 | 636 | ||
| 526 | If RESULT is not null, store into *RESULT the converted time. | 637 | If RESULT is not null, store into *RESULT the converted time. |
| 527 | Otherwise, store into *DRESULT the number of seconds since the | 638 | Otherwise, store into *DRESULT the number of seconds since the |
| 528 | start of the POSIX Epoch. Unsuccessful calls may or may not store | 639 | start of the POSIX Epoch. |
| 529 | results. | ||
| 530 | 640 | ||
| 531 | Return zero if successful, an error number if (TICKS . HZ) would not | 641 | Return zero, which indicates success. */ |
| 532 | be a valid new-format timestamp. */ | ||
| 533 | static int | 642 | static int |
| 534 | decode_ticks_hz (Lisp_Object ticks, Lisp_Object hz, | 643 | decode_ticks_hz (Lisp_Object ticks, Lisp_Object hz, |
| 535 | struct lisp_time *result, double *dresult) | 644 | struct lisp_time *result, double *dresult) |
| 536 | { | 645 | { |
| 537 | int ns; | ||
| 538 | mpz_t *q = &mpz[0]; | ||
| 539 | |||
| 540 | if (! (INTEGERP (ticks) | ||
| 541 | && ((FIXNUMP (hz) && 0 < XFIXNUM (hz)) | ||
| 542 | || (BIGNUMP (hz) && 0 < mpz_sgn (XBIGNUM (hz)->value))))) | ||
| 543 | return EINVAL; | ||
| 544 | |||
| 545 | if (result) | 646 | if (result) |
| 546 | { | 647 | { |
| 547 | result->ticks = ticks; | 648 | result->ticks = ticks; |
| 548 | result->hz = hz; | 649 | result->hz = hz; |
| 549 | } | 650 | } |
| 550 | else | 651 | else |
| 551 | { | 652 | *dresult = frac_to_double (ticks, hz); |
| 552 | if (FASTER_TIMEFNS && EQ (hz, timespec_hz)) | ||
| 553 | { | ||
| 554 | if (FIXNUMP (ticks)) | ||
| 555 | { | ||
| 556 | verify (1 < TIMESPEC_HZ); | ||
| 557 | EMACS_INT s = XFIXNUM (ticks) / TIMESPEC_HZ; | ||
| 558 | ns = XFIXNUM (ticks) % TIMESPEC_HZ; | ||
| 559 | if (ns < 0) | ||
| 560 | s--, ns += TIMESPEC_HZ; | ||
| 561 | *dresult = s_ns_to_double (s, ns); | ||
| 562 | return 0; | ||
| 563 | } | ||
| 564 | ns = mpz_fdiv_q_ui (*q, XBIGNUM (ticks)->value, TIMESPEC_HZ); | ||
| 565 | } | ||
| 566 | else if (FASTER_TIMEFNS && EQ (hz, make_fixnum (1))) | ||
| 567 | { | ||
| 568 | ns = 0; | ||
| 569 | if (FIXNUMP (ticks)) | ||
| 570 | { | ||
| 571 | *dresult = XFIXNUM (ticks); | ||
| 572 | return 0; | ||
| 573 | } | ||
| 574 | q = &XBIGNUM (ticks)->value; | ||
| 575 | } | ||
| 576 | else | ||
| 577 | { | ||
| 578 | mpz_mul_ui (*q, *bignum_integer (&mpz[1], ticks), TIMESPEC_HZ); | ||
| 579 | mpz_fdiv_q (*q, *q, *bignum_integer (&mpz[1], hz)); | ||
| 580 | ns = mpz_fdiv_q_ui (*q, *q, TIMESPEC_HZ); | ||
| 581 | } | ||
| 582 | |||
| 583 | *dresult = s_ns_to_double (mpz_get_d (*q), ns); | ||
| 584 | } | ||
| 585 | |||
| 586 | return 0; | 653 | return 0; |
| 587 | } | 654 | } |
| 588 | 655 | ||
| @@ -621,7 +688,10 @@ decode_time_components (enum timeform form, | |||
| 621 | return EINVAL; | 688 | return EINVAL; |
| 622 | 689 | ||
| 623 | case TIMEFORM_TICKS_HZ: | 690 | case TIMEFORM_TICKS_HZ: |
| 624 | return decode_ticks_hz (high, low, result, dresult); | 691 | if (INTEGERP (high) |
| 692 | && (!NILP (Fnatnump (low)) && !EQ (low, make_fixnum (0)))) | ||
| 693 | return decode_ticks_hz (high, low, result, dresult); | ||
| 694 | return EINVAL; | ||
| 625 | 695 | ||
| 626 | case TIMEFORM_FLOAT: | 696 | case TIMEFORM_FLOAT: |
| 627 | { | 697 | { |
| @@ -636,17 +706,8 @@ decode_time_components (enum timeform form, | |||
| 636 | } | 706 | } |
| 637 | 707 | ||
| 638 | case TIMEFORM_NIL: | 708 | case TIMEFORM_NIL: |
| 639 | { | 709 | return decode_ticks_hz (timespec_ticks (current_timespec ()), |
| 640 | struct timespec now = current_timespec (); | 710 | timespec_hz, result, dresult); |
| 641 | if (result) | ||
| 642 | { | ||
| 643 | result->ticks = timespec_ticks (now); | ||
| 644 | result->hz = timespec_hz; | ||
| 645 | } | ||
| 646 | else | ||
| 647 | *dresult = s_ns_to_double (now.tv_sec, now.tv_nsec); | ||
| 648 | return 0; | ||
| 649 | } | ||
| 650 | 711 | ||
| 651 | default: | 712 | default: |
| 652 | break; | 713 | break; |
| @@ -1814,6 +1875,10 @@ syms_of_timefns (void) | |||
| 1814 | defsubr (&Scurrent_time_string); | 1875 | defsubr (&Scurrent_time_string); |
| 1815 | defsubr (&Scurrent_time_zone); | 1876 | defsubr (&Scurrent_time_zone); |
| 1816 | defsubr (&Sset_time_zone_rule); | 1877 | defsubr (&Sset_time_zone_rule); |
| 1878 | |||
| 1879 | flt_radix_power = make_vector (flt_radix_power_size, Qnil); | ||
| 1880 | staticpro (&flt_radix_power); | ||
| 1881 | |||
| 1817 | #ifdef NEED_ZTRILLION_INIT | 1882 | #ifdef NEED_ZTRILLION_INIT |
| 1818 | pdumper_do_now_and_after_load (syms_of_timefns_for_pdumper); | 1883 | pdumper_do_now_and_after_load (syms_of_timefns_for_pdumper); |
| 1819 | #endif | 1884 | #endif |