@@ -337,12 +337,24 @@ report_parse_error(UC const *p, parse_error error) {
337337// spans (read only by the rare digit_comp slow path) are not materialized,
338338// which keeps the fat parsed_number_string_t off the hot path. The caller
339339// re-parses with store_spans=true if the slow path is actually reached.
340- template <bool basic_json_fmt, typename UC >
340+ //
341+ // has_separator is a *compile-time* flag (the opposite choice from store_spans,
342+ // and deliberately so): the separator-aware code paths are an opt-in feature
343+ // that the vast majority of callers never enable. Gating them on a template
344+ // parameter means the has_separator==false instantiation -- the default that
345+ // everybody uses -- compiles to exactly the same code as if the feature did not
346+ // exist: no separator comparison ever enters a digit loop, and the SIMD
347+ // eight-digit fast path stays intact. The has_separator==true instantiation is
348+ // cold code that default callers never execute. See parse_number_string_options
349+ // for the runtime->compile-time dispatch.
350+ template <bool basic_json_fmt, bool has_separator, typename UC >
341351fastfloat_really_inline FASTFLOAT_CONSTEXPR20 parsed_number_string_t <UC >
342352parse_number_string (UC const *p, UC const *pend, parse_options_t <UC > options,
343353 bool store_spans = true ) noexcept {
344354 chars_format const fmt = detail::adjust_for_feature_macros (options.format );
345355 UC const decimal_point = options.decimal_point ;
356+ UC const separator = options.digit_separator ;
357+ (void )separator; // unused when has_separator == false
346358
347359 parsed_number_string_t <UC > answer;
348360 answer.valid = false ;
@@ -375,16 +387,19 @@ parse_number_string(UC const *p, UC const *pend, parse_options_t<UC> options,
375387 UC const *const start_digits = p;
376388
377389 uint64_t i = 0 ; // an unsigned int avoids signed overflows (which are bad)
378-
379- // Straight-line unroll of the integer-part scan: most integer parts are
380- // 1-5 digits, so peeling the first iterations eliminates the loop back-edge
381- // for the common case. Semantics are identical to the original `while` loop:
382- // i = 10*i + digit, advancing p.
383- if ((p != pend) && is_integer (*p)) {
384- i = uint64_t (*p - UC (' 0' ));
385- ++p;
390+ int64_t digit_count = 0 ;
391+ // Points at the first actual digit (== start_digits when no separator
392+ // precedes it). Used only by the basic_json leading-zero check.
393+ UC const *first_digit_ptr = start_digits;
394+ (void )first_digit_ptr; // only read in the basic_json_fmt path
395+
396+ FASTFLOAT_IF_CONSTEXPR17 (!has_separator) {
397+ // Straight-line unroll of the integer-part scan: most integer parts are
398+ // 1-5 digits, so peeling the first iterations eliminates the loop back-edge
399+ // for the common case. Semantics are identical to the original `while` loop:
400+ // i = 10*i + digit, advancing p.
386401 if ((p != pend) && is_integer (*p)) {
387- i = 10 * i + uint64_t (*p - UC (' 0' ));
402+ i = uint64_t (*p - UC (' 0' ));
388403 ++p;
389404 if ((p != pend) && is_integer (*p)) {
390405 i = 10 * i + uint64_t (*p - UC (' 0' ));
@@ -395,29 +410,58 @@ parse_number_string(UC const *p, UC const *pend, parse_options_t<UC> options,
395410 if ((p != pend) && is_integer (*p)) {
396411 i = 10 * i + uint64_t (*p - UC (' 0' ));
397412 ++p;
398- while ((p != pend) && is_integer (*p)) {
399- // a multiplication by 10 is cheaper than an arbitrary integer
400- // multiplication
401- i = 10 * i +
402- uint64_t (*p - UC (' 0' )); // might overflow, handled later
413+ if ((p != pend) && is_integer (*p)) {
414+ i = 10 * i + uint64_t (*p - UC (' 0' ));
403415 ++p;
416+ while ((p != pend) && is_integer (*p)) {
417+ // a multiplication by 10 is cheaper than an arbitrary integer
418+ // multiplication
419+ i = 10 * i +
420+ uint64_t (*p - UC (' 0' )); // might overflow, handled later
421+ ++p;
422+ }
404423 }
405424 }
406425 }
407426 }
408427 }
428+ digit_count = int64_t (p - start_digits);
429+ }
430+ else {
431+ // Separator-aware scan: a configured digit separator (e.g. '\'') may appear
432+ // between digits. It is skipped and does not contribute to the value or the
433+ // digit count, but it is retained in the integer span below so the overflow
434+ // re-scan can re-tokenize correctly.
435+ while (p != pend) {
436+ if (*p == separator) {
437+ ++p;
438+ continue ;
439+ }
440+ if (!is_integer (*p)) {
441+ break ;
442+ }
443+ if (digit_count == 0 ) {
444+ first_digit_ptr = p;
445+ }
446+ i = 10 * i + uint64_t (*p - UC (' 0' )); // might overflow, handled later
447+ ++p;
448+ ++digit_count;
449+ }
409450 }
410451 UC const *const end_of_integer_part = p;
411- int64_t digit_count = int64_t (end_of_integer_part - start_digits);
412452 if (store_spans) {
413- answer.integer = span<UC const >(start_digits, size_t (digit_count));
453+ // The span keeps the raw characters (separators included) so the overflow
454+ // re-scan below can re-tokenize correctly; for has_separator == false the
455+ // length equals digit_count.
456+ answer.integer =
457+ span<UC const >(start_digits, size_t (end_of_integer_part - start_digits));
414458 }
415459 FASTFLOAT_IF_CONSTEXPR17 (basic_json_fmt) {
416460 // at least 1 digit in integer part, without leading zeros
417461 if (digit_count == 0 ) {
418462 return report_parse_error<UC >(p, parse_error::no_digits_in_integer_part);
419463 }
420- if ((start_digits[ 0 ] == UC (' 0' ) && digit_count > 1 )) {
464+ if ((*first_digit_ptr == UC (' 0' ) && digit_count > 1 )) {
421465 return report_parse_error<UC >(start_digits,
422466 parse_error::leading_zeros_in_integer_part);
423467 }
@@ -428,20 +472,40 @@ parse_number_string(UC const *p, UC const *pend, parse_options_t<UC> options,
428472 if (has_decimal_point) {
429473 ++p;
430474 UC const *before = p;
431- // can occur at most twice without overflowing, but let it occur more, since
432- // for integers with many digits, digit parsing is the primary bottleneck.
433- loop_parse_if_eight_digits (p, pend, i);
475+ int64_t fractional_digit_count = 0 ;
476+ FASTFLOAT_IF_CONSTEXPR17 (!has_separator) {
477+ // can occur at most twice without overflowing, but let it occur more,
478+ // since for integers with many digits, digit parsing is the primary
479+ // bottleneck.
480+ loop_parse_if_eight_digits (p, pend, i);
434481
435- while ((p != pend) && is_integer (*p)) {
436- uint8_t digit = uint8_t (*p - UC (' 0' ));
437- ++p;
438- i = i * 10 + digit; // in rare cases, this will overflow, but that's ok
482+ while ((p != pend) && is_integer (*p)) {
483+ uint8_t digit = uint8_t (*p - UC (' 0' ));
484+ ++p;
485+ i = i * 10 + digit; // in rare cases, this will overflow, but that's ok
486+ }
487+ fractional_digit_count = int64_t (p - before);
439488 }
440- exponent = before - p;
489+ else {
490+ while (p != pend) {
491+ if (*p == separator) {
492+ ++p;
493+ continue ;
494+ }
495+ if (!is_integer (*p)) {
496+ break ;
497+ }
498+ uint8_t digit = uint8_t (*p - UC (' 0' ));
499+ ++p;
500+ i = i * 10 + digit; // in rare cases, this will overflow, but that's ok
501+ ++fractional_digit_count;
502+ }
503+ }
504+ exponent = -fractional_digit_count;
441505 if (store_spans) {
442506 answer.fraction = span<UC const >(before, size_t (p - before));
443507 }
444- digit_count -= exponent ;
508+ digit_count += fractional_digit_count ;
445509 }
446510 FASTFLOAT_IF_CONSTEXPR17 (basic_json_fmt) {
447511 // at least 1 digit in fractional part
@@ -483,12 +547,30 @@ parse_number_string(UC const *p, UC const *pend, parse_options_t<UC> options,
483547 // Otherwise, we will be ignoring the 'e'.
484548 p = location_of_e;
485549 } else {
486- while ((p != pend) && is_integer (*p)) {
487- uint8_t digit = uint8_t (*p - UC (' 0' ));
488- if (exp_number < 0x10000000 ) {
489- exp_number = 10 * exp_number + digit;
550+ FASTFLOAT_IF_CONSTEXPR17 (!has_separator) {
551+ while ((p != pend) && is_integer (*p)) {
552+ uint8_t digit = uint8_t (*p - UC (' 0' ));
553+ if (exp_number < 0x10000000 ) {
554+ exp_number = 10 * exp_number + digit;
555+ }
556+ ++p;
557+ }
558+ }
559+ else {
560+ while (p != pend) {
561+ if (*p == separator) {
562+ ++p;
563+ continue ;
564+ }
565+ if (!is_integer (*p)) {
566+ break ;
567+ }
568+ uint8_t digit = uint8_t (*p - UC (' 0' ));
569+ if (exp_number < 0x10000000 ) {
570+ exp_number = 10 * exp_number + digit;
571+ }
572+ ++p;
490573 }
491- ++p;
492574 }
493575 if (neg_exp) {
494576 exp_number = -exp_number;
@@ -514,9 +596,12 @@ parse_number_string(UC const *p, UC const *pend, parse_options_t<UC> options,
514596 // It is possible that the integer had an overflow.
515597 // We have to handle the case where we have 0.0000somenumber.
516598 // We need to be mindful of the case where we only have zeroes...
517- // E.g., 0.000000000...000.
599+ // E.g., 0.000000000...000. The `has_separator &&` guard below is a
600+ // compile-time constant, so this loop is identical to the original when the
601+ // feature is disabled.
518602 UC const *start = start_digits;
519- while ((start != pend) && (*start == UC (' 0' ) || *start == decimal_point)) {
603+ while ((start != pend) && (*start == UC (' 0' ) || *start == decimal_point ||
604+ (has_separator && *start == separator))) {
520605 if (*start == UC (' 0' )) {
521606 digit_count--;
522607 }
@@ -537,20 +622,60 @@ parse_number_string(UC const *p, UC const *pend, parse_options_t<UC> options,
537622 p = answer.integer .ptr ;
538623 UC const *int_end = p + answer.integer .len ();
539624 uint64_t const minimal_nineteen_digit_integer{1000000000000000000 };
540- while ((i < minimal_nineteen_digit_integer) && (p != int_end)) {
541- i = i * 10 + uint64_t (*p - UC (' 0' ));
542- ++p;
625+ FASTFLOAT_IF_CONSTEXPR17 (!has_separator) {
626+ while ((i < minimal_nineteen_digit_integer) && (p != int_end)) {
627+ i = i * 10 + uint64_t (*p - UC (' 0' ));
628+ ++p;
629+ }
630+ if (i >= minimal_nineteen_digit_integer) { // We have a big integer
631+ exponent = end_of_integer_part - p + exp_number;
632+ } else { // We have a value with a fractional component.
633+ p = answer.fraction .ptr ;
634+ UC const *frac_end = p + answer.fraction .len ();
635+ while ((i < minimal_nineteen_digit_integer) && (p != frac_end)) {
636+ i = i * 10 + uint64_t (*p - UC (' 0' ));
637+ ++p;
638+ }
639+ exponent = answer.fraction .ptr - p + exp_number;
640+ }
543641 }
544- if (i >= minimal_nineteen_digit_integer) { // We have a big integer
545- exponent = end_of_integer_part - p + exp_number;
546- } else { // We have a value with a fractional component.
547- p = answer.fraction .ptr ;
548- UC const *frac_end = p + answer.fraction .len ();
549- while ((i < minimal_nineteen_digit_integer) && (p != frac_end)) {
642+ else {
643+ // Separator-aware re-scan: separators are skipped and excluded from
644+ // the digit counts that determine the exponent.
645+ while ((i < minimal_nineteen_digit_integer) && (p != int_end)) {
646+ if (*p == separator) {
647+ ++p;
648+ continue ;
649+ }
550650 i = i * 10 + uint64_t (*p - UC (' 0' ));
551651 ++p;
552652 }
553- exponent = answer.fraction .ptr - p + exp_number;
653+ if (i >= minimal_nineteen_digit_integer) { // We have a big integer
654+ int64_t remaining_integer_digits = 0 ;
655+ while (p != int_end) {
656+ if (*p == separator) {
657+ ++p;
658+ continue ;
659+ }
660+ ++p;
661+ ++remaining_integer_digits;
662+ }
663+ exponent = remaining_integer_digits + exp_number;
664+ } else { // We have a value with a fractional component.
665+ p = answer.fraction .ptr ;
666+ UC const *frac_end = p + answer.fraction .len ();
667+ int64_t fraction_digits_consumed = 0 ;
668+ while ((i < minimal_nineteen_digit_integer) && (p != frac_end)) {
669+ if (*p == separator) {
670+ ++p;
671+ continue ;
672+ }
673+ i = i * 10 + uint64_t (*p - UC (' 0' ));
674+ ++p;
675+ ++fraction_digits_consumed;
676+ }
677+ exponent = exp_number - fraction_digits_consumed;
678+ }
554679 }
555680 // We have now corrected both exponent and i, to a truncated value
556681 }
0 commit comments