@@ -354,23 +354,72 @@ ensure_minimum_exponent_length(char* buffer, size_t buf_size)
354354 }
355355}
356356
357- /* Ensure that buffer has a decimal point in it. The decimal point will not
358- be in the current locale, it will always be '.'. Don't add a decimal if an
359- exponent is present. */
357+ /* Remove trailing zeros after the decimal point from a numeric string; also
358+ remove the decimal point if all digits following it are zero. The numeric
359+ string must end in '\0', and should not have any leading or trailing
360+ whitespace. Assumes that the decimal point is '.'. */
360361Py_LOCAL_INLINE (void )
361- ensure_decimal_point (char * buffer , size_t buf_size )
362+ remove_trailing_zeros (char * buffer )
363+ {
364+ char * old_fraction_end , * new_fraction_end , * end , * p ;
365+
366+ p = buffer ;
367+ if (* p == '-' || * p == '+' )
368+ /* Skip leading sign, if present */
369+ ++ p ;
370+ while (Py_ISDIGIT (* p ))
371+ ++ p ;
372+
373+ /* if there's no decimal point there's nothing to do */
374+ if (* p ++ != '.' )
375+ return ;
376+
377+ /* scan any digits after the point */
378+ while (Py_ISDIGIT (* p ))
379+ ++ p ;
380+ old_fraction_end = p ;
381+
382+ /* scan up to ending '\0' */
383+ while (* p != '\0' )
384+ p ++ ;
385+ /* +1 to make sure that we move the null byte as well */
386+ end = p + 1 ;
387+
388+ /* scan back from fraction_end, looking for removable zeros */
389+ p = old_fraction_end ;
390+ while (* (p - 1 ) == '0' )
391+ -- p ;
392+ /* and remove point if we've got that far */
393+ if (* (p - 1 ) == '.' )
394+ -- p ;
395+ new_fraction_end = p ;
396+
397+ memmove (new_fraction_end , old_fraction_end , end - old_fraction_end );
398+ }
399+
400+ /* Ensure that buffer has a decimal point in it. The decimal point will not
401+ be in the current locale, it will always be '.'. Don't add a decimal point
402+ if an exponent is present. Also, convert to exponential notation where
403+ adding a '.0' would produce too many significant digits (see issue 5864).
404+
405+ Returns a pointer to the fixed buffer, or NULL on failure.
406+ */
407+ Py_LOCAL_INLINE (char * )
408+ ensure_decimal_point (char * buffer , size_t buf_size , int precision )
362409{
363- int insert_count = 0 ;
364- char * chars_to_insert ;
410+ int digit_count , insert_count = 0 , convert_to_exp = 0 ;
411+ char * chars_to_insert , * digits_start ;
365412
366413 /* search for the first non-digit character */
367414 char * p = buffer ;
368415 if (* p == '-' || * p == '+' )
369416 /* Skip leading sign, if present. I think this could only
370417 ever be '-', but it can't hurt to check for both. */
371418 ++ p ;
419+ digits_start = p ;
372420 while (* p && Py_ISDIGIT (* p ))
373421 ++ p ;
422+ digit_count = Py_SAFE_DOWNCAST (p - digits_start , Py_ssize_t , int );
374423
375424 if (* p == '.' ) {
376425 if (Py_ISDIGIT (* (p + 1 ))) {
@@ -380,15 +429,31 @@ ensure_decimal_point(char* buffer, size_t buf_size)
380429 else {
381430 /* We have a decimal point, but no following
382431 digit. Insert a zero after the decimal. */
432+ /* can't ever get here via PyOS_double_to_string */
433+ assert (precision == -1 );
383434 ++ p ;
384435 chars_to_insert = "0" ;
385436 insert_count = 1 ;
386437 }
387438 }
388439 else if (!(* p == 'e' || * p == 'E' )) {
389440 /* Don't add ".0" if we have an exponent. */
390- chars_to_insert = ".0" ;
391- insert_count = 2 ;
441+ if (digit_count == precision ) {
442+ /* issue 5864: don't add a trailing .0 in the case
443+ where the '%g'-formatted result already has as many
444+ significant digits as were requested. Switch to
445+ exponential notation instead. */
446+ convert_to_exp = 1 ;
447+ /* no exponent, no point, and we shouldn't land here
448+ for infs and nans, so we must be at the end of the
449+ string. */
450+ assert (* p == '\0' );
451+ }
452+ else {
453+ assert (precision == -1 || digit_count < precision );
454+ chars_to_insert = ".0" ;
455+ insert_count = 2 ;
456+ }
392457 }
393458 if (insert_count ) {
394459 size_t buf_len = strlen (buffer );
@@ -403,6 +468,30 @@ ensure_decimal_point(char* buffer, size_t buf_size)
403468 memcpy (p , chars_to_insert , insert_count );
404469 }
405470 }
471+ if (convert_to_exp ) {
472+ int written ;
473+ size_t buf_avail ;
474+ p = digits_start ;
475+ /* insert decimal point */
476+ assert (digit_count >= 1 );
477+ memmove (p + 2 , p + 1 , digit_count ); /* safe, but overwrites nul */
478+ p [1 ] = '.' ;
479+ p += digit_count + 1 ;
480+ assert (p <= buf_size + buffer );
481+ buf_avail = buf_size + buffer - p ;
482+ if (buf_avail == 0 )
483+ return NULL ;
484+ /* Add exponent. It's okay to use lower case 'e': we only
485+ arrive here as a result of using the empty format code or
486+ repr/str builtins and those never want an upper case 'E' */
487+ written = PyOS_snprintf (p , buf_avail , "e%+.02d" , digit_count - 1 );
488+ if (!(0 <= written &&
489+ written < Py_SAFE_DOWNCAST (buf_avail , size_t , int )))
490+ /* output truncated, or something else bad happened */
491+ return NULL ;
492+ remove_trailing_zeros (buffer );
493+ }
494+ return buffer ;
406495}
407496
408497/* see FORMATBUFLEN in unicodeobject.c */
@@ -425,12 +514,14 @@ ensure_decimal_point(char* buffer, size_t buf_size)
425514 * at least one digit after the decimal.
426515 *
427516 * Return value: The pointer to the buffer with the converted string.
517+ * On failure returns NULL but does not set any Python exception.
428518 **/
429519char *
430520_PyOS_ascii_formatd (char * buffer ,
431521 size_t buf_size ,
432522 const char * format ,
433- double d )
523+ double d ,
524+ int precision )
434525{
435526 char format_char ;
436527 size_t format_len = strlen (format );
@@ -495,9 +586,12 @@ _PyOS_ascii_formatd(char *buffer,
495586 ensure_minimum_exponent_length (buffer , buf_size );
496587
497588 /* If format_char is 'Z', make sure we have at least one character
498- after the decimal point (and make sure we have a decimal point). */
589+ after the decimal point (and make sure we have a decimal point);
590+ also switch to exponential notation in some edge cases where the
591+ extra character would produce more significant digits that we
592+ really want. */
499593 if (format_char == 'Z' )
500- ensure_decimal_point (buffer , buf_size );
594+ buffer = ensure_decimal_point (buffer , buf_size , precision );
501595
502596 return buffer ;
503597}
@@ -513,57 +607,13 @@ PyOS_ascii_formatd(char *buffer,
513607 "use PyOS_double_to_string instead" , 1 ) < 0 )
514608 return NULL ;
515609
516- return _PyOS_ascii_formatd (buffer , buf_size , format , d );
610+ return _PyOS_ascii_formatd (buffer , buf_size , format , d , -1 );
517611}
518612
519613#ifdef PY_NO_SHORT_FLOAT_REPR
520614
521615/* The fallback code to use if _Py_dg_dtoa is not available. */
522616
523- /* Remove trailing zeros after the decimal point from a numeric string; also
524- remove the decimal point if all digits following it are zero. The numeric
525- string must end in '\0', and should not have any leading or trailing
526- whitespace. Assumes that the decimal point is '.'. */
527- Py_LOCAL_INLINE (void )
528- remove_trailing_zeros (char * buffer )
529- {
530- char * old_fraction_end , * new_fraction_end , * end , * p ;
531-
532- p = buffer ;
533- if (* p == '-' || * p == '+' )
534- /* Skip leading sign, if present */
535- ++ p ;
536- while (isdigit (Py_CHARMASK (* p )))
537- ++ p ;
538-
539- /* if there's no decimal point there's nothing to do */
540- if (* p ++ != '.' )
541- return ;
542-
543- /* scan any digits after the point */
544- while (isdigit (Py_CHARMASK (* p )))
545- ++ p ;
546- old_fraction_end = p ;
547-
548- /* scan up to ending '\0' */
549- while (* p != '\0' )
550- p ++ ;
551- /* +1 to make sure that we move the null byte as well */
552- end = p + 1 ;
553-
554- /* scan back from fraction_end, looking for removable zeros */
555- p = old_fraction_end ;
556- while (* (p - 1 ) == '0' )
557- -- p ;
558- /* and remove point if we've got that far */
559- if (* (p - 1 ) == '.' )
560- -- p ;
561- new_fraction_end = p ;
562-
563- memmove (new_fraction_end , old_fraction_end , end - old_fraction_end );
564- }
565-
566-
567617PyAPI_FUNC (char * ) PyOS_double_to_string (double val ,
568618 char format_code ,
569619 int precision ,
@@ -577,7 +627,6 @@ PyAPI_FUNC(char *) PyOS_double_to_string(double val,
577627 char * p ;
578628 int t ;
579629 int upper = 0 ;
580- int strip_trailing_zeros = 0 ;
581630
582631 /* Validate format_code, and map upper and lower case */
583632 switch (format_code ) {
@@ -612,17 +661,8 @@ PyAPI_FUNC(char *) PyOS_double_to_string(double val,
612661 PyErr_BadInternalCall ();
613662 return NULL ;
614663 }
615- /* switch to exponential notation at 1e11, or 1e12 if we're
616- not adding a .0 */
617- if (fabs (val ) >= (flags & Py_DTSF_ADD_DOT_0 ? 1e11 : 1e12 )) {
618- precision = 11 ;
619- format_code = 'e' ;
620- strip_trailing_zeros = 1 ;
621- }
622- else {
623- precision = 12 ;
624- format_code = 'g' ;
625- }
664+ precision = 12 ;
665+ format_code = 'g' ;
626666 break ;
627667 default :
628668 PyErr_BadInternalCall ();
@@ -641,18 +681,13 @@ PyAPI_FUNC(char *) PyOS_double_to_string(double val,
641681 t = Py_DTST_INFINITE ;
642682 } else {
643683 t = Py_DTST_FINITE ;
644-
645-
646- if ((flags & Py_DTSF_ADD_DOT_0 ) && (format_code != 'e' ))
684+ if (flags & Py_DTSF_ADD_DOT_0 )
647685 format_code = 'Z' ;
648686
649687 PyOS_snprintf (format , sizeof (format ), "%%%s.%i%c" ,
650688 (flags & Py_DTSF_ALT ? "#" : "" ), precision ,
651689 format_code );
652- _PyOS_ascii_formatd (buf , sizeof (buf ), format , val );
653- /* remove trailing zeros if necessary */
654- if (strip_trailing_zeros )
655- remove_trailing_zeros (buf );
690+ _PyOS_ascii_formatd (buf , sizeof (buf ), format , val , precision );
656691 }
657692
658693 len = strlen (buf );
@@ -678,7 +713,7 @@ PyAPI_FUNC(char *) PyOS_double_to_string(double val,
678713 /* Convert to upper case. */
679714 char * p1 ;
680715 for (p1 = p ; * p1 ; p1 ++ )
681- * p1 = toupper (* p1 );
716+ * p1 = Py_TOUPPER (* p1 );
682717 }
683718
684719 if (type )
@@ -766,7 +801,7 @@ format_float_short(double d, char format_code,
766801 assert (digits_end != NULL && digits_end >= digits );
767802 digits_len = digits_end - digits ;
768803
769- if (digits_len && !isdigit ( Py_CHARMASK ( digits [0 ]) )) {
804+ if (digits_len && !Py_ISDIGIT ( digits [0 ])) {
770805 /* Infinities and nans here; adapt Gay's output,
771806 so convert Infinity to inf and NaN to nan, and
772807 ignore sign of nan. Then return. */
@@ -851,7 +886,8 @@ format_float_short(double d, char format_code,
851886 vdigits_end = decpt + precision ;
852887 break ;
853888 case 'g' :
854- if (decpt <= -4 || decpt > precision )
889+ if (decpt <= -4 || decpt >
890+ (add_dot_0_if_integer ? precision - 1 : precision ))
855891 use_exp = 1 ;
856892 if (use_alt_formatting )
857893 vdigits_end = precision ;
0 commit comments