@@ -354,23 +354,72 @@ ensure_minimum_exponent_length(char* buffer, size_t buf_size)
354
354
}
355
355
}
356
356
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 '.'. */
360
361
Py_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 )
362
409
{
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 ;
365
412
366
413
/* search for the first non-digit character */
367
414
char * p = buffer ;
368
415
if (* p == '-' || * p == '+' )
369
416
/* Skip leading sign, if present. I think this could only
370
417
ever be '-', but it can't hurt to check for both. */
371
418
++ p ;
419
+ digits_start = p ;
372
420
while (* p && Py_ISDIGIT (* p ))
373
421
++ p ;
422
+ digit_count = Py_SAFE_DOWNCAST (p - digits_start , Py_ssize_t , int );
374
423
375
424
if (* p == '.' ) {
376
425
if (Py_ISDIGIT (* (p + 1 ))) {
@@ -380,15 +429,31 @@ ensure_decimal_point(char* buffer, size_t buf_size)
380
429
else {
381
430
/* We have a decimal point, but no following
382
431
digit. Insert a zero after the decimal. */
432
+ /* can't ever get here via PyOS_double_to_string */
433
+ assert (precision == -1 );
383
434
++ p ;
384
435
chars_to_insert = "0" ;
385
436
insert_count = 1 ;
386
437
}
387
438
}
388
439
else if (!(* p == 'e' || * p == 'E' )) {
389
440
/* 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
+ }
392
457
}
393
458
if (insert_count ) {
394
459
size_t buf_len = strlen (buffer );
@@ -403,6 +468,30 @@ ensure_decimal_point(char* buffer, size_t buf_size)
403
468
memcpy (p , chars_to_insert , insert_count );
404
469
}
405
470
}
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 ;
406
495
}
407
496
408
497
/* see FORMATBUFLEN in unicodeobject.c */
@@ -425,12 +514,14 @@ ensure_decimal_point(char* buffer, size_t buf_size)
425
514
* at least one digit after the decimal.
426
515
*
427
516
* Return value: The pointer to the buffer with the converted string.
517
+ * On failure returns NULL but does not set any Python exception.
428
518
**/
429
519
char *
430
520
_PyOS_ascii_formatd (char * buffer ,
431
521
size_t buf_size ,
432
522
const char * format ,
433
- double d )
523
+ double d ,
524
+ int precision )
434
525
{
435
526
char format_char ;
436
527
size_t format_len = strlen (format );
@@ -495,9 +586,12 @@ _PyOS_ascii_formatd(char *buffer,
495
586
ensure_minimum_exponent_length (buffer , buf_size );
496
587
497
588
/* 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. */
499
593
if (format_char == 'Z' )
500
- ensure_decimal_point (buffer , buf_size );
594
+ buffer = ensure_decimal_point (buffer , buf_size , precision );
501
595
502
596
return buffer ;
503
597
}
@@ -513,57 +607,13 @@ PyOS_ascii_formatd(char *buffer,
513
607
"use PyOS_double_to_string instead" , 1 ) < 0 )
514
608
return NULL ;
515
609
516
- return _PyOS_ascii_formatd (buffer , buf_size , format , d );
610
+ return _PyOS_ascii_formatd (buffer , buf_size , format , d , -1 );
517
611
}
518
612
519
613
#ifdef PY_NO_SHORT_FLOAT_REPR
520
614
521
615
/* The fallback code to use if _Py_dg_dtoa is not available. */
522
616
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
-
567
617
PyAPI_FUNC (char * ) PyOS_double_to_string (double val ,
568
618
char format_code ,
569
619
int precision ,
@@ -577,7 +627,6 @@ PyAPI_FUNC(char *) PyOS_double_to_string(double val,
577
627
char * p ;
578
628
int t ;
579
629
int upper = 0 ;
580
- int strip_trailing_zeros = 0 ;
581
630
582
631
/* Validate format_code, and map upper and lower case */
583
632
switch (format_code ) {
@@ -612,17 +661,8 @@ PyAPI_FUNC(char *) PyOS_double_to_string(double val,
612
661
PyErr_BadInternalCall ();
613
662
return NULL ;
614
663
}
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' ;
626
666
break ;
627
667
default :
628
668
PyErr_BadInternalCall ();
@@ -641,18 +681,13 @@ PyAPI_FUNC(char *) PyOS_double_to_string(double val,
641
681
t = Py_DTST_INFINITE ;
642
682
} else {
643
683
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 )
647
685
format_code = 'Z' ;
648
686
649
687
PyOS_snprintf (format , sizeof (format ), "%%%s.%i%c" ,
650
688
(flags & Py_DTSF_ALT ? "#" : "" ), precision ,
651
689
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 );
656
691
}
657
692
658
693
len = strlen (buf );
@@ -678,7 +713,7 @@ PyAPI_FUNC(char *) PyOS_double_to_string(double val,
678
713
/* Convert to upper case. */
679
714
char * p1 ;
680
715
for (p1 = p ; * p1 ; p1 ++ )
681
- * p1 = toupper (* p1 );
716
+ * p1 = Py_TOUPPER (* p1 );
682
717
}
683
718
684
719
if (type )
@@ -766,7 +801,7 @@ format_float_short(double d, char format_code,
766
801
assert (digits_end != NULL && digits_end >= digits );
767
802
digits_len = digits_end - digits ;
768
803
769
- if (digits_len && !isdigit ( Py_CHARMASK ( digits [0 ]) )) {
804
+ if (digits_len && !Py_ISDIGIT ( digits [0 ])) {
770
805
/* Infinities and nans here; adapt Gay's output,
771
806
so convert Infinity to inf and NaN to nan, and
772
807
ignore sign of nan. Then return. */
@@ -851,7 +886,8 @@ format_float_short(double d, char format_code,
851
886
vdigits_end = decpt + precision ;
852
887
break ;
853
888
case 'g' :
854
- if (decpt <= -4 || decpt > precision )
889
+ if (decpt <= -4 || decpt >
890
+ (add_dot_0_if_integer ? precision - 1 : precision ))
855
891
use_exp = 1 ;
856
892
if (use_alt_formatting )
857
893
vdigits_end = precision ;
0 commit comments