Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package fast_float for openSUSE:Factory checked in at 2026-11-03 17:53:16 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/fast_float (Old) and /work/SRC/openSUSE:Factory/.fast_float.new.1257 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "fast_float" Tue Nov 3 17:53:16 2026 rev:15 rq:1394425 version:8.3.3 Changes: -------- --- /work/SRC/openSUSE:Factory/fast_float/fast_float.changes 2026-10-11 15:14:45.199302198 +0200 +++ /work/SRC/openSUSE:Factory/.fast_float.new.1257/fast_float.changes 2026-11-03 17:54:05.072606423 +0100 @@ -1,0 +2,9 @@ +Mon Nov 2 10:04:53 UTC 2026 - Jan Engelhardt <[email protected]> + +- Update to release 8.3.3 + * std::float32_t/std::float64_t is left unmodified on + invalid input + * Right-shifting a negative value in detail::power() is avoided + * integer_times_pow10 arguments wider than 64 bits are rejected + +------------------------------------------------------------------- Old: ---- v8.3.2.tar.gz New: ---- v8.3.3.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ fast_float.spec ++++++ --- /var/tmp/diff_new_pack.aH2WuP/_old 2026-11-03 17:54:06.611670750 +0100 +++ /var/tmp/diff_new_pack.aH2WuP/_new 2026-11-03 17:54:06.614670875 +0100 @@ -17,7 +17,7 @@ Name: fast_float -Version: 8.3.2 +Version: 8.3.3 Release: 0 Summary: Re-implementation of std::from_chars for parsing strings into numbers License: Apache-2.0 OR BSL-1.0 OR MIT ++++++ _scmsync.obsinfo ++++++ --- /var/tmp/diff_new_pack.aH2WuP/_old 2026-11-03 17:54:06.686673885 +0100 +++ /var/tmp/diff_new_pack.aH2WuP/_new 2026-11-03 17:54:06.694674219 +0100 @@ -1,5 +1,5 @@ -mtime: 1791582352 -commit: 5847ec360eba64490bdac4c03626b0ec0a3768a5dbe797b6936c93f305ad4f60 +mtime: 1793614918 +commit: beac1185734ccb28d1839c2a85e5db31d9621aa6064bb3667a11c13ba3e07693 url: https://src.opensuse.org/jengelh/fast_float revision: master ++++++ build.specials.obscpio ++++++ ++++++ build.specials.obscpio ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/.gitignore new/.gitignore --- old/.gitignore 1970-01-01 01:00:00.000000000 +0100 +++ new/.gitignore 2026-11-02 11:21:58.000000000 +0100 @@ -0,0 +1 @@ +.osc ++++++ v8.3.2.tar.gz -> v8.3.3.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fast_float-8.3.2/.github/workflows/ubuntu22-clang.yml new/fast_float-8.3.3/.github/workflows/ubuntu22-clang.yml --- old/fast_float-8.3.2/.github/workflows/ubuntu22-clang.yml 2026-10-09 21:56:38.000000000 +0200 +++ new/fast_float-8.3.3/.github/workflows/ubuntu22-clang.yml 2026-10-31 14:45:33.000000000 +0100 @@ -9,6 +9,14 @@ - uses: actions/checkout@v7 - name: Install clang++-14 run: sudo apt-get install -y clang++-14 + - name: Check for pedantic warnings in pre-C++20 modes (issue 427) + run: | + set -xe + printf '#include "fast_float/fast_float.h"\nint main() { return 0; }\n' > pedantic_check.cpp + for std in c++11 c++14 c++17 c++20; do + clang++-14 -std=$std -Wall -Wextra -pedantic-errors -Werror \ + -Iinclude -fsyntax-only pedantic_check.cpp + done - name: Use cmake run: | mkdir build && @@ -22,4 +30,4 @@ cd build20 && CXX=clang++-14 cmake -DFASTFLOAT_CONSTEXPR_TESTS=ON -DFASTFLOAT_CXX_STANDARD=20 -DFASTFLOAT_TEST=ON .. && cmake --build . && - ctest --output-on-failure \ No newline at end of file + ctest --output-on-failure diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fast_float-8.3.2/CMakeLists.txt new/fast_float-8.3.3/CMakeLists.txt --- old/fast_float-8.3.2/CMakeLists.txt 2026-10-09 21:56:38.000000000 +0200 +++ new/fast_float-8.3.3/CMakeLists.txt 2026-10-31 14:45:33.000000000 +0100 @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.14) -project(fast_float VERSION 8.3.2 LANGUAGES CXX) +project(fast_float VERSION 8.3.3 LANGUAGES CXX) set(FASTFLOAT_CXX_STANDARD 11 CACHE STRING "the C++ standard to use for fastfloat") set(CMAKE_CXX_STANDARD ${FASTFLOAT_CXX_STANDARD}) option(FASTFLOAT_TEST "Enable tests" OFF) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fast_float-8.3.2/MODULE.bazel new/fast_float-8.3.3/MODULE.bazel --- old/fast_float-8.3.2/MODULE.bazel 2026-10-09 21:56:38.000000000 +0200 +++ new/fast_float-8.3.3/MODULE.bazel 2026-10-31 14:45:33.000000000 +0100 @@ -2,7 +2,7 @@ module( name = "fast_float", - version = "8.3.2", + version = "8.3.3", compatibility_level = 8, ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fast_float-8.3.2/README.md new/fast_float-8.3.3/README.md --- old/fast_float-8.3.2/README.md 2026-10-09 21:56:38.000000000 +0200 +++ new/fast_float-8.3.3/README.md 2026-10-31 14:45:33.000000000 +0100 @@ -482,7 +482,7 @@ ``` Overloads of `fast_float::integer_times_pow10()` are provided for -signed and unsigned integer types: `int64_t`, `uint64_t`, etc. +signed and unsigned integer types up to 64 bits: `int64_t`, `uint64_t`, etc. ## Users and Related Work @@ -587,7 +587,7 @@ FetchContent_Declare( fast_float GIT_REPOSITORY https://github.com/fastfloat/fast_float.git - GIT_TAG tags/v8.3.2 + GIT_TAG tags/v8.3.3 GIT_SHALLOW TRUE) FetchContent_MakeAvailable(fast_float) @@ -603,7 +603,7 @@ CPMAddPackage( NAME fast_float GITHUB_REPOSITORY "fastfloat/fast_float" - GIT_TAG v8.3.2) + GIT_TAG v8.3.3) ``` ## Using as single header @@ -615,7 +615,7 @@ You may directly download automatically generated single-header files: -<https://github.com/fastfloat/fast_float/releases/download/v8.3.2/fast_float.h> +<https://github.com/fastfloat/fast_float/releases/download/v8.3.3/fast_float.h> ## Benchmarking diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fast_float-8.3.2/fuzz/from_chars.cc new/fast_float-8.3.3/fuzz/from_chars.cc --- old/fast_float-8.3.2/fuzz/from_chars.cc 2026-10-09 21:56:38.000000000 +0200 +++ new/fast_float-8.3.3/fuzz/from_chars.cc 2026-10-31 14:45:33.000000000 +0100 @@ -1,38 +1,205 @@ #include "fast_float/fast_float.h" #include <fuzzer/FuzzedDataProvider.h> +#include <cstdint> +#include <cstdio> +#include <cstdlib> +#include <cstring> #include <string> #include <system_error> +#include <type_traits> +#include <vector> + +#if defined(__has_include) +#if __has_include(<charconv>) && __cplusplus >= 201703L +#include <charconv> +#define FASTFLOAT_FUZZ_CHARCONV 1 +#endif +#endif + +static void fail(char const *check) { + fprintf(stderr, "fast_float fuzz failure: %s\n", check); + abort(); +} fast_float::chars_format arbitrary_format(FuzzedDataProvider &fdp) { using fast_float::chars_format; - switch (fdp.ConsumeIntegralInRange<int>(0, 3)) { + chars_format format; + switch (fdp.ConsumeIntegralInRange<int>(0, 7)) { case 0: - return chars_format::scientific; + format = chars_format::scientific; break; case 1: - return chars_format::fixed; + format = chars_format::fixed; break; case 2: - return chars_format::fixed; + format = chars_format::json; + break; + case 3: + format = chars_format::json_or_infnan; + break; + case 4: + format = chars_format::fortran; break; + case 5: + format = chars_format::javascript; + break; + case 6: + format = chars_format::javascript_sloppy; + break; + default: + format = chars_format::general; + break; + } + if (fdp.ConsumeBool()) { + format = format | chars_format::allow_leading_plus; + } + if (fdp.ConsumeBool()) { + format = format | chars_format::skip_white_space; + } + return format; +} + +template <typename T> uint64_t bits_of(T value) { + uint64_t bits = 0; + memcpy(&bits, &value, sizeof(T)); + return bits; +} + +// The same ASCII text must parse identically for every character type: same +// error code, same number of consumed code units, bit-identical value. +template <typename UC> +void check_widened(std::string const &input, + fast_float::from_chars_result_t<char> expected, + double expected_value, fast_float::chars_format format) { + std::vector<UC> wide; + wide.reserve(input.size()); + for (char c : input) { + wide.push_back(UC(static_cast<unsigned char>(c))); + } + double value = 0.0; + auto result = fast_float::from_chars(wide.data(), wide.data() + wide.size(), + value, format); + if (result.ec != expected.ec || + (result.ptr - wide.data()) != (expected.ptr - input.data())) { + fail("wide result disagrees with char result"); + } + if (expected.ec == std::errc() && bits_of(value) != bits_of(expected_value)) { + fail("wide value disagrees with char value"); + } +} + +// Every number format accepted by the library is pure ASCII, so a successful +// parse must never consume a code unit >= 0x80. A violation means some helper +// inspected only the low byte of a wide code unit (the bug behind #391). +// The generated sequences keep an ASCII low byte but take hostile high bits: +// top-bit-set units (near the minimum of a signed wchar_t, the shape behind +// #422), sign-extended negative units (the shape behind #395), and non-ASCII +// code points whose low byte is an ASCII digit (the shape behind #391). +template <typename UC> +void check_arbitrary_units(FuzzedDataProvider &fdp, + fast_float::chars_format format) { + using unsigned_uc = typename std::make_unsigned<UC>::type; + size_t count = fdp.ConsumeIntegralInRange<size_t>(0, 64); + std::vector<UC> units; + units.reserve(count); + for (size_t i = 0; i < count; ++i) { + unsigned_uc unit = fdp.ConsumeIntegral<uint8_t>(); + switch (fdp.ConsumeIntegralInRange<int>(0, 4)) { + case 0: // top bit set: near the minimum value when UC is signed + unit = unsigned_uc(unit | (unsigned_uc(1) << (sizeof(UC) * 8 - 1))); + break; + case 1: // all high bits set: a small negative value when UC is signed + unit = unsigned_uc(unit | ~unsigned_uc(0xFF)); + break; + case 2: // arbitrary high byte: a non-ASCII code point, ASCII low byte + unit = unsigned_uc( + unit | unsigned_uc(unsigned_uc(fdp.ConsumeIntegral<uint8_t>()) << 8)); + break; + default: // plain ASCII unit + break; + } + units.push_back(UC(unit)); + } + if (units.empty()) { + return; + } + UC const *begin = units.data(); + UC const *end = begin + units.size(); + + double as_double = 0.0; + auto result = fast_float::from_chars(begin, end, as_double, format); + if (result.ec == std::errc()) { + for (UC const *p = begin; p != result.ptr; ++p) { + if (static_cast<unsigned_uc>(*p) >= 0x80) { + fail("float parse consumed a non-ASCII code unit"); + } + } + } + uint16_t as_uint16 = 0; + auto int_result = fast_float::from_chars(begin, end, as_uint16); + if (int_result.ec == std::errc()) { + for (UC const *p = begin; p != int_result.ptr; ++p) { + if (static_cast<unsigned_uc>(*p) >= 0x80) { + fail("integer parse consumed a non-ASCII code unit"); + } + } + } +} + +// fast_float's integer parsing is specified to match std::from_chars. +template <typename T> void check_int(std::string const &input, int base) { + T value{}; + auto result = fast_float::from_chars( + input.data(), input.data() + input.size(), value, base); +#ifdef FASTFLOAT_FUZZ_CHARCONV + T expected{}; + auto std_result = std::from_chars(input.data(), input.data() + input.size(), + expected, base); + if (result.ec != std_result.ec || result.ptr != std_result.ptr) { + fail("integer result disagrees with std::from_chars"); } - return chars_format::general; + if (result.ec == std::errc() && value != expected) { + fail("integer value disagrees with std::from_chars"); + } +#else + static_cast<void>(result); +#endif +} + +template <typename T> +void check_int_all_bases(std::string const &input, int base) { + check_int<T>(input, 10); + check_int<T>(input, base); } extern "C" int LLVMFuzzerTestOneInput(uint8_t const *data, size_t size) { FuzzedDataProvider fdp(data, size); fast_float::chars_format format = arbitrary_format(fdp); - double result_d = 0.0; + int base = fdp.ConsumeIntegralInRange<int>(2, 36); + std::string input_d = fdp.ConsumeRandomLengthString(128); - auto answer = fast_float::from_chars( + double result_d = 0.0; + auto answer_d = fast_float::from_chars( input_d.data(), input_d.data() + input_d.size(), result_d, format); - std::string input_f = fdp.ConsumeRandomLengthString(128); float result_f = 0.0; - answer = fast_float::from_chars( - input_f.data(), input_f.data() + input_f.size(), result_f, format); - int result_i = 0; + fast_float::from_chars(input_d.data(), input_d.data() + input_d.size(), + result_f, format); + check_widened<wchar_t>(input_d, answer_d, result_d, format); + check_widened<char16_t>(input_d, answer_d, result_d, format); + check_widened<char32_t>(input_d, answer_d, result_d, format); + std::string input_i = fdp.ConsumeRandomLengthString(128); - answer = fast_float::from_chars(input_i.data(), - input_i.data() + input_i.size(), result_i); + check_int_all_bases<uint8_t>(input_i, base); + check_int_all_bases<int8_t>(input_i, base); + check_int_all_bases<uint16_t>(input_i, base); + check_int_all_bases<int16_t>(input_i, base); + check_int_all_bases<uint32_t>(input_i, base); + check_int_all_bases<int32_t>(input_i, base); + check_int_all_bases<uint64_t>(input_i, base); + check_int_all_bases<int64_t>(input_i, base); + + check_arbitrary_units<wchar_t>(fdp, format); + check_arbitrary_units<char16_t>(fdp, format); + check_arbitrary_units<char32_t>(fdp, format); return 0; -} \ No newline at end of file +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fast_float-8.3.2/include/fast_float/ascii_number.h new/fast_float-8.3.3/include/fast_float/ascii_number.h --- old/fast_float-8.3.2/include/fast_float/ascii_number.h 2026-10-09 21:56:38.000000000 +0200 +++ new/fast_float-8.3.3/include/fast_float/ascii_number.h 2026-10-31 14:45:33.000000000 +0100 @@ -622,10 +622,22 @@ // JSON and JavaScript are mutually exclusive, so only the non-JSON // instantiation has to look at the flag. FASTFLOAT_IF_CONSTEXPR17(!basic_json_fmt) { + // We have to disable -Wc++20-extensions for the [[unlikely]] attribute + // See comment for @jwakely at + // https://github.com/fastfloat/fast_float/pull/387#discussion_r3366943539 +#ifdef __clang__ +#pragma clang diagnostic push +#if (!defined(__APPLE_CC__) && __clang_major__ >= 10) || (__clang_major__ >= 13) +#pragma clang diagnostic ignored "-Wc++20-extensions" +#endif +#endif if fastfloat_unlikely (uint64_t(options.format & detail::basic_javascript_fmt)) { return parse_number_string_javascript<UC>(p, pend, options, store_spans); } +#ifdef __clang__ +#pragma clang diagnostic pop +#endif } return parse_number_string_impl<basic_json_fmt, false, UC>(p, pend, options, store_spans); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fast_float-8.3.2/include/fast_float/decimal_to_binary.h new/fast_float-8.3.3/include/fast_float/decimal_to_binary.h --- old/fast_float-8.3.2/include/fast_float/decimal_to_binary.h 2026-10-09 21:56:38.000000000 +0200 +++ new/fast_float-8.3.3/include/fast_float/decimal_to_binary.h 2026-10-31 14:45:33.000000000 +0100 @@ -61,9 +61,15 @@ * -ceil(p) + q * where * p = log(5**-q)/log(2) = -q * log(5)/log(2) + * + * The shift must floor towards negative infinity, as an arithmetic right + * shift does, but right-shifting a negative value is implementation-defined + * before C++20. Biasing the product by 2048 * 65536 keeps the shifted value + * non-negative without changing the result: the bias is a multiple of 2^16 + * and is removed again after the shift. */ constexpr fastfloat_really_inline int32_t power(int32_t q) noexcept { - return (((152170 + 65536) * q) >> 16) + 63; + return (((152170 + 65536) * q + (2048 * 65536)) >> 16) - 2048 + 63; } } // namespace detail diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fast_float-8.3.2/include/fast_float/float_common.h new/fast_float-8.3.3/include/fast_float/float_common.h --- old/fast_float-8.3.2/include/fast_float/float_common.h 2026-10-09 21:56:38.000000000 +0200 +++ new/fast_float-8.3.3/include/fast_float/float_common.h 2026-10-31 14:45:33.000000000 +0100 @@ -18,7 +18,7 @@ #define FASTFLOAT_VERSION_MAJOR 8 #define FASTFLOAT_VERSION_MINOR 3 -#define FASTFLOAT_VERSION_PATCH 2 +#define FASTFLOAT_VERSION_PATCH 3 #define FASTFLOAT_STRINGIZE_IMPL(x) #x #define FASTFLOAT_STRINGIZE(x) FASTFLOAT_STRINGIZE_IMPL(x) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fast_float-8.3.2/include/fast_float/parse_number.h new/fast_float-8.3.3/include/fast_float/parse_number.h --- old/fast_float-8.3.2/include/fast_float/parse_number.h 2026-10-09 21:56:38.000000000 +0200 +++ new/fast_float-8.3.3/include/fast_float/parse_number.h 2026-10-31 14:45:33.000000000 +0100 @@ -158,7 +158,10 @@ // float32_t float val = 0.0f; auto ret = from_chars_advanced(first, last, val, options); - value = val; + // If no characters match the pattern, value must be left unmodified. + if (ret.ec != std::errc::invalid_argument) { + value = val; + } return ret; } }; @@ -175,7 +178,10 @@ // float64_t double val = 0.0; auto ret = from_chars_advanced(first, last, val, options); - value = val; + // If no characters match the pattern, value must be left unmodified. + if (ret.ec != std::errc::invalid_argument) { + value = val; + } return ret; } }; @@ -385,9 +391,19 @@ // Leave for the javascript conversion before anything else is live, so this // compiles to a tail call and the common path keeps its registers. The cold // function repeats the leading-whitespace and empty-input handling below. + // See the comment on -Wc++20-extensions further down in this function. +#ifdef __clang__ +#pragma clang diagnostic push +#if (!defined(__APPLE_CC__) && __clang_major__ >= 10) || (__clang_major__ >= 13) +#pragma clang diagnostic ignored "-Wc++20-extensions" +#endif +#endif if fastfloat_unlikely (uint64_t(fmt & detail::basic_javascript_fmt)) { return from_chars_float_javascript<T, UC>(first, last, value, options); } +#ifdef __clang__ +#pragma clang diagnostic pop +#endif from_chars_result_t<UC> answer; if (uint64_t(fmt & chars_format::skip_white_space)) { @@ -529,38 +545,42 @@ // the following overloads are here to avoid surprising ambiguity for int, // unsigned, etc. template <typename T, typename Int> -FASTFLOAT_CONSTEXPR20 - typename std::enable_if<is_supported_float_type<T>::value && - std::is_integral<Int>::value && - !std::is_signed<Int>::value, - T>::type - integer_times_pow10(Int mantissa, int decimal_exponent) noexcept { +FASTFLOAT_CONSTEXPR20 typename std::enable_if< + is_supported_float_type<T>::value && std::is_integral<Int>::value && + sizeof(Int) <= sizeof(uint64_t) && !std::is_signed<Int>::value, + T>::type +integer_times_pow10(Int mantissa, int decimal_exponent) noexcept { return integer_times_pow10<T>(static_cast<uint64_t>(mantissa), decimal_exponent); } template <typename T, typename Int> -FASTFLOAT_CONSTEXPR20 - typename std::enable_if<is_supported_float_type<T>::value && - std::is_integral<Int>::value && - std::is_signed<Int>::value, - T>::type - integer_times_pow10(Int mantissa, int decimal_exponent) noexcept { +FASTFLOAT_CONSTEXPR20 typename std::enable_if< + is_supported_float_type<T>::value && std::is_integral<Int>::value && + sizeof(Int) <= sizeof(int64_t) && std::is_signed<Int>::value, + T>::type +integer_times_pow10(Int mantissa, int decimal_exponent) noexcept { return integer_times_pow10<T>(static_cast<int64_t>(mantissa), decimal_exponent); } template <typename Int> -FASTFLOAT_CONSTEXPR20 typename std::enable_if< - std::is_integral<Int>::value && !std::is_signed<Int>::value, double>::type -integer_times_pow10(Int mantissa, int decimal_exponent) noexcept { +FASTFLOAT_CONSTEXPR20 + typename std::enable_if<std::is_integral<Int>::value && + sizeof(Int) <= sizeof(uint64_t) && + !std::is_signed<Int>::value, + double>::type + integer_times_pow10(Int mantissa, int decimal_exponent) noexcept { return integer_times_pow10(static_cast<uint64_t>(mantissa), decimal_exponent); } template <typename Int> -FASTFLOAT_CONSTEXPR20 typename std::enable_if< - std::is_integral<Int>::value && std::is_signed<Int>::value, double>::type -integer_times_pow10(Int mantissa, int decimal_exponent) noexcept { +FASTFLOAT_CONSTEXPR20 + typename std::enable_if<std::is_integral<Int>::value && + sizeof(Int) <= sizeof(int64_t) && + std::is_signed<Int>::value, + double>::type + integer_times_pow10(Int mantissa, int decimal_exponent) noexcept { return integer_times_pow10(static_cast<int64_t>(mantissa), decimal_exponent); } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fast_float-8.3.2/tests/basictest.cpp new/fast_float-8.3.3/tests/basictest.cpp --- old/fast_float-8.3.2/tests/basictest.cpp 2026-10-09 21:56:38.000000000 +0200 +++ new/fast_float-8.3.3/tests/basictest.cpp 2026-10-31 14:45:33.000000000 +0100 @@ -2283,6 +2283,38 @@ } } // namespace all_supported_types +#if defined(__SIZEOF_INT128__) +template <typename Int, typename = void> +struct has_integer_times_pow10 : std::false_type {}; + +template <typename Int> +struct has_integer_times_pow10< + Int, std::void_t<decltype(fast_float::integer_times_pow10(Int{}, 0))>> + : std::true_type {}; + +template <typename Float, typename Int, typename = void> +struct has_typed_integer_times_pow10 : std::false_type {}; + +template <typename Float, typename Int> +struct has_typed_integer_times_pow10< + Float, Int, + std::void_t<decltype(fast_float::integer_times_pow10<Float>(Int{}, 0))>> + : std::true_type {}; + +TEST_CASE("integer_times_pow10 rejects integers wider than 64 bits") { + CHECK(has_integer_times_pow10<int64_t>::value); + CHECK(has_integer_times_pow10<uint64_t>::value); + CHECK(has_typed_integer_times_pow10<float, int64_t>::value); + CHECK(has_typed_integer_times_pow10<double, uint64_t>::value); + CHECK_FALSE(has_integer_times_pow10<__int128_t>::value); + CHECK_FALSE(has_integer_times_pow10<__uint128_t>::value); + CHECK_FALSE(has_typed_integer_times_pow10<float, __int128_t>::value); + CHECK_FALSE(has_typed_integer_times_pow10<float, __uint128_t>::value); + CHECK_FALSE(has_typed_integer_times_pow10<double, __int128_t>::value); + CHECK_FALSE(has_typed_integer_times_pow10<double, __uint128_t>::value); +} +#endif + TEST_CASE("integer_times_pow10") { /* explicitly verifying API with different types of integers */ // double (the "default" overload) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fast_float-8.3.2/tests/fixedwidthtest.cpp new/fast_float-8.3.3/tests/fixedwidthtest.cpp --- old/fast_float-8.3.2/tests/fixedwidthtest.cpp 2026-10-09 21:56:38.000000000 +0200 +++ new/fast_float-8.3.3/tests/fixedwidthtest.cpp 2026-10-31 14:45:33.000000000 +0100 @@ -2,6 +2,8 @@ #include <iostream> #include <vector> #include <cstring> +#include <limits> +#include <string_view> #include "fast_float/fast_float.h" #include <cstdint> @@ -9,6 +11,39 @@ #include <stdfloat> #endif +// If no characters match the pattern, value must be left unmodified +// (see https://github.com/fastfloat/fast_float/issues/424). +template <typename T> bool check_invalid_input_preserves_value() { + std::vector<std::string_view> const invalid_inputs{"x", "", "-", ".", + "e1", "-x", "na", "+"}; + for (auto const &f : invalid_inputs) { + T result = T(42); + auto answer = fast_float::from_chars(f.data(), f.data() + f.size(), result); + if (answer.ec != std::errc::invalid_argument || answer.ptr != f.data()) { + std::cerr << "Expected invalid_argument for input: \"" << f << "\"" + << std::endl; + return false; + } + if (result != T(42)) { + std::cerr << "Value modified on invalid input: \"" << f << "\" got " + << result << std::endl; + return false; + } + } + // Out-of-range input must still store the documented value. + std::string_view const too_large = "1e999"; + T result = T(42); + auto answer = fast_float::from_chars( + too_large.data(), too_large.data() + too_large.size(), result); + if (answer.ec != std::errc::result_out_of_range || + result != std::numeric_limits<T>::infinity()) { + std::cerr << "Expected infinity and result_out_of_range for \"1e999\"" + << std::endl; + return false; + } + return true; +} + int main() { // Write some testcases for the parsing of floating point numbers in the // float32_t type. We use the from_chars function defined in this library. @@ -33,6 +68,9 @@ return EXIT_FAILURE; } } + if (!check_invalid_input_preserves_value<std::float32_t>()) { + return EXIT_FAILURE; + } #else std::cout << "No std::float32_t type available." << std::endl; #endif @@ -60,6 +98,9 @@ return EXIT_FAILURE; } } + if (!check_invalid_input_preserves_value<std::float64_t>()) { + return EXIT_FAILURE; + } #else std::cout << "No std::float64_t type available." << std::endl; #endif
