aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorPaul Eggert2018-08-28 11:59:21 -0700
committerPaul Eggert2018-08-28 12:00:20 -0700
commitd4586b7a9cea6aac7d710d59fd29ce1b9a705449 (patch)
tree0b96e557d181323d3c018301148753603da61913 /src
parent5cb057a854a848be85dc2b81f6497964f5a93dbb (diff)
downloademacs-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')
-rw-r--r--src/editfns.c88
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 {