diff options
| author | Paul Eggert | 2018-08-28 11:59:21 -0700 |
|---|---|---|
| committer | Paul Eggert | 2018-08-28 12:00:20 -0700 |
| commit | d4586b7a9cea6aac7d710d59fd29ce1b9a705449 (patch) | |
| tree | 0b96e557d181323d3c018301148753603da61913 /src/editfns.c | |
| parent | 5cb057a854a848be85dc2b81f6497964f5a93dbb (diff) | |
| download | emacs-d4586b7a9cea6aac7d710d59fd29ce1b9a705449.tar.gz emacs-d4586b7a9cea6aac7d710d59fd29ce1b9a705449.zip | |
Improve (format "%g" bignum) precision
* src/editfns.c (styled_format): When formatting bignums with
floating-point conversions like %g, use long double if that
would lose less information than double, which is what the
code was already doing for fixnums. On Fedora 28 x86-64, for
example, (format "%.100g" (1- (ash 1 64))) now yields
"18446744073709551615" instead of the numerically incorrect
"18446744073709549568". Also, fix a stray INTEGERP that
can just be FIXNUMP, since bignums are not possible there.
Diffstat (limited to 'src/editfns.c')
| -rw-r--r-- | src/editfns.c | 88 |
1 files changed, 59 insertions, 29 deletions
diff --git a/src/editfns.c b/src/editfns.c index ad5a26606b4..b4c597feda1 100644 --- a/src/editfns.c +++ b/src/editfns.c | |||
| @@ -4608,17 +4608,6 @@ styled_format (ptrdiff_t nargs, Lisp_Object *args, bool message) | |||
| 4608 | { | 4608 | { |
| 4609 | enum | 4609 | enum |
| 4610 | { | 4610 | { |
| 4611 | /* Lower bound on the number of bits per | ||
| 4612 | base-FLT_RADIX digit. */ | ||
| 4613 | DIG_BITS_LBOUND = FLT_RADIX < 16 ? 1 : 4, | ||
| 4614 | |||
| 4615 | /* 1 if integers should be formatted as long doubles, | ||
| 4616 | because they may be so large that there is a rounding | ||
| 4617 | error when converting them to double, and long doubles | ||
| 4618 | are wider than doubles. */ | ||
| 4619 | INT_AS_LDBL = (DIG_BITS_LBOUND * DBL_MANT_DIG < FIXNUM_BITS - 1 | ||
| 4620 | && DBL_MANT_DIG < LDBL_MANT_DIG), | ||
| 4621 | |||
| 4622 | /* Maximum precision for a %f conversion such that the | 4611 | /* Maximum precision for a %f conversion such that the |
| 4623 | trailing output digit might be nonzero. Any precision | 4612 | trailing output digit might be nonzero. Any precision |
| 4624 | larger than this will not yield useful information. */ | 4613 | larger than this will not yield useful information. */ |
| @@ -4649,7 +4638,7 @@ styled_format (ptrdiff_t nargs, Lisp_Object *args, bool message) | |||
| 4649 | with "L" possibly inserted for floating-point formats, | 4638 | with "L" possibly inserted for floating-point formats, |
| 4650 | and with pM inserted for integer formats. | 4639 | and with pM inserted for integer formats. |
| 4651 | At most two flags F can be specified at once. */ | 4640 | At most two flags F can be specified at once. */ |
| 4652 | char convspec[sizeof "%FF.*d" + max (INT_AS_LDBL, pMlen)]; | 4641 | char convspec[sizeof "%FF.*d" + max (sizeof "L" - 1, pMlen)]; |
| 4653 | char *f = convspec; | 4642 | char *f = convspec; |
| 4654 | *f++ = '%'; | 4643 | *f++ = '%'; |
| 4655 | /* MINUS_FLAG and ZERO_FLAG are dealt with later. */ | 4644 | /* MINUS_FLAG and ZERO_FLAG are dealt with later. */ |
| @@ -4658,15 +4647,7 @@ styled_format (ptrdiff_t nargs, Lisp_Object *args, bool message) | |||
| 4658 | *f = '#'; f += sharp_flag; | 4647 | *f = '#'; f += sharp_flag; |
| 4659 | *f++ = '.'; | 4648 | *f++ = '.'; |
| 4660 | *f++ = '*'; | 4649 | *f++ = '*'; |
| 4661 | if (float_conversion) | 4650 | if (! (float_conversion || conversion == 'c')) |
| 4662 | { | ||
| 4663 | if (INT_AS_LDBL) | ||
| 4664 | { | ||
| 4665 | *f = 'L'; | ||
| 4666 | f += FIXNUMP (arg); | ||
| 4667 | } | ||
| 4668 | } | ||
| 4669 | else if (conversion != 'c') | ||
| 4670 | { | 4651 | { |
| 4671 | memcpy (f, pMd, pMlen); | 4652 | memcpy (f, pMd, pMlen); |
| 4672 | f += pMlen; | 4653 | f += pMlen; |
| @@ -4694,17 +4675,66 @@ styled_format (ptrdiff_t nargs, Lisp_Object *args, bool message) | |||
| 4694 | ptrdiff_t sprintf_bytes; | 4675 | ptrdiff_t sprintf_bytes; |
| 4695 | if (float_conversion) | 4676 | if (float_conversion) |
| 4696 | { | 4677 | { |
| 4697 | if (INT_AS_LDBL && FIXNUMP (arg)) | 4678 | /* Format as a long double if the arg is an integer |
| 4679 | that would lose less information than when formatting | ||
| 4680 | it as a double. Otherwise, format as a double; | ||
| 4681 | this is likely to be faster and better-tested. */ | ||
| 4682 | |||
| 4683 | bool format_as_long_double = false; | ||
| 4684 | double darg; | ||
| 4685 | long double ldarg; | ||
| 4686 | |||
| 4687 | if (FLOATP (arg)) | ||
| 4688 | darg = XFLOAT_DATA (arg); | ||
| 4689 | else | ||
| 4698 | { | 4690 | { |
| 4699 | /* Although long double may have a rounding error if | 4691 | bool format_bignum_as_double = false; |
| 4700 | DIG_BITS_LBOUND * LDBL_MANT_DIG < FIXNUM_BITS - 1, | 4692 | if (LDBL_MANT_DIG <= DBL_MANT_DIG) |
| 4701 | it is more accurate than plain 'double'. */ | 4693 | { |
| 4702 | long double x = XFIXNUM (arg); | 4694 | if (FIXNUMP (arg)) |
| 4703 | sprintf_bytes = sprintf (sprintf_buf, convspec, prec, x); | 4695 | darg = XFIXNUM (arg); |
| 4696 | else | ||
| 4697 | format_bignum_as_double = true; | ||
| 4698 | } | ||
| 4699 | else | ||
| 4700 | { | ||
| 4701 | if (FIXNUMP (arg)) | ||
| 4702 | ldarg = XFIXNUM (arg); | ||
| 4703 | else | ||
| 4704 | { | ||
| 4705 | intmax_t iarg = bignum_to_intmax (arg); | ||
| 4706 | if (iarg != 0) | ||
| 4707 | ldarg = iarg; | ||
| 4708 | else | ||
| 4709 | { | ||
| 4710 | uintmax_t uarg = bignum_to_uintmax (arg); | ||
| 4711 | if (uarg != 0) | ||
| 4712 | ldarg = uarg; | ||
| 4713 | else | ||
| 4714 | format_bignum_as_double = true; | ||
| 4715 | } | ||
| 4716 | } | ||
| 4717 | if (!format_bignum_as_double) | ||
| 4718 | { | ||
| 4719 | darg = ldarg; | ||
| 4720 | format_as_long_double = darg != ldarg; | ||
| 4721 | } | ||
| 4722 | } | ||
| 4723 | if (format_bignum_as_double) | ||
| 4724 | darg = bignum_to_double (arg); | ||
| 4725 | } | ||
| 4726 | |||
| 4727 | if (format_as_long_double) | ||
| 4728 | { | ||
| 4729 | f[-1] = 'L'; | ||
| 4730 | *f++ = conversion; | ||
| 4731 | *f = '\0'; | ||
| 4732 | sprintf_bytes = sprintf (sprintf_buf, convspec, prec, | ||
| 4733 | ldarg); | ||
| 4704 | } | 4734 | } |
| 4705 | else | 4735 | else |
| 4706 | sprintf_bytes = sprintf (sprintf_buf, convspec, prec, | 4736 | sprintf_bytes = sprintf (sprintf_buf, convspec, prec, |
| 4707 | XFLOATINT (arg)); | 4737 | darg); |
| 4708 | } | 4738 | } |
| 4709 | else if (conversion == 'c') | 4739 | else if (conversion == 'c') |
| 4710 | { | 4740 | { |
| @@ -4740,7 +4770,7 @@ styled_format (ptrdiff_t nargs, Lisp_Object *args, bool message) | |||
| 4740 | { | 4770 | { |
| 4741 | uprintmax_t x; | 4771 | uprintmax_t x; |
| 4742 | bool negative; | 4772 | bool negative; |
| 4743 | if (INTEGERP (arg)) | 4773 | if (FIXNUMP (arg)) |
| 4744 | { | 4774 | { |
| 4745 | if (binary_as_unsigned) | 4775 | if (binary_as_unsigned) |
| 4746 | { | 4776 | { |