496 lines
		
	
	
	
		
			18 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			496 lines
		
	
	
	
		
			18 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| // Copyright 2018 The Abseil Authors.
 | |
| //
 | |
| // Licensed under the Apache License, Version 2.0 (the "License");
 | |
| // you may not use this file except in compliance with the License.
 | |
| // You may obtain a copy of the License at
 | |
| //
 | |
| //      https://www.apache.org/licenses/LICENSE-2.0
 | |
| //
 | |
| // Unless required by applicable law or agreed to in writing, software
 | |
| // distributed under the License is distributed on an "AS IS" BASIS,
 | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | |
| // See the License for the specific language governing permissions and
 | |
| // limitations under the License.
 | |
| 
 | |
| #include "absl/strings/internal/charconv_parse.h"
 | |
| #include "absl/strings/charconv.h"
 | |
| 
 | |
| #include <cassert>
 | |
| #include <cstdint>
 | |
| #include <limits>
 | |
| 
 | |
| #include "absl/strings/internal/memutil.h"
 | |
| 
 | |
| namespace absl {
 | |
| namespace {
 | |
| 
 | |
| // ParseFloat<10> will read the first 19 significant digits of the mantissa.
 | |
| // This number was chosen for multiple reasons.
 | |
| //
 | |
| // (a) First, for whatever integer type we choose to represent the mantissa, we
 | |
| // want to choose the largest possible number of decimal digits for that integer
 | |
| // type.  We are using uint64_t, which can express any 19-digit unsigned
 | |
| // integer.
 | |
| //
 | |
| // (b) Second, we need to parse enough digits that the binary value of any
 | |
| // mantissa we capture has more bits of resolution than the mantissa
 | |
| // representation in the target float.  Our algorithm requires at least 3 bits
 | |
| // of headway, but 19 decimal digits give a little more than that.
 | |
| //
 | |
| // The following static assertions verify the above comments:
 | |
| constexpr int kDecimalMantissaDigitsMax = 19;
 | |
| 
 | |
| static_assert(std::numeric_limits<uint64_t>::digits10 ==
 | |
|                   kDecimalMantissaDigitsMax,
 | |
|               "(a) above");
 | |
| 
 | |
| // IEEE doubles, which we assume in Abseil, have 53 binary bits of mantissa.
 | |
| static_assert(std::numeric_limits<double>::is_iec559, "IEEE double assumed");
 | |
| static_assert(std::numeric_limits<double>::radix == 2, "IEEE double fact");
 | |
| static_assert(std::numeric_limits<double>::digits == 53, "IEEE double fact");
 | |
| 
 | |
| // The lowest valued 19-digit decimal mantissa we can read still contains
 | |
| // sufficient information to reconstruct a binary mantissa.
 | |
| static_assert(1000000000000000000u > (uint64_t(1) << (53 + 3)), "(b) above");
 | |
| 
 | |
| // ParseFloat<16> will read the first 15 significant digits of the mantissa.
 | |
| //
 | |
| // Because a base-16-to-base-2 conversion can be done exactly, we do not need
 | |
| // to maximize the number of scanned hex digits to improve our conversion.  What
 | |
| // is required is to scan two more bits than the mantissa can represent, so that
 | |
| // we always round correctly.
 | |
| //
 | |
| // (One extra bit does not suffice to perform correct rounding, since a number
 | |
| // exactly halfway between two representable floats has unique rounding rules,
 | |
| // so we need to differentiate between a "halfway between" number and a "closer
 | |
| // to the larger value" number.)
 | |
| constexpr int kHexadecimalMantissaDigitsMax = 15;
 | |
| 
 | |
| // The minimum number of significant bits that will be read from
 | |
| // kHexadecimalMantissaDigitsMax hex digits.  We must subtract by three, since
 | |
| // the most significant digit can be a "1", which only contributes a single
 | |
| // significant bit.
 | |
| constexpr int kGuaranteedHexadecimalMantissaBitPrecision =
 | |
|     4 * kHexadecimalMantissaDigitsMax - 3;
 | |
| 
 | |
| static_assert(kGuaranteedHexadecimalMantissaBitPrecision >
 | |
|                   std::numeric_limits<double>::digits + 2,
 | |
|               "kHexadecimalMantissaDigitsMax too small");
 | |
| 
 | |
| // We also impose a limit on the number of significant digits we will read from
 | |
| // an exponent, to avoid having to deal with integer overflow.  We use 9 for
 | |
| // this purpose.
 | |
| //
 | |
| // If we read a 9 digit exponent, the end result of the conversion will
 | |
| // necessarily be infinity or zero, depending on the sign of the exponent.
 | |
| // Therefore we can just drop extra digits on the floor without any extra
 | |
| // logic.
 | |
| constexpr int kDecimalExponentDigitsMax = 9;
 | |
| static_assert(std::numeric_limits<int>::digits10 >= kDecimalExponentDigitsMax,
 | |
|               "int type too small");
 | |
| 
 | |
| // To avoid incredibly large inputs causing integer overflow for our exponent,
 | |
| // we impose an arbitrary but very large limit on the number of significant
 | |
| // digits we will accept.  The implementation refuses to match a string with
 | |
| // more consecutive significant mantissa digits than this.
 | |
| constexpr int kDecimalDigitLimit = 50000000;
 | |
| 
 | |
| // Corresponding limit for hexadecimal digit inputs.  This is one fourth the
 | |
| // amount of kDecimalDigitLimit, since each dropped hexadecimal digit requires
 | |
| // a binary exponent adjustment of 4.
 | |
| constexpr int kHexadecimalDigitLimit = kDecimalDigitLimit / 4;
 | |
| 
 | |
| // The largest exponent we can read is 999999999 (per
 | |
| // kDecimalExponentDigitsMax), and the largest exponent adjustment we can get
 | |
| // from dropped mantissa digits is 2 * kDecimalDigitLimit, and the sum of these
 | |
| // comfortably fits in an integer.
 | |
| //
 | |
| // We count kDecimalDigitLimit twice because there are independent limits for
 | |
| // numbers before and after the decimal point.  (In the case where there are no
 | |
| // significant digits before the decimal point, there are independent limits for
 | |
| // post-decimal-point leading zeroes and for significant digits.)
 | |
| static_assert(999999999 + 2 * kDecimalDigitLimit <
 | |
|                   std::numeric_limits<int>::max(),
 | |
|               "int type too small");
 | |
| static_assert(999999999 + 2 * (4 * kHexadecimalDigitLimit) <
 | |
|                   std::numeric_limits<int>::max(),
 | |
|               "int type too small");
 | |
| 
 | |
| // Returns true if the provided bitfield allows parsing an exponent value
 | |
| // (e.g., "1.5e100").
 | |
| bool AllowExponent(chars_format flags) {
 | |
|   bool fixed = (flags & chars_format::fixed) == chars_format::fixed;
 | |
|   bool scientific =
 | |
|       (flags & chars_format::scientific) == chars_format::scientific;
 | |
|   return scientific || !fixed;
 | |
| }
 | |
| 
 | |
| // Returns true if the provided bitfield requires an exponent value be present.
 | |
| bool RequireExponent(chars_format flags) {
 | |
|   bool fixed = (flags & chars_format::fixed) == chars_format::fixed;
 | |
|   bool scientific =
 | |
|       (flags & chars_format::scientific) == chars_format::scientific;
 | |
|   return scientific && !fixed;
 | |
| }
 | |
| 
 | |
| const int8_t kAsciiToInt[256] = {
 | |
|     -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
 | |
|     -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
 | |
|     -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0,  1,  2,  3,  4,  5,  6,  7,  8,
 | |
|     9,  -1, -1, -1, -1, -1, -1, -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1,
 | |
|     -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
 | |
|     -1, -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
 | |
|     -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
 | |
|     -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
 | |
|     -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
 | |
|     -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
 | |
|     -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
 | |
|     -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
 | |
|     -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
 | |
|     -1, -1, -1, -1, -1, -1, -1, -1, -1};
 | |
| 
 | |
| // Returns true if `ch` is a digit in the given base
 | |
| template <int base>
 | |
| bool IsDigit(char ch);
 | |
| 
 | |
| // Converts a valid `ch` to its digit value in the given base.
 | |
| template <int base>
 | |
| unsigned ToDigit(char ch);
 | |
| 
 | |
| // Returns true if `ch` is the exponent delimiter for the given base.
 | |
| template <int base>
 | |
| bool IsExponentCharacter(char ch);
 | |
| 
 | |
| // Returns the maximum number of significant digits we will read for a float
 | |
| // in the given base.
 | |
| template <int base>
 | |
| constexpr int MantissaDigitsMax();
 | |
| 
 | |
| // Returns the largest consecutive run of digits we will accept when parsing a
 | |
| // number in the given base.
 | |
| template <int base>
 | |
| constexpr int DigitLimit();
 | |
| 
 | |
| // Returns the amount the exponent must be adjusted by for each dropped digit.
 | |
| // (For decimal this is 1, since the digits are in base 10 and the exponent base
 | |
| // is also 10, but for hexadecimal this is 4, since the digits are base 16 but
 | |
| // the exponent base is 2.)
 | |
| template <int base>
 | |
| constexpr int DigitMagnitude();
 | |
| 
 | |
| template <>
 | |
| bool IsDigit<10>(char ch) {
 | |
|   return ch >= '0' && ch <= '9';
 | |
| }
 | |
| template <>
 | |
| bool IsDigit<16>(char ch) {
 | |
|   return kAsciiToInt[static_cast<unsigned char>(ch)] >= 0;
 | |
| }
 | |
| 
 | |
| template <>
 | |
| unsigned ToDigit<10>(char ch) {
 | |
|   return ch - '0';
 | |
| }
 | |
| template <>
 | |
| unsigned ToDigit<16>(char ch) {
 | |
|   return kAsciiToInt[static_cast<unsigned char>(ch)];
 | |
| }
 | |
| 
 | |
| template <>
 | |
| bool IsExponentCharacter<10>(char ch) {
 | |
|   return ch == 'e' || ch == 'E';
 | |
| }
 | |
| 
 | |
| template <>
 | |
| bool IsExponentCharacter<16>(char ch) {
 | |
|   return ch == 'p' || ch == 'P';
 | |
| }
 | |
| 
 | |
| template <>
 | |
| constexpr int MantissaDigitsMax<10>() {
 | |
|   return kDecimalMantissaDigitsMax;
 | |
| }
 | |
| template <>
 | |
| constexpr int MantissaDigitsMax<16>() {
 | |
|   return kHexadecimalMantissaDigitsMax;
 | |
| }
 | |
| 
 | |
| template <>
 | |
| constexpr int DigitLimit<10>() {
 | |
|   return kDecimalDigitLimit;
 | |
| }
 | |
| template <>
 | |
| constexpr int DigitLimit<16>() {
 | |
|   return kHexadecimalDigitLimit;
 | |
| }
 | |
| 
 | |
| template <>
 | |
| constexpr int DigitMagnitude<10>() {
 | |
|   return 1;
 | |
| }
 | |
| template <>
 | |
| constexpr int DigitMagnitude<16>() {
 | |
|   return 4;
 | |
| }
 | |
| 
 | |
| // Reads decimal digits from [begin, end) into *out.  Returns the number of
 | |
| // digits consumed.
 | |
| //
 | |
| // After max_digits has been read, keeps consuming characters, but no longer
 | |
| // adjusts *out.  If a nonzero digit is dropped this way, *dropped_nonzero_digit
 | |
| // is set; otherwise, it is left unmodified.
 | |
| //
 | |
| // If no digits are matched, returns 0 and leaves *out unchanged.
 | |
| //
 | |
| // ConsumeDigits does not protect against overflow on *out; max_digits must
 | |
| // be chosen with respect to type T to avoid the possibility of overflow.
 | |
| template <int base, typename T>
 | |
| std::size_t ConsumeDigits(const char* begin, const char* end, int max_digits,
 | |
|                           T* out, bool* dropped_nonzero_digit) {
 | |
|   if (base == 10) {
 | |
|     assert(max_digits <= std::numeric_limits<T>::digits10);
 | |
|   } else if (base == 16) {
 | |
|     assert(max_digits * 4 <= std::numeric_limits<T>::digits);
 | |
|   }
 | |
|   const char* const original_begin = begin;
 | |
|   T accumulator = *out;
 | |
|   const char* significant_digits_end =
 | |
|       (end - begin > max_digits) ? begin + max_digits : end;
 | |
|   while (begin < significant_digits_end && IsDigit<base>(*begin)) {
 | |
|     // Do not guard against *out overflow; max_digits was chosen to avoid this.
 | |
|     // Do assert against it, to detect problems in debug builds.
 | |
|     auto digit = static_cast<T>(ToDigit<base>(*begin));
 | |
|     assert(accumulator * base >= accumulator);
 | |
|     accumulator *= base;
 | |
|     assert(accumulator + digit >= accumulator);
 | |
|     accumulator += digit;
 | |
|     ++begin;
 | |
|   }
 | |
|   bool dropped_nonzero = false;
 | |
|   while (begin < end && IsDigit<base>(*begin)) {
 | |
|     dropped_nonzero = dropped_nonzero || (*begin != '0');
 | |
|     ++begin;
 | |
|   }
 | |
|   if (dropped_nonzero && dropped_nonzero_digit != nullptr) {
 | |
|     *dropped_nonzero_digit = true;
 | |
|   }
 | |
|   *out = accumulator;
 | |
|   return begin - original_begin;
 | |
| }
 | |
| 
 | |
| // Returns true if `v` is one of the chars allowed inside parentheses following
 | |
| // a NaN.
 | |
| bool IsNanChar(char v) {
 | |
|   return (v == '_') || (v >= '0' && v <= '9') || (v >= 'a' && v <= 'z') ||
 | |
|          (v >= 'A' && v <= 'Z');
 | |
| }
 | |
| 
 | |
| // Checks the range [begin, end) for a strtod()-formatted infinity or NaN.  If
 | |
| // one is found, sets `out` appropriately and returns true.
 | |
| bool ParseInfinityOrNan(const char* begin, const char* end,
 | |
|                         strings_internal::ParsedFloat* out) {
 | |
|   if (end - begin < 3) {
 | |
|     return false;
 | |
|   }
 | |
|   switch (*begin) {
 | |
|     case 'i':
 | |
|     case 'I': {
 | |
|       // An infinity std::string consists of the characters "inf" or "infinity",
 | |
|       // case insensitive.
 | |
|       if (strings_internal::memcasecmp(begin + 1, "nf", 2) != 0) {
 | |
|         return false;
 | |
|       }
 | |
|       out->type = strings_internal::FloatType::kInfinity;
 | |
|       if (end - begin >= 8 &&
 | |
|           strings_internal::memcasecmp(begin + 3, "inity", 5) == 0) {
 | |
|         out->end = begin + 8;
 | |
|       } else {
 | |
|         out->end = begin + 3;
 | |
|       }
 | |
|       return true;
 | |
|     }
 | |
|     case 'n':
 | |
|     case 'N': {
 | |
|       // A NaN consists of the characters "nan", case insensitive, optionally
 | |
|       // followed by a parenthesized sequence of zero or more alphanumeric
 | |
|       // characters and/or underscores.
 | |
|       if (strings_internal::memcasecmp(begin + 1, "an", 2) != 0) {
 | |
|         return false;
 | |
|       }
 | |
|       out->type = strings_internal::FloatType::kNan;
 | |
|       out->end = begin + 3;
 | |
|       // NaN is allowed to be followed by a parenthesized std::string, consisting of
 | |
|       // only the characters [a-zA-Z0-9_].  Match that if it's present.
 | |
|       begin += 3;
 | |
|       if (begin < end && *begin == '(') {
 | |
|         const char* nan_begin = begin + 1;
 | |
|         while (nan_begin < end && IsNanChar(*nan_begin)) {
 | |
|           ++nan_begin;
 | |
|         }
 | |
|         if (nan_begin < end && *nan_begin == ')') {
 | |
|           // We found an extra NaN specifier range
 | |
|           out->subrange_begin = begin + 1;
 | |
|           out->subrange_end = nan_begin;
 | |
|           out->end = nan_begin + 1;
 | |
|         }
 | |
|       }
 | |
|       return true;
 | |
|     }
 | |
|     default:
 | |
|       return false;
 | |
|   }
 | |
| }
 | |
| }  // namespace
 | |
| 
 | |
| namespace strings_internal {
 | |
| 
 | |
| template <int base>
 | |
| strings_internal::ParsedFloat ParseFloat(const char* begin, const char* end,
 | |
|                                          chars_format format_flags) {
 | |
|   strings_internal::ParsedFloat result;
 | |
| 
 | |
|   // Exit early if we're given an empty range.
 | |
|   if (begin == end) return result;
 | |
| 
 | |
|   // Handle the infinity and NaN cases.
 | |
|   if (ParseInfinityOrNan(begin, end, &result)) {
 | |
|     return result;
 | |
|   }
 | |
| 
 | |
|   const char* const mantissa_begin = begin;
 | |
|   while (begin < end && *begin == '0') {
 | |
|     ++begin;  // skip leading zeros
 | |
|   }
 | |
|   uint64_t mantissa = 0;
 | |
| 
 | |
|   int exponent_adjustment = 0;
 | |
|   bool mantissa_is_inexact = false;
 | |
|   std::size_t pre_decimal_digits = ConsumeDigits<base>(
 | |
|       begin, end, MantissaDigitsMax<base>(), &mantissa, &mantissa_is_inexact);
 | |
|   begin += pre_decimal_digits;
 | |
|   int digits_left;
 | |
|   if (pre_decimal_digits >= DigitLimit<base>()) {
 | |
|     // refuse to parse pathological inputs
 | |
|     return result;
 | |
|   } else if (pre_decimal_digits > MantissaDigitsMax<base>()) {
 | |
|     // We dropped some non-fraction digits on the floor.  Adjust our exponent
 | |
|     // to compensate.
 | |
|     exponent_adjustment =
 | |
|         static_cast<int>(pre_decimal_digits - MantissaDigitsMax<base>());
 | |
|     digits_left = 0;
 | |
|   } else {
 | |
|     digits_left =
 | |
|         static_cast<int>(MantissaDigitsMax<base>() - pre_decimal_digits);
 | |
|   }
 | |
|   if (begin < end && *begin == '.') {
 | |
|     ++begin;
 | |
|     if (mantissa == 0) {
 | |
|       // If we haven't seen any nonzero digits yet, keep skipping zeros.  We
 | |
|       // have to adjust the exponent to reflect the changed place value.
 | |
|       const char* begin_zeros = begin;
 | |
|       while (begin < end && *begin == '0') {
 | |
|         ++begin;
 | |
|       }
 | |
|       std::size_t zeros_skipped = begin - begin_zeros;
 | |
|       if (zeros_skipped >= DigitLimit<base>()) {
 | |
|         // refuse to parse pathological inputs
 | |
|         return result;
 | |
|       }
 | |
|       exponent_adjustment -= static_cast<int>(zeros_skipped);
 | |
|     }
 | |
|     std::size_t post_decimal_digits = ConsumeDigits<base>(
 | |
|         begin, end, digits_left, &mantissa, &mantissa_is_inexact);
 | |
|     begin += post_decimal_digits;
 | |
| 
 | |
|     // Since `mantissa` is an integer, each significant digit we read after
 | |
|     // the decimal point requires an adjustment to the exponent. "1.23e0" will
 | |
|     // be stored as `mantissa` == 123 and `exponent` == -2 (that is,
 | |
|     // "123e-2").
 | |
|     if (post_decimal_digits >= DigitLimit<base>()) {
 | |
|       // refuse to parse pathological inputs
 | |
|       return result;
 | |
|     } else if (post_decimal_digits > digits_left) {
 | |
|       exponent_adjustment -= digits_left;
 | |
|     } else {
 | |
|       exponent_adjustment -= post_decimal_digits;
 | |
|     }
 | |
|   }
 | |
|   // If we've found no mantissa whatsoever, this isn't a number.
 | |
|   if (mantissa_begin == begin) {
 | |
|     return result;
 | |
|   }
 | |
|   // A bare "." doesn't count as a mantissa either.
 | |
|   if (begin - mantissa_begin == 1 && *mantissa_begin == '.') {
 | |
|     return result;
 | |
|   }
 | |
| 
 | |
|   if (mantissa_is_inexact) {
 | |
|     // We dropped significant digits on the floor.  Handle this appropriately.
 | |
|     if (base == 10) {
 | |
|       // If we truncated significant decimal digits, store the full range of the
 | |
|       // mantissa for future big integer math for exact rounding.
 | |
|       result.subrange_begin = mantissa_begin;
 | |
|       result.subrange_end = begin;
 | |
|     } else if (base == 16) {
 | |
|       // If we truncated hex digits, reflect this fact by setting the low
 | |
|       // ("sticky") bit.  This allows for correct rounding in all cases.
 | |
|       mantissa |= 1;
 | |
|     }
 | |
|   }
 | |
|   result.mantissa = mantissa;
 | |
| 
 | |
|   const char* const exponent_begin = begin;
 | |
|   result.literal_exponent = 0;
 | |
|   bool found_exponent = false;
 | |
|   if (AllowExponent(format_flags) && begin < end &&
 | |
|       IsExponentCharacter<base>(*begin)) {
 | |
|     bool negative_exponent = false;
 | |
|     ++begin;
 | |
|     if (begin < end && *begin == '-') {
 | |
|       negative_exponent = true;
 | |
|       ++begin;
 | |
|     } else if (begin < end && *begin == '+') {
 | |
|       ++begin;
 | |
|     }
 | |
|     const char* const exponent_digits_begin = begin;
 | |
|     // Exponent is always expressed in decimal, even for hexadecimal floats.
 | |
|     begin += ConsumeDigits<10>(begin, end, kDecimalExponentDigitsMax,
 | |
|                                &result.literal_exponent, nullptr);
 | |
|     if (begin == exponent_digits_begin) {
 | |
|       // there were no digits where we expected an exponent.  We failed to read
 | |
|       // an exponent and should not consume the 'e' after all.  Rewind 'begin'.
 | |
|       found_exponent = false;
 | |
|       begin = exponent_begin;
 | |
|     } else {
 | |
|       found_exponent = true;
 | |
|       if (negative_exponent) {
 | |
|         result.literal_exponent = -result.literal_exponent;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (!found_exponent && RequireExponent(format_flags)) {
 | |
|     // Provided flags required an exponent, but none was found.  This results
 | |
|     // in a failure to scan.
 | |
|     return result;
 | |
|   }
 | |
| 
 | |
|   // Success!
 | |
|   result.type = strings_internal::FloatType::kNumber;
 | |
|   if (result.mantissa > 0) {
 | |
|     result.exponent = result.literal_exponent +
 | |
|                       (DigitMagnitude<base>() * exponent_adjustment);
 | |
|   } else {
 | |
|     result.exponent = 0;
 | |
|   }
 | |
|   result.end = begin;
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| template ParsedFloat ParseFloat<10>(const char* begin, const char* end,
 | |
|                                     chars_format format_flags);
 | |
| template ParsedFloat ParseFloat<16>(const char* begin, const char* end,
 | |
|                                     chars_format format_flags);
 | |
| 
 | |
| }  // namespace strings_internal
 | |
| }  // namespace absl
 |