Skip to content

Commit 3370cce

Browse files
committed
Issue 5780: Fix test_float failures for legacy style float repr.
1 parent 7efad9e commit 3370cce

File tree

2 files changed

+65
-12
lines changed

2 files changed

+65
-12
lines changed

Misc/NEWS

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,7 @@ Core and Builtins
2020
- Implement PEP 378, Format Specifier for Thousands Separator, for
2121
floats.
2222

23-
- The repr function switches to exponential notation at 1e16, not 1e17
24-
as it did before. This change applies to both 'short' and legacy
25-
float repr styles. For the new repr style, it avoids misleading
26-
output in some cases: an example is repr(2e16+8), which gives
27-
'2.000000000000001e+16'; without this change it would have produced
28-
'20000000000000010.0' instead.
29-
30-
- Similarly, the str function switches to exponential notation at
23+
- The str function switches to exponential notation at
3124
1e11, not 1e12. This avoids printing 13 significant digits in
3225
situations where only 12 of them are correct. Example problem
3326
value: str(1e11 + 0.5). (This minor issue has existed in 2.x for a
@@ -44,6 +37,9 @@ Core and Builtins
4437
finite float x, repr(x) now outputs a string based on the shortest
4538
sequence of decimal digits that rounds to x. Previous behaviour was
4639
to output 17 significant digits and then strip trailing zeros.
40+
Another minor difference is that the new repr switches to
41+
exponential notation at 1e16 instead of the previous 1e17; this
42+
avoids misleading output in some cases.
4743

4844
There's a new sys attribute sys.float_repr_style, which takes
4945
the value 'short' to indicate that we're using short float repr,

Python/pystrtod.c

Lines changed: 61 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -485,6 +485,50 @@ PyOS_ascii_formatd(char *buffer,
485485

486486
/* The fallback code to use if _Py_dg_dtoa is not available. */
487487

488+
/* Remove trailing zeros after the decimal point from a numeric string; also
489+
remove the decimal point if all digits following it are zero. The numeric
490+
string must end in '\0', and should not have any leading or trailing
491+
whitespace. Assumes that the decimal point is '.'. */
492+
Py_LOCAL_INLINE(void)
493+
remove_trailing_zeros(char *buffer)
494+
{
495+
char *old_fraction_end, *new_fraction_end, *end, *p;
496+
497+
p = buffer;
498+
if (*p == '-' || *p == '+')
499+
/* Skip leading sign, if present */
500+
++p;
501+
while (isdigit(Py_CHARMASK(*p)))
502+
++p;
503+
504+
/* if there's no decimal point there's nothing to do */
505+
if (*p++ != '.')
506+
return;
507+
508+
/* scan any digits after the point */
509+
while (isdigit(Py_CHARMASK(*p)))
510+
++p;
511+
old_fraction_end = p;
512+
513+
/* scan up to ending '\0' */
514+
while (*p != '\0')
515+
p++;
516+
/* +1 to make sure that we move the null byte as well */
517+
end = p+1;
518+
519+
/* scan back from fraction_end, looking for removable zeros */
520+
p = old_fraction_end;
521+
while (*(p-1) == '0')
522+
--p;
523+
/* and remove point if we've got that far */
524+
if (*(p-1) == '.')
525+
--p;
526+
new_fraction_end = p;
527+
528+
memmove(new_fraction_end, old_fraction_end, end-old_fraction_end);
529+
}
530+
531+
488532
PyAPI_FUNC(char *) PyOS_double_to_string(double val,
489533
char format_code,
490534
int precision,
@@ -498,6 +542,7 @@ PyAPI_FUNC(char *) PyOS_double_to_string(double val,
498542
char *p;
499543
int t;
500544
int upper = 0;
545+
int strip_trailing_zeros = 0;
501546

502547
/* Validate format_code, and map upper and lower case */
503548
switch (format_code) {
@@ -532,8 +577,17 @@ PyAPI_FUNC(char *) PyOS_double_to_string(double val,
532577
PyErr_BadInternalCall();
533578
return NULL;
534579
}
535-
precision = 12;
536-
format_code = 'g';
580+
/* switch to exponential notation at 1e11, or 1e12 if we're
581+
not adding a .0 */
582+
if (fabs(val) >= (flags & Py_DTSF_ADD_DOT_0 ? 1e11 : 1e12)) {
583+
precision = 11;
584+
format_code = 'e';
585+
strip_trailing_zeros = 1;
586+
}
587+
else {
588+
precision = 12;
589+
format_code = 'g';
590+
}
537591
break;
538592
default:
539593
PyErr_BadInternalCall();
@@ -554,11 +608,14 @@ PyAPI_FUNC(char *) PyOS_double_to_string(double val,
554608
t = Py_DTST_FINITE;
555609

556610

557-
if (flags & Py_DTSF_ADD_DOT_0)
611+
if ((flags & Py_DTSF_ADD_DOT_0) && (format_code != 'e'))
558612
format_code = 'Z';
559613

560614
PyOS_snprintf(format, 32, "%%%s.%i%c", (flags & Py_DTSF_ALT ? "#" : ""), precision, format_code);
561615
PyOS_ascii_formatd(buf, sizeof(buf), format, val);
616+
/* remove trailing zeros if necessary */
617+
if (strip_trailing_zeros)
618+
remove_trailing_zeros(buf);
562619
}
563620

564621
len = strlen(buf);
@@ -671,7 +728,7 @@ format_float_short(double d, char format_code,
671728
assert(digits_end != NULL && digits_end >= digits);
672729
digits_len = digits_end - digits;
673730

674-
if (digits_len && !isdigit(digits[0])) {
731+
if (digits_len && !isdigit(Py_CHARMASK(digits[0]))) {
675732
/* Infinities and nans here; adapt Gay's output,
676733
so convert Infinity to inf and NaN to nan, and
677734
ignore sign of nan. Then return. */

0 commit comments

Comments
 (0)