diff --git a/src/iceberg/util/string_util.h b/src/iceberg/util/string_util.h index 3528204ee..e2def1b48 100644 --- a/src/iceberg/util/string_util.h +++ b/src/iceberg/util/string_util.h @@ -20,12 +20,14 @@ #pragma once #include +#include #include +#include #include #include #include +#include #include -#include #include "iceberg/iceberg_export.h" #include "iceberg/result.h" @@ -68,23 +70,71 @@ class ICEBERG_EXPORT StringUtils { } template - requires std::is_arithmetic_v && (!std::same_as) + requires std::is_integral_v && (!std::same_as) static Result ParseNumber(std::string_view str) { - T value = 0; + T value{}; auto [ptr, ec] = std::from_chars(str.data(), str.data() + str.size(), value); - if (ec == std::errc()) [[likely]] { + if (ec == std::errc() && ptr == str.data() + str.size()) [[likely]] { return value; } - if (ec == std::errc::invalid_argument) { - return InvalidArgument("Failed to parse {} from string '{}': invalid argument", - typeid(T).name(), str); - } if (ec == std::errc::result_out_of_range) { return InvalidArgument("Failed to parse {} from string '{}': value out of range", typeid(T).name(), str); } - std::unreachable(); + return InvalidArgument("Failed to parse {} from string '{}': invalid argument", + typeid(T).name(), str); } + + template + requires std::is_floating_point_v + static Result ParseNumber(std::string_view str) { + T value{}; + if constexpr (kHasFloatFromChars) { + auto [ptr, ec] = std::from_chars(str.data(), str.data() + str.size(), value); + if (ec == std::errc() && ptr == str.data() + str.size()) [[likely]] { + return value; + } + if (ec == std::errc::result_out_of_range) { + return InvalidArgument("Failed to parse {} from string '{}': value out of range", + typeid(T).name(), str); + } + return InvalidArgument("Failed to parse {} from string '{}': invalid argument", + typeid(T).name(), str); + } else { + // strto* require null-terminated input; string_view does not guarantee it. + std::string owned(str); + const char* start = owned.c_str(); + char* end = nullptr; + errno = 0; + + if constexpr (std::same_as) { + value = std::strtof(start, &end); + } else if constexpr (std::same_as) { + value = std::strtod(start, &end); + } else { + value = std::strtold(start, &end); + } + + if (end == start || end != start + static_cast(owned.size())) { + return InvalidArgument("Failed to parse {} from string '{}': invalid argument", + typeid(T).name(), str); + } + if (errno == ERANGE) { + return InvalidArgument("Failed to parse {} from string '{}': value out of range", + typeid(T).name(), str); + } + return value; + } + } + + private: + // libc++ 20+ provides floating-point std::from_chars; use strto* fallback for older + // versions. +#if defined(_LIBCPP_VERSION) && _LIBCPP_VERSION < 200000 + static constexpr bool kHasFloatFromChars = false; +#else + static constexpr bool kHasFloatFromChars = true; +#endif }; /// \brief Transparent hash function that supports std::string_view as lookup key