diff options
| author | Paul Eggert | 2017-03-04 23:14:52 -0800 |
|---|---|---|
| committer | Paul Eggert | 2017-03-04 23:18:39 -0800 |
| commit | 44e7ee2e356452139156e8175c46f646835d27ff (patch) | |
| tree | 7b877113c8ad6b9e2d64c560354656f7397b3325 /src | |
| parent | 207de3303076bff1bb392bd407fee0dea892fe40 (diff) | |
| download | emacs-44e7ee2e356452139156e8175c46f646835d27ff.tar.gz emacs-44e7ee2e356452139156e8175c46f646835d27ff.zip | |
Fewer rounding errors with (format "%f" fixnum)
* etc/NEWS: Document this.
* src/editfns.c (styled_format): When formatting integers via a
floating-point format, use long double instead of double
conversion, if long double’s extra precision might help.
Diffstat (limited to 'src')
| -rw-r--r-- | src/editfns.c | 56 |
1 files changed, 43 insertions, 13 deletions
diff --git a/src/editfns.c b/src/editfns.c index db9ad066909..612893c377b 100644 --- a/src/editfns.c +++ b/src/editfns.c | |||
| @@ -4145,6 +4145,9 @@ styled_format (ptrdiff_t nargs, Lisp_Object *args, bool message) | |||
| 4145 | } | 4145 | } |
| 4146 | } | 4146 | } |
| 4147 | 4147 | ||
| 4148 | bool float_conversion | ||
| 4149 | = conversion == 'e' || conversion == 'f' || conversion == 'g'; | ||
| 4150 | |||
| 4148 | if (conversion == 's') | 4151 | if (conversion == 's') |
| 4149 | { | 4152 | { |
| 4150 | /* handle case (precision[n] >= 0) */ | 4153 | /* handle case (precision[n] >= 0) */ |
| @@ -4229,8 +4232,7 @@ styled_format (ptrdiff_t nargs, Lisp_Object *args, bool message) | |||
| 4229 | } | 4232 | } |
| 4230 | } | 4233 | } |
| 4231 | else if (! (conversion == 'c' || conversion == 'd' | 4234 | else if (! (conversion == 'c' || conversion == 'd' |
| 4232 | || conversion == 'e' || conversion == 'f' | 4235 | || float_conversion || conversion == 'i' |
| 4233 | || conversion == 'g' || conversion == 'i' | ||
| 4234 | || conversion == 'o' || conversion == 'x' | 4236 | || conversion == 'o' || conversion == 'x' |
| 4235 | || conversion == 'X')) | 4237 | || conversion == 'X')) |
| 4236 | error ("Invalid format operation %%%c", | 4238 | error ("Invalid format operation %%%c", |
| @@ -4242,11 +4244,22 @@ styled_format (ptrdiff_t nargs, Lisp_Object *args, bool message) | |||
| 4242 | { | 4244 | { |
| 4243 | enum | 4245 | enum |
| 4244 | { | 4246 | { |
| 4247 | /* Lower bound on the number of bits per | ||
| 4248 | base-FLT_RADIX digit. */ | ||
| 4249 | DIG_BITS_LBOUND = FLT_RADIX < 16 ? 1 : 4, | ||
| 4250 | |||
| 4251 | /* 1 if integers should be formatted as long doubles, | ||
| 4252 | because they may be so large that there is a rounding | ||
| 4253 | error when converting them to double, and long doubles | ||
| 4254 | are wider than doubles. */ | ||
| 4255 | INT_AS_LDBL = (DIG_BITS_LBOUND * DBL_MANT_DIG < FIXNUM_BITS - 1 | ||
| 4256 | && DBL_MANT_DIG < LDBL_MANT_DIG), | ||
| 4257 | |||
| 4245 | /* Maximum precision for a %f conversion such that the | 4258 | /* Maximum precision for a %f conversion such that the |
| 4246 | trailing output digit might be nonzero. Any precision | 4259 | trailing output digit might be nonzero. Any precision |
| 4247 | larger than this will not yield useful information. */ | 4260 | larger than this will not yield useful information. */ |
| 4248 | USEFUL_PRECISION_MAX = | 4261 | USEFUL_PRECISION_MAX = |
| 4249 | ((1 - DBL_MIN_EXP) | 4262 | ((1 - LDBL_MIN_EXP) |
| 4250 | * (FLT_RADIX == 2 || FLT_RADIX == 10 ? 1 | 4263 | * (FLT_RADIX == 2 || FLT_RADIX == 10 ? 1 |
| 4251 | : FLT_RADIX == 16 ? 4 | 4264 | : FLT_RADIX == 16 ? 4 |
| 4252 | : -1)), | 4265 | : -1)), |
| @@ -4255,7 +4268,7 @@ styled_format (ptrdiff_t nargs, Lisp_Object *args, bool message) | |||
| 4255 | precision is no more than USEFUL_PRECISION_MAX. | 4268 | precision is no more than USEFUL_PRECISION_MAX. |
| 4256 | On all practical hosts, %f is the worst case. */ | 4269 | On all practical hosts, %f is the worst case. */ |
| 4257 | SPRINTF_BUFSIZE = | 4270 | SPRINTF_BUFSIZE = |
| 4258 | sizeof "-." + (DBL_MAX_10_EXP + 1) + USEFUL_PRECISION_MAX, | 4271 | sizeof "-." + (LDBL_MAX_10_EXP + 1) + USEFUL_PRECISION_MAX, |
| 4259 | 4272 | ||
| 4260 | /* Length of pM (that is, of pMd without the | 4273 | /* Length of pM (that is, of pMd without the |
| 4261 | trailing "d"). */ | 4274 | trailing "d"). */ |
| @@ -4269,9 +4282,10 @@ styled_format (ptrdiff_t nargs, Lisp_Object *args, bool message) | |||
| 4269 | 4282 | ||
| 4270 | /* Create the copy of the conversion specification, with | 4283 | /* Create the copy of the conversion specification, with |
| 4271 | any width and precision removed, with ".*" inserted, | 4284 | any width and precision removed, with ".*" inserted, |
| 4285 | with "L" possibly inserted for floating-point formats, | ||
| 4272 | and with pM inserted for integer formats. | 4286 | and with pM inserted for integer formats. |
| 4273 | At most two flags F can be specified at once. */ | 4287 | At most two flags F can be specified at once. */ |
| 4274 | char convspec[sizeof "%FF.*d" + pMlen]; | 4288 | char convspec[sizeof "%FF.*d" + max (INT_AS_LDBL, pMlen)]; |
| 4275 | { | 4289 | { |
| 4276 | char *f = convspec; | 4290 | char *f = convspec; |
| 4277 | *f++ = '%'; | 4291 | *f++ = '%'; |
| @@ -4281,9 +4295,15 @@ styled_format (ptrdiff_t nargs, Lisp_Object *args, bool message) | |||
| 4281 | *f = '#'; f += sharp_flag; | 4295 | *f = '#'; f += sharp_flag; |
| 4282 | *f++ = '.'; | 4296 | *f++ = '.'; |
| 4283 | *f++ = '*'; | 4297 | *f++ = '*'; |
| 4284 | if (conversion == 'd' || conversion == 'i' | 4298 | if (float_conversion) |
| 4285 | || conversion == 'o' || conversion == 'x' | 4299 | { |
| 4286 | || conversion == 'X') | 4300 | if (INT_AS_LDBL) |
| 4301 | { | ||
| 4302 | *f = 'L'; | ||
| 4303 | f += INTEGERP (args[n]); | ||
| 4304 | } | ||
| 4305 | } | ||
| 4306 | else if (conversion != 'c') | ||
| 4287 | { | 4307 | { |
| 4288 | memcpy (f, pMd, pMlen); | 4308 | memcpy (f, pMd, pMlen); |
| 4289 | f += pMlen; | 4309 | f += pMlen; |
| @@ -4310,9 +4330,20 @@ styled_format (ptrdiff_t nargs, Lisp_Object *args, bool message) | |||
| 4310 | not suitable here. */ | 4330 | not suitable here. */ |
| 4311 | char sprintf_buf[SPRINTF_BUFSIZE]; | 4331 | char sprintf_buf[SPRINTF_BUFSIZE]; |
| 4312 | ptrdiff_t sprintf_bytes; | 4332 | ptrdiff_t sprintf_bytes; |
| 4313 | if (conversion == 'e' || conversion == 'f' || conversion == 'g') | 4333 | if (float_conversion) |
| 4314 | sprintf_bytes = sprintf (sprintf_buf, convspec, prec, | 4334 | { |
| 4315 | XFLOATINT (args[n])); | 4335 | if (INT_AS_LDBL && INTEGERP (args[n])) |
| 4336 | { | ||
| 4337 | /* Although long double may have a rounding error if | ||
| 4338 | DIG_BITS_LBOUND * LDBL_MANT_DIG < FIXNUM_BITS - 1, | ||
| 4339 | it is more accurate than plain 'double'. */ | ||
| 4340 | long double x = XINT (args[n]); | ||
| 4341 | sprintf_bytes = sprintf (sprintf_buf, convspec, prec, x); | ||
| 4342 | } | ||
| 4343 | else | ||
| 4344 | sprintf_bytes = sprintf (sprintf_buf, convspec, prec, | ||
| 4345 | XFLOATINT (args[n])); | ||
| 4346 | } | ||
| 4316 | else if (conversion == 'c') | 4347 | else if (conversion == 'c') |
| 4317 | { | 4348 | { |
| 4318 | /* Don't use sprintf here, as it might mishandle prec. */ | 4349 | /* Don't use sprintf here, as it might mishandle prec. */ |
| @@ -4374,8 +4405,7 @@ styled_format (ptrdiff_t nargs, Lisp_Object *args, bool message) | |||
| 4374 | uintmax_t leading_zeros = 0, trailing_zeros = 0; | 4405 | uintmax_t leading_zeros = 0, trailing_zeros = 0; |
| 4375 | if (excess_precision) | 4406 | if (excess_precision) |
| 4376 | { | 4407 | { |
| 4377 | if (conversion == 'e' || conversion == 'f' | 4408 | if (float_conversion) |
| 4378 | || conversion == 'g') | ||
| 4379 | { | 4409 | { |
| 4380 | if ((conversion == 'g' && ! sharp_flag) | 4410 | if ((conversion == 'g' && ! sharp_flag) |
| 4381 | || ! ('0' <= sprintf_buf[sprintf_bytes - 1] | 4411 | || ! ('0' <= sprintf_buf[sprintf_bytes - 1] |