@@ -135,6 +135,7 @@ typedef struct {
135135 Py_ssize_t width ;
136136 enum LocaleType thousands_separators ;
137137 Py_ssize_t precision ;
138+ enum LocaleType frac_thousands_separator ;
138139 Py_UCS4 type ;
139140} InternalFormatSpec ;
140141
@@ -171,6 +172,7 @@ parse_internal_render_format_spec(PyObject *obj,
171172 format -> sign = '\0' ;
172173 format -> width = -1 ;
173174 format -> thousands_separators = LT_NO_LOCALE ;
175+ format -> frac_thousands_separator = LT_NO_LOCALE ;
174176 format -> precision = -1 ;
175177 format -> type = default_type ;
176178
@@ -260,7 +262,16 @@ parse_internal_render_format_spec(PyObject *obj,
260262 /* Overflow error. Exception already set. */
261263 return 0 ;
262264
263- /* Not having a precision after a dot is an error. */
265+ if (end - pos && READ_spec (pos ) == '_' ) {
266+ if (consumed == 0 ) {
267+ format -> precision = -1 ;
268+ }
269+ format -> frac_thousands_separator = LT_UNDERSCORE_LOCALE ;
270+ ++ pos ;
271+ ++ consumed ;
272+ }
273+
274+ /* Not having a precision or underscore after a dot is an error. */
264275 if (consumed == 0 ) {
265276 PyErr_Format (PyExc_ValueError ,
266277 "Format specifier missing precision" );
@@ -402,6 +413,7 @@ fill_padding(_PyUnicodeWriter *writer,
402413typedef struct {
403414 PyObject * decimal_point ;
404415 PyObject * thousands_sep ;
416+ PyObject * frac_thousands_sep ;
405417 const char * grouping ;
406418 char * grouping_buffer ;
407419} LocaleInfo ;
@@ -423,6 +435,8 @@ typedef struct {
423435 Py_ssize_t n_remainder ; /* Digits in decimal and/or exponent part,
424436 excluding the decimal itself, if
425437 present. */
438+ Py_ssize_t n_frac ;
439+ Py_ssize_t n_grouped_frac_digits ;
426440
427441 /* These 2 are not the widths of fields, but are needed by
428442 STRINGLIB_GROUPING. */
@@ -445,24 +459,32 @@ typedef struct {
445459*/
446460static void
447461parse_number (PyObject * s , Py_ssize_t pos , Py_ssize_t end ,
448- Py_ssize_t * n_remainder , int * has_decimal )
462+ Py_ssize_t * n_remainder , Py_ssize_t * n_frac , int * has_decimal )
449463{
450- Py_ssize_t remainder ;
464+ Py_ssize_t frac ;
451465 int kind = PyUnicode_KIND (s );
452466 const void * data = PyUnicode_DATA (s );
453467
454- while (pos < end && Py_ISDIGIT (PyUnicode_READ (kind , data , pos )))
468+ while (pos < end && Py_ISDIGIT (PyUnicode_READ (kind , data , pos ))) {
455469 ++ pos ;
456- remainder = pos ;
470+ }
471+ frac = pos ;
457472
458473 /* Does remainder start with a decimal point? */
459- * has_decimal = pos < end && PyUnicode_READ (kind , data , remainder ) == '.' ;
474+ * has_decimal = pos < end && PyUnicode_READ (kind , data , frac ) == '.' ;
460475
461476 /* Skip the decimal point. */
462- if (* has_decimal )
463- remainder ++ ;
477+ if (* has_decimal ) {
478+ frac ++ ;
479+ pos ++ ;
480+ }
481+
482+ while (pos < end && Py_ISDIGIT (PyUnicode_READ (kind , data , pos ))) {
483+ ++ pos ;
484+ }
464485
465- * n_remainder = end - remainder ;
486+ * n_frac = pos - frac ;
487+ * n_remainder = end - pos ;
466488}
467489
468490/* not all fields of format are used. for example, precision is
@@ -473,18 +495,19 @@ parse_number(PyObject *s, Py_ssize_t pos, Py_ssize_t end,
473495static Py_ssize_t
474496calc_number_widths (NumberFieldWidths * spec , Py_ssize_t n_prefix ,
475497 Py_UCS4 sign_char , Py_ssize_t n_start ,
476- Py_ssize_t n_end , Py_ssize_t n_remainder ,
498+ Py_ssize_t n_end , Py_ssize_t n_remainder , Py_ssize_t n_frac ,
477499 int has_decimal , const LocaleInfo * locale ,
478500 const InternalFormatSpec * format , Py_UCS4 * maxchar )
479501{
480502 Py_ssize_t n_non_digit_non_padding ;
481503 Py_ssize_t n_padding ;
482504
483- spec -> n_digits = n_end - n_start - n_remainder - (has_decimal ?1 :0 );
505+ spec -> n_digits = n_end - n_start - n_frac - n_remainder - (has_decimal ?1 :0 );
484506 spec -> n_lpadding = 0 ;
485507 spec -> n_prefix = n_prefix ;
486508 spec -> n_decimal = has_decimal ? PyUnicode_GET_LENGTH (locale -> decimal_point ) : 0 ;
487509 spec -> n_remainder = n_remainder ;
510+ spec -> n_frac = n_frac ;
488511 spec -> n_spadding = 0 ;
489512 spec -> n_rpadding = 0 ;
490513 spec -> sign = '\0' ;
@@ -557,12 +580,29 @@ calc_number_widths(NumberFieldWidths *spec, Py_ssize_t n_prefix,
557580 * maxchar = Py_MAX (* maxchar , grouping_maxchar );
558581 }
559582
583+ if (spec -> n_frac == 0 ) {
584+ spec -> n_grouped_frac_digits = 0 ;
585+ }
586+ else {
587+ Py_UCS4 grouping_maxchar ;
588+ spec -> n_grouped_frac_digits = _PyUnicode_InsertThousandsGrouping (
589+ NULL , 0 ,
590+ NULL , 0 , spec -> n_frac ,
591+ spec -> n_min_width ,
592+ locale -> grouping , locale -> frac_thousands_sep , & grouping_maxchar );
593+ if (spec -> n_grouped_frac_digits == -1 ) {
594+ return -1 ;
595+ }
596+ * maxchar = Py_MAX (* maxchar , grouping_maxchar );
597+ }
598+
560599 /* Given the desired width and the total of digit and non-digit
561600 space we consume, see if we need any padding. format->width can
562601 be negative (meaning no padding), but this code still works in
563602 that case. */
564603 n_padding = format -> width -
565- (n_non_digit_non_padding + spec -> n_grouped_digits );
604+ (n_non_digit_non_padding + spec -> n_grouped_digits
605+ + spec -> n_grouped_frac_digits );
566606 if (n_padding > 0 ) {
567607 /* Some padding is needed. Determine if it's left, space, or right. */
568608 switch (format -> align ) {
@@ -593,7 +633,8 @@ calc_number_widths(NumberFieldWidths *spec, Py_ssize_t n_prefix,
593633
594634 return spec -> n_lpadding + spec -> n_sign + spec -> n_prefix +
595635 spec -> n_spadding + spec -> n_grouped_digits + spec -> n_decimal +
596- spec -> n_remainder + spec -> n_rpadding ;
636+ spec -> n_grouped_frac_digits + spec -> n_frac + spec -> n_remainder +
637+ spec -> n_rpadding ;
597638}
598639
599640/* Fill in the digit parts of a number's string representation,
@@ -677,6 +718,19 @@ fill_number(_PyUnicodeWriter *writer, const NumberFieldWidths *spec,
677718 d_pos += 1 ;
678719 }
679720
721+ if (spec -> n_frac ) {
722+ r = _PyUnicode_InsertThousandsGrouping (
723+ writer , spec -> n_grouped_frac_digits ,
724+ digits , d_pos , spec -> n_frac , spec -> n_min_width ,
725+ locale -> grouping , locale -> frac_thousands_sep , NULL );
726+ if (r == -1 ) {
727+ return -1 ;
728+ }
729+ assert (r == spec -> n_grouped_frac_digits );
730+ d_pos += spec -> n_frac ;
731+ writer -> pos += spec -> n_grouped_frac_digits ;
732+ }
733+
680734 if (spec -> n_remainder ) {
681735 _PyUnicode_FastCopyCharacters (
682736 writer -> buffer , writer -> pos ,
@@ -701,7 +755,8 @@ static const char no_grouping[1] = {CHAR_MAX};
701755 LT_CURRENT_LOCALE, a hard-coded locale if LT_DEFAULT_LOCALE or
702756 LT_UNDERSCORE_LOCALE/LT_UNDER_FOUR_LOCALE, or none if LT_NO_LOCALE. */
703757static int
704- get_locale_info (enum LocaleType type , LocaleInfo * locale_info )
758+ get_locale_info (enum LocaleType type , enum LocaleType frac_type ,
759+ LocaleInfo * locale_info )
705760{
706761 switch (type ) {
707762 case LT_CURRENT_LOCALE : {
@@ -746,6 +801,15 @@ get_locale_info(enum LocaleType type, LocaleInfo *locale_info)
746801 locale_info -> grouping = no_grouping ;
747802 break ;
748803 }
804+ if (frac_type == LT_UNDERSCORE_LOCALE ) {
805+ locale_info -> frac_thousands_sep = PyUnicode_FromOrdinal ('_' );
806+ if (locale_info -> grouping == no_grouping ) {
807+ locale_info -> grouping = "\3" ;
808+ }
809+ }
810+ else {
811+ locale_info -> frac_thousands_sep = Py_GetConstant (Py_CONSTANT_EMPTY_STR );
812+ }
749813 return 0 ;
750814}
751815
@@ -754,6 +818,7 @@ free_locale_info(LocaleInfo *locale_info)
754818{
755819 Py_XDECREF (locale_info -> decimal_point );
756820 Py_XDECREF (locale_info -> thousands_sep );
821+ Py_XDECREF (locale_info -> frac_thousands_sep );
757822 PyMem_Free (locale_info -> grouping_buffer );
758823}
759824
@@ -1005,13 +1070,13 @@ format_long_internal(PyObject *value, const InternalFormatSpec *format,
10051070
10061071 /* Determine the grouping, separator, and decimal point, if any. */
10071072 if (get_locale_info (format -> type == 'n' ? LT_CURRENT_LOCALE :
1008- format -> thousands_separators ,
1073+ format -> thousands_separators , 0 ,
10091074 & locale ) == -1 )
10101075 goto done ;
10111076
10121077 /* Calculate how much memory we'll need. */
10131078 n_total = calc_number_widths (& spec , n_prefix , sign_char , inumeric_chars ,
1014- inumeric_chars + n_digits , n_remainder , 0 ,
1079+ inumeric_chars + n_digits , n_remainder , 0 , 0 ,
10151080 & locale , format , & maxchar );
10161081 if (n_total == -1 ) {
10171082 goto done ;
@@ -1046,6 +1111,7 @@ format_float_internal(PyObject *value,
10461111 char * buf = NULL ; /* buffer returned from PyOS_double_to_string */
10471112 Py_ssize_t n_digits ;
10481113 Py_ssize_t n_remainder ;
1114+ Py_ssize_t n_frac ;
10491115 Py_ssize_t n_total ;
10501116 int has_decimal ;
10511117 double val ;
@@ -1125,7 +1191,8 @@ format_float_internal(PyObject *value,
11251191 if (format -> sign != '+' && format -> sign != ' '
11261192 && format -> width == -1
11271193 && format -> type != 'n'
1128- && !format -> thousands_separators )
1194+ && !format -> thousands_separators
1195+ && !format -> frac_thousands_separator )
11291196 {
11301197 /* Fast path */
11311198 result = _PyUnicodeWriter_WriteASCIIString (writer , buf , n_digits );
@@ -1151,18 +1218,20 @@ format_float_internal(PyObject *value,
11511218
11521219 /* Determine if we have any "remainder" (after the digits, might include
11531220 decimal or exponent or both (or neither)) */
1154- parse_number (unicode_tmp , index , index + n_digits , & n_remainder , & has_decimal );
1221+ parse_number (unicode_tmp , index , index + n_digits ,
1222+ & n_remainder , & n_frac , & has_decimal );
11551223
11561224 /* Determine the grouping, separator, and decimal point, if any. */
11571225 if (get_locale_info (format -> type == 'n' ? LT_CURRENT_LOCALE :
11581226 format -> thousands_separators ,
1227+ format -> frac_thousands_separator ,
11591228 & locale ) == -1 )
11601229 goto done ;
11611230
11621231 /* Calculate how much memory we'll need. */
11631232 n_total = calc_number_widths (& spec , 0 , sign_char , index ,
1164- index + n_digits , n_remainder , has_decimal ,
1165- & locale , format , & maxchar );
1233+ index + n_digits , n_remainder , n_frac ,
1234+ has_decimal , & locale , format , & maxchar );
11661235 if (n_total == -1 ) {
11671236 goto done ;
11681237 }
@@ -1202,6 +1271,8 @@ format_complex_internal(PyObject *value,
12021271 Py_ssize_t n_im_digits ;
12031272 Py_ssize_t n_re_remainder ;
12041273 Py_ssize_t n_im_remainder ;
1274+ Py_ssize_t n_re_frac ;
1275+ Py_ssize_t n_im_frac ;
12051276 Py_ssize_t n_re_total ;
12061277 Py_ssize_t n_im_total ;
12071278 int re_has_decimal ;
@@ -1330,13 +1401,14 @@ format_complex_internal(PyObject *value,
13301401 /* Determine if we have any "remainder" (after the digits, might include
13311402 decimal or exponent or both (or neither)) */
13321403 parse_number (re_unicode_tmp , i_re , i_re + n_re_digits ,
1333- & n_re_remainder , & re_has_decimal );
1404+ & n_re_remainder , & n_re_frac , & re_has_decimal );
13341405 parse_number (im_unicode_tmp , i_im , i_im + n_im_digits ,
1335- & n_im_remainder , & im_has_decimal );
1406+ & n_im_remainder , & n_im_frac , & im_has_decimal );
13361407
13371408 /* Determine the grouping, separator, and decimal point, if any. */
13381409 if (get_locale_info (format -> type == 'n' ? LT_CURRENT_LOCALE :
13391410 format -> thousands_separators ,
1411+ format -> frac_thousands_separator ,
13401412 & locale ) == -1 )
13411413 goto done ;
13421414
@@ -1349,8 +1421,8 @@ format_complex_internal(PyObject *value,
13491421 /* Calculate how much memory we'll need. */
13501422 n_re_total = calc_number_widths (& re_spec , 0 , re_sign_char ,
13511423 i_re , i_re + n_re_digits , n_re_remainder ,
1352- re_has_decimal , & locale , & tmp_format ,
1353- & maxchar );
1424+ n_re_frac , re_has_decimal , & locale ,
1425+ & tmp_format , & maxchar );
13541426 if (n_re_total == -1 ) {
13551427 goto done ;
13561428 }
@@ -1362,8 +1434,8 @@ format_complex_internal(PyObject *value,
13621434 tmp_format .sign = '+' ;
13631435 n_im_total = calc_number_widths (& im_spec , 0 , im_sign_char ,
13641436 i_im , i_im + n_im_digits , n_im_remainder ,
1365- im_has_decimal , & locale , & tmp_format ,
1366- & maxchar );
1437+ n_im_frac , im_has_decimal , & locale ,
1438+ & tmp_format , & maxchar );
13671439 if (n_im_total == -1 ) {
13681440 goto done ;
13691441 }
0 commit comments