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-10-06 19:13:28
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/fast_float (Old)
 and      /work/SRC/openSUSE:Factory/.fast_float.new.3698736 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "fast_float"

Tue Oct  6 19:13:28 2026 rev:13 rq:1382390 version:8.3.1

Changes:
--------
--- /work/SRC/openSUSE:Factory/fast_float/fast_float.changes    2026-09-17 
15:15:58.998317685 +0200
+++ /work/SRC/openSUSE:Factory/.fast_float.new.3698736/fast_float.changes       
2026-10-06 19:13:33.527305487 +0200
@@ -1,0 +2,12 @@
+Sun Oct  4 18:19:45 UTC 2026 - Jan Engelhardt <[email protected]>
+
+- Update to release 8.3.1
+  * Keep chars_format::javascript out of the common hot path
+  * Restore the simple loop in fastfloat_strncasecmp
+  * Fix std::float16_t rounding of subnormal ties and fast-path
+    overflow
+  * Average branch misses over the repetitions in the benchmark
+  * Drop the unreachable tail of parse_mantissa
+  * Reorder parsed_number_string_t fields to avoid padding
+
+-------------------------------------------------------------------

Old:
----
  v8.3.0.tar.gz

New:
----
  v8.3.1.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ fast_float.spec ++++++
--- /var/tmp/diff_new_pack.i3A5MG/_old  2026-10-06 19:13:34.405342428 +0200
+++ /var/tmp/diff_new_pack.i3A5MG/_new  2026-10-06 19:13:34.409342596 +0200
@@ -17,7 +17,7 @@
 
 
 Name:           fast_float
-Version:        8.3.0
+Version:        8.3.1
 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.i3A5MG/_old  2026-10-06 19:13:34.459344699 +0200
+++ /var/tmp/diff_new_pack.i3A5MG/_new  2026-10-06 19:13:34.463344868 +0200
@@ -1,5 +1,5 @@
-mtime: 1789507582
-commit: 352a6cb4f14d55bee7899902b668d74c5590bbe812578b7d716f0f0f7c9e39e1
+mtime: 1791138049
+commit: abf21466f60ea271f4d014864cad018ff8717e21b8f4e645f59c8ac5f45ca1ed
 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-10-04 20:20:49.000000000 +0200
@@ -0,0 +1 @@
+.osc

++++++ v8.3.0.tar.gz -> v8.3.1.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fast_float-8.3.0/.github/workflows/alpine.yml 
new/fast_float-8.3.1/.github/workflows/alpine.yml
--- old/fast_float-8.3.0/.github/workflows/alpine.yml   2026-09-15 
17:03:18.000000000 +0200
+++ new/fast_float-8.3.1/.github/workflows/alpine.yml   2026-10-04 
18:26:53.000000000 +0200
@@ -24,7 +24,9 @@
         uses: jirutka/setup-alpine@v1
         with:
           arch: ${{ matrix.arch }}
-          branch: ${{ matrix.arch == 'riscv64' && 'edge' || 'latest-stable' }}
+          # v3.22 is the last release with apk-tools v2; setup-alpine v1 
bootstraps
+          # with apk.static v2, which cannot set up the apk v3 releases.
+          branch: v3.22
           packages: >
             build-base
             cmake
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fast_float-8.3.0/.github/workflows/on-release.yml 
new/fast_float-8.3.1/.github/workflows/on-release.yml
--- old/fast_float-8.3.0/.github/workflows/on-release.yml       2026-09-15 
17:03:18.000000000 +0200
+++ new/fast_float-8.3.1/.github/workflows/on-release.yml       2026-10-04 
18:26:53.000000000 +0200
@@ -17,7 +17,7 @@
 
     steps:
       - uses: actions/checkout@v7
-      
+
       - name: Amalgamate fast_float.h
         run: |
           mkdir build
@@ -29,13 +29,13 @@
           cp tests/string_test.cpp build/
           cd build
           g++ string_test.cpp
+          ./a.out
 
       - name: Upload Release Asset
-        uses: actions/upload-release-asset@v1
+        # --clobber replaces an asset that was already attached by hand, which
+        # would otherwise make the upload fail.
         env:
-          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-        with:
-          upload_url: ${{ github.event.release.upload_url }}
-          asset_path: build/fast_float/fast_float.h
-          asset_name: fast_float.h
-          asset_content_type: text/plain
+          GH_TOKEN: ${{ github.token }}
+        run: |
+          gh release upload "${{ github.event.release.tag_name }}" \
+            build/fast_float/fast_float.h --clobber
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fast_float-8.3.0/.github/workflows/release.yml 
new/fast_float-8.3.1/.github/workflows/release.yml
--- old/fast_float-8.3.0/.github/workflows/release.yml  1970-01-01 
01:00:00.000000000 +0100
+++ new/fast_float-8.3.1/.github/workflows/release.yml  2026-10-04 
18:26:53.000000000 +0200
@@ -0,0 +1,104 @@
+name: Release
+
+# Manually triggered. Bumps the version (patch, minor or major), regenerates
+# the amalgamated fast_float.h, runs the tests, commits the version bump to
+# main, tags it and publishes a GitHub release with fast_float.h attached.
+#
+# Releases created with GITHUB_TOKEN do not trigger other workflows, so
+# on-release.yml does not run for releases made here.
+
+'on':
+  workflow_dispatch:
+    inputs:
+      bump:
+        description: 'Which part of the version to bump'
+        required: true
+        default: 'patch'
+        type: choice
+        options:
+          - patch
+          - minor
+          - major
+
+permissions:
+  contents: write
+
+concurrency:
+  group: release
+  cancel-in-progress: false
+
+jobs:
+  release:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v7
+        with:
+          ref: main
+          fetch-depth: 0
+
+      - name: Compute new version
+        id: version
+        run: |
+          current=$(git describe --abbrev=0 --tags)
+          current=${current#v}
+          IFS=. read -r major minor patch <<< "$current"
+          case "${{ inputs.bump }}" in
+            major) major=$((major + 1)); minor=0; patch=0 ;;
+            minor) minor=$((minor + 1)); patch=0 ;;
+            patch) patch=$((patch + 1)) ;;
+            *) echo "unknown bump type"; exit 1 ;;
+          esac
+          new="$major.$minor.$patch"
+          echo "current=$current" >> "$GITHUB_OUTPUT"
+          echo "new=$new" >> "$GITHUB_OUTPUT"
+          echo "Releasing $current -> $new (${{ inputs.bump }})"
+
+      - name: Bump version files and amalgamate
+        run: |
+          new="${{ steps.version.outputs.new }}"
+          IFS=. read -r major minor patch <<< "$new"
+          python3 script/release.py "$new"
+          rm -f CMakeLists.txt.bak README.md.bak MODULE.bazel.bak \
+                include/fast_float/float_common.h.bak
+          grep -q "project(fast_float VERSION $new LANGUAGES CXX)" 
CMakeLists.txt
+          grep -q "#define FASTFLOAT_VERSION_MAJOR $major$" 
include/fast_float/float_common.h
+          grep -q "#define FASTFLOAT_VERSION_MINOR $minor$" 
include/fast_float/float_common.h
+          grep -q "#define FASTFLOAT_VERSION_PATCH $patch$" 
include/fast_float/float_common.h
+          grep -q "version = \"$new\"," MODULE.bazel
+          grep -q "releases/download/v$new/fast_float.h" README.md
+          grep -q "#define FASTFLOAT_VERSION_PATCH $patch$" fast_float.h
+
+      - name: Test amalgamation
+        run: |
+          mkdir -p amalgamation/fast_float
+          cp fast_float.h amalgamation/fast_float/
+          g++ -O2 -std=c++17 -Iamalgamation -o amalgamation/string_test 
tests/string_test.cpp
+          ./amalgamation/string_test
+
+      - name: Build and test
+        run: |
+          cmake -B build -DFASTFLOAT_TEST=ON -DCMAKE_CXX_FLAGS='-Werror 
-Wundef'
+          cmake --build build --parallel
+          ctest --test-dir build --output-on-failure
+
+      - name: Commit, tag and push
+        run: |
+          new="${{ steps.version.outputs.new }}"
+          git config user.name "github-actions[bot]"
+          git config user.email 
"41898282+github-actions[bot]@users.noreply.github.com"
+          git add CMakeLists.txt README.md MODULE.bazel 
include/fast_float/float_common.h
+          git commit -m "Version $new"
+          git tag -a "v$new" -m "version $new"
+          git push origin main
+          git push origin "v$new"
+
+      - name: Create GitHub release
+        env:
+          GH_TOKEN: ${{ github.token }}
+        run: |
+          new="${{ steps.version.outputs.new }}"
+          gh release create "v$new" \
+            --title "Version $new" \
+            --generate-notes \
+            --notes-start-tag "v${{ steps.version.outputs.current }}" \
+            fast_float.h
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fast_float-8.3.0/CMakeLists.txt 
new/fast_float-8.3.1/CMakeLists.txt
--- old/fast_float-8.3.0/CMakeLists.txt 2026-09-15 17:03:18.000000000 +0200
+++ new/fast_float-8.3.1/CMakeLists.txt 2026-10-04 18:26:53.000000000 +0200
@@ -1,7 +1,7 @@
 cmake_minimum_required(VERSION 3.14)
 
 
-project(fast_float VERSION 8.3.0 LANGUAGES CXX)
+project(fast_float VERSION 8.3.1 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.0/MODULE.bazel 
new/fast_float-8.3.1/MODULE.bazel
--- old/fast_float-8.3.0/MODULE.bazel   2026-09-15 17:03:18.000000000 +0200
+++ new/fast_float-8.3.1/MODULE.bazel   2026-10-04 18:26:53.000000000 +0200
@@ -2,7 +2,7 @@
 
 module(
     name = "fast_float",
-    version = "8.2.4",
+    version = "8.3.1",
     compatibility_level = 8,
 )
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fast_float-8.3.0/README.md 
new/fast_float-8.3.1/README.md
--- old/fast_float-8.3.0/README.md      2026-09-15 17:03:18.000000000 +0200
+++ new/fast_float-8.3.1/README.md      2026-10-04 18:26:53.000000000 +0200
@@ -587,7 +587,7 @@
 FetchContent_Declare(
   fast_float
   GIT_REPOSITORY https://github.com/fastfloat/fast_float.git
-  GIT_TAG tags/v8.3.0
+  GIT_TAG tags/v8.3.1
   GIT_SHALLOW TRUE)
 
 FetchContent_MakeAvailable(fast_float)
@@ -603,7 +603,7 @@
 CPMAddPackage(
   NAME fast_float
   GITHUB_REPOSITORY "fastfloat/fast_float"
-  GIT_TAG v8.3.0)
+  GIT_TAG v8.3.1)
 ```
 
 ## 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.0/fast_float.h>
+<https://github.com/fastfloat/fast_float/releases/download/v8.3.1/fast_float.h>
 
 ## Benchmarking
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fast_float-8.3.0/benchmarks/benchmark.cpp 
new/fast_float-8.3.1/benchmarks/benchmark.cpp
--- old/fast_float-8.3.0/benchmarks/benchmark.cpp       2026-09-15 
17:03:18.000000000 +0200
+++ new/fast_float-8.3.1/benchmarks/benchmark.cpp       2026-10-04 
18:26:53.000000000 +0200
@@ -111,6 +111,7 @@
   instructions_avg /= events.size();
   average_ns /= events.size();
   branches_avg /= events.size();
+  branch_misses_avg /= events.size();
   printf("%-40s: %8.2f MB/s (+/- %.1f %%) ", name.data(),
          volumeMB * 1000000000 / min_ns,
          (average_ns - min_ns) * 100.0 / average_ns);
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fast_float-8.3.0/include/fast_float/ascii_number.h 
new/fast_float-8.3.1/include/fast_float/ascii_number.h
--- old/fast_float-8.3.0/include/fast_float/ascii_number.h      2026-09-15 
17:03:18.000000000 +0200
+++ new/fast_float-8.3.1/include/fast_float/ascii_number.h      2026-10-04 
18:26:53.000000000 +0200
@@ -308,13 +308,16 @@
   int64_t exponent{0};
   uint64_t mantissa{0};
   UC const *lastmatch{nullptr};
+  // The field order matters: placing 'error' next to the booleans avoids
+  // padding, keeping the struct at 64 bytes on 64-bit systems instead of 72.
+  // See https://github.com/fastfloat/fast_float/issues/418
+  parse_error error{parse_error::no_error};
   bool negative{false};
   bool valid{false};
   bool too_many_digits{false};
   // contains the range of the significant digits
   span<UC const> integer{};  // non-nullable
   span<UC const> fraction{}; // nullable
-  parse_error error{parse_error::no_error};
 };
 
 using byte_span = span<char const>;
@@ -339,10 +342,11 @@
 // spans (read only by the rare digit_comp slow path) are not materialized,
 // which keeps the fat parsed_number_string_t off the hot path. The caller
 // re-parses with store_spans=true if the slow path is actually reached.
-template <bool basic_json_fmt, typename UC>
+template <bool basic_json_fmt, bool basic_javascript_fmt, typename UC>
 fastfloat_really_inline FASTFLOAT_CONSTEXPR20 parsed_number_string_t<UC>
-parse_number_string(UC const *p, UC const *pend, parse_options_t<UC> options,
-                    bool store_spans = true) noexcept {
+parse_number_string_impl(UC const *p, UC const *pend,
+                         parse_options_t<UC> options,
+                         bool store_spans) noexcept {
   chars_format const fmt = detail::adjust_for_feature_macros(options.format);
   UC const decimal_point = options.decimal_point;
 
@@ -424,7 +428,7 @@
                                     
parse_error::leading_zeros_in_integer_part);
     }
   }
-  else if (uint64_t(fmt & detail::basic_javascript_fmt)) {
+  else FASTFLOAT_IF_CONSTEXPR17(basic_javascript_fmt) {
     // ECMAScript DecimalIntegerLiteral is "0" or a non-zero digit followed by
     // digits: no leading zeros. Unlike JSON, the integer part may be empty
     // (".5"); the no_digits_in_mantissa check below still rejects ".".
@@ -587,6 +591,40 @@
   return answer;
 }
 
+// Cold instantiation of the parser: the ECMAScript integer-part rule costs two
+// error returns plus the Annex B octal scan, and parse_number_string_impl is
+// force-inlined, so an inlined javascript body would enlarge the frame of 
every
+// caller that never asks for it. Out of line, it costs those callers nothing.
+template <typename UC>
+fastfloat_never_inline FASTFLOAT_CONSTEXPR20 parsed_number_string_t<UC>
+parse_number_string_javascript(UC const *p, UC const *pend,
+                               parse_options_t<UC> options,
+                               bool store_spans) noexcept {
+  return parse_number_string_impl<false, true, UC>(p, pend, options,
+                                                   store_spans);
+}
+
+// Public entry point, behaviour unchanged: chars_format::javascript is still
+// honoured, it is just selected here once instead of being re-tested inside 
the
+// parser loop. Callers that have already ruled the format out (see
+// from_chars_float_advanced) should call parse_number_string_impl directly so
+// that not even this test reaches their hot path.
+template <bool basic_json_fmt, typename UC>
+fastfloat_really_inline FASTFLOAT_CONSTEXPR20 parsed_number_string_t<UC>
+parse_number_string(UC const *p, UC const *pend, parse_options_t<UC> options,
+                    bool store_spans = true) noexcept {
+  // JSON and JavaScript are mutually exclusive, so only the non-JSON
+  // instantiation has to look at the flag.
+  FASTFLOAT_IF_CONSTEXPR17(!basic_json_fmt) {
+    if fastfloat_unlikely (uint64_t(options.format &
+                                    detail::basic_javascript_fmt)) {
+      return parse_number_string_javascript<UC>(p, pend, options, store_spans);
+    }
+  }
+  return parse_number_string_impl<basic_json_fmt, false, UC>(p, pend, options,
+                                                             store_spans);
+}
+
 template <typename T, typename UC>
 fastfloat_really_inline FASTFLOAT_CONSTEXPR20 from_chars_result_t<UC>
 parse_int_string(UC const *p, UC const *pend, T &value,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/fast_float-8.3.0/include/fast_float/decimal_to_binary.h 
new/fast_float-8.3.1/include/fast_float/decimal_to_binary.h
--- old/fast_float-8.3.0/include/fast_float/decimal_to_binary.h 2026-09-15 
17:03:18.000000000 +0200
+++ new/fast_float-8.3.1/include/fast_float/decimal_to_binary.h 2026-10-04 
18:26:53.000000000 +0200
@@ -156,10 +156,21 @@
       return answer;
     }
     // next line is safe because -answer.power2 + 1 < 64
-    answer.mantissa >>= -answer.power2 + 1;
-    // Thankfully, we can't have both "round-to-even" and subnormals because
-    // "round-to-even" only occurs for powers close to 0 in the 32-bit and
-    // and 64-bit case (with no more than 19 digits).
+    int const subnormal_shift = -answer.power2 + 1;
+    answer.mantissa >>= subnormal_shift;
+    // A subnormal result can also fall exactly between two floats. With at
+    // most 19 digits this never happens for float and double, but it does for
+    // std::float16_t (e.g., 2^-25 = 298023223876953125e-25), so we apply the
+    // same round-to-even test as in the normal case below.
+    // See script/format_parameters.py.
+    if (binary::subnormal_ties_possible() && (product.low <= 1) &&
+        (q >= binary::min_exponent_round_to_even()) &&
+        (q <= binary::max_exponent_round_to_even()) &&
+        ((answer.mantissa & 3) == 1)) {
+      if (((answer.mantissa << subnormal_shift) << shift) == product.high) {
+        answer.mantissa &= ~uint64_t(1); // flip it so that we do not round up
+      }
+    }
     answer.mantissa += (answer.mantissa & 1); // round up
     answer.mantissa >>= 1;
     // There is a weird scenario where we don't have a subnormal but just.
@@ -194,16 +205,31 @@
 
   answer.mantissa += (answer.mantissa & 1); // round up
   answer.mantissa >>= 1;
-  if (answer.mantissa >= (uint64_t(2) << binary::mantissa_explicit_bits())) {
+  // Both fix-ups below are rare. They are marked unlikely so that clang keeps
+  // them as branches instead of folding them into conditional moves that
+  // every conversion pays for.
+#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_clang_unlikely (answer.mantissa >=
+                               (uint64_t(2)
+                                << binary::mantissa_explicit_bits())) {
     answer.mantissa = (uint64_t(1) << binary::mantissa_explicit_bits());
     answer.power2++; // undo previous addition
   }
 
   answer.mantissa &= ~(uint64_t(1) << binary::mantissa_explicit_bits());
-  if (answer.power2 >= binary::infinite_power()) { // infinity
+  if fastfloat_clang_unlikely (answer.power2 >= binary::infinite_power()) {
+    // infinity
     answer.power2 = binary::infinite_power();
     answer.mantissa = 0;
   }
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif
   return answer;
 }
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/fast_float-8.3.0/include/fast_float/digit_comparison.h 
new/fast_float-8.3.1/include/fast_float/digit_comparison.h
--- old/fast_float-8.3.0/include/fast_float/digit_comparison.h  2026-09-15 
17:03:18.000000000 +0200
+++ new/fast_float-8.3.1/include/fast_float/digit_comparison.h  2026-10-04 
18:26:53.000000000 +0200
@@ -330,10 +330,6 @@
       }
     }
   }
-
-  if (counter != 0) {
-    add_native(result, limb(powers_of_ten_uint64[counter]), value);
-  }
 }
 
 template <typename T>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fast_float-8.3.0/include/fast_float/float_common.h 
new/fast_float-8.3.1/include/fast_float/float_common.h
--- old/fast_float-8.3.0/include/fast_float/float_common.h      2026-09-15 
17:03:18.000000000 +0200
+++ new/fast_float-8.3.1/include/fast_float/float_common.h      2026-10-04 
18:26:53.000000000 +0200
@@ -18,7 +18,7 @@
 
 #define FASTFLOAT_VERSION_MAJOR 8
 #define FASTFLOAT_VERSION_MINOR 3
-#define FASTFLOAT_VERSION_PATCH 0
+#define FASTFLOAT_VERSION_PATCH 1
 
 #define FASTFLOAT_STRINGIZE_IMPL(x) #x
 #define FASTFLOAT_STRINGIZE(x) FASTFLOAT_STRINGIZE_IMPL(x)
@@ -217,6 +217,38 @@
 #define fastfloat_really_inline inline __attribute__((always_inline))
 #endif
 
+// Opposite of fastfloat_really_inline. Used for a rare, format-specific 
variant
+// of a force-inlined function, so that its body does not land in the frame of
+// every caller that will never execute it.
+#ifdef FASTFLOAT_VISUAL_STUDIO
+#define fastfloat_never_inline __declspec(noinline)
+#elif defined(__GNUC__) || defined(__clang__)
+#define fastfloat_never_inline __attribute__((noinline))
+#else
+#define fastfloat_never_inline
+#endif
+
+// fastfloat_really_inline under clang only, a no-op elsewhere. Clang declines
+// to inline the parser into its callers, so the parser instantiation with a
+// compile-time format (from_chars_fixed_format) must force the one thin
+// forwarder between it and the parser body. GCC inlines the chain on its own,
+// and forcing it there changes its inlining order for the worse.
+#ifdef __clang__
+#define fastfloat_clang_really_inline fastfloat_really_inline
+#else
+#define fastfloat_clang_really_inline
+#endif
+
+// fastfloat_unlikely under clang only, a plain condition elsewhere. Clang
+// if-converts rare checks into chains of conditional moves that every
+// conversion executes; the hint keeps them as branches. GCC already emits
+// branches there, and the hint only reorders its blocks, for the worse.
+#ifdef __clang__
+#define fastfloat_clang_unlikely(x) fastfloat_unlikely(x)
+#else
+#define fastfloat_clang_unlikely(x) (x)
+#endif
+
 // Branch-probability hint marking the rare slow-path branches as cold, so the
 // optimizer keeps the out-of-line slow-path re-parse off the hot path (and 
does
 // not duplicate the force-inlined hot scanner into the caller, which bloated
@@ -314,147 +346,22 @@
                              > {
 };
 
-template <typename UC>
-inline FASTFLOAT_CONSTEXPR14 bool
-fastfloat_strncasecmp3(UC const *actual_mixedcase,
-                       UC const *expected_lowercase) {
-  uint64_t mask{0};
-  FASTFLOAT_IF_CONSTEXPR17(sizeof(UC) == 1) { mask = 0x2020202020202020; }
-  else FASTFLOAT_IF_CONSTEXPR17(sizeof(UC) == 2) {
-    mask = 0x0020002000200020;
-  }
-  else FASTFLOAT_IF_CONSTEXPR17(sizeof(UC) == 4) {
-    mask = 0x0000002000000020;
-  }
-  else {
-    return false;
-  }
-
-  uint64_t val1{0}, val2{0};
-  if (cpp20_and_in_constexpr()) {
-    for (size_t i = 0; i < 3; i++) {
-      if ((actual_mixedcase[i] | 32) != expected_lowercase[i]) {
-        return false;
-      }
-    }
-    return true;
-  } else {
-    FASTFLOAT_IF_CONSTEXPR17(sizeof(UC) == 1 || sizeof(UC) == 2) {
-      ::memcpy(&val1, actual_mixedcase, 3 * sizeof(UC));
-      ::memcpy(&val2, expected_lowercase, 3 * sizeof(UC));
-      val1 |= mask;
-      val2 |= mask;
-      return val1 == val2;
-    }
-    else FASTFLOAT_IF_CONSTEXPR17(sizeof(UC) == 4) {
-      ::memcpy(&val1, actual_mixedcase, 2 * sizeof(UC));
-      ::memcpy(&val2, expected_lowercase, 2 * sizeof(UC));
-      val1 |= mask;
-      if (val1 != val2) {
-        return false;
-      }
-      return (actual_mixedcase[2] | 32) == (expected_lowercase[2]);
-    }
-    else {
-      return false;
-    }
-  }
-}
-
-template <typename UC>
-inline FASTFLOAT_CONSTEXPR14 bool
-fastfloat_strncasecmp5(UC const *actual_mixedcase,
-                       UC const *expected_lowercase) {
-  uint64_t mask{0};
-  uint64_t val1{0}, val2{0};
-  if (cpp20_and_in_constexpr()) {
-    for (size_t i = 0; i < 5; i++) {
-      if ((actual_mixedcase[i] | 32) != expected_lowercase[i]) {
-        return false;
-      }
-    }
-    return true;
-  } else {
-    FASTFLOAT_IF_CONSTEXPR17(sizeof(UC) == 1) {
-      mask = 0x2020202020202020;
-      ::memcpy(&val1, actual_mixedcase, 5 * sizeof(UC));
-      ::memcpy(&val2, expected_lowercase, 5 * sizeof(UC));
-      val1 |= mask;
-      val2 |= mask;
-      return val1 == val2;
-    }
-    else FASTFLOAT_IF_CONSTEXPR17(sizeof(UC) == 2) {
-      mask = 0x0020002000200020;
-      ::memcpy(&val1, actual_mixedcase, 4 * sizeof(UC));
-      ::memcpy(&val2, expected_lowercase, 4 * sizeof(UC));
-      val1 |= mask;
-      if (val1 != val2) {
-        return false;
-      }
-      return (actual_mixedcase[4] | 32) == (expected_lowercase[4]);
-    }
-    else FASTFLOAT_IF_CONSTEXPR17(sizeof(UC) == 4) {
-      mask = 0x0000002000000020;
-      ::memcpy(&val1, actual_mixedcase, 2 * sizeof(UC));
-      ::memcpy(&val2, expected_lowercase, 2 * sizeof(UC));
-      val1 |= mask;
-      if (val1 != val2) {
-        return false;
-      }
-      ::memcpy(&val1, actual_mixedcase + 2, 2 * sizeof(UC));
-      ::memcpy(&val2, expected_lowercase + 2, 2 * sizeof(UC));
-      val1 |= mask;
-      if (val1 != val2) {
-        return false;
-      }
-      return (actual_mixedcase[4] | 32) == (expected_lowercase[4]);
-    }
-    else {
-      return false;
-    }
-  }
-}
-
-// Compares two ASCII strings in a case insensitive manner.
+// Compares two ASCII strings in a case insensitive manner. The expected
+// string is lowercase ASCII, so OR-ing 0x20 into each actual character maps
+// 'A'..'Z' onto 'a'..'z' and leaves everything else mismatched. The lengths
+// used are tiny constants (3 and 5), so the compiler unrolls this loop; keep
+// it small so that the (rarely executed) inf/nan path does not bloat the hot
+// parser it is inlined into.
 template <typename UC>
 inline FASTFLOAT_CONSTEXPR14 bool
 fastfloat_strncasecmp(UC const *actual_mixedcase, UC const *expected_lowercase,
                       size_t length) {
-  uint64_t mask{0};
-  FASTFLOAT_IF_CONSTEXPR17(sizeof(UC) == 1) { mask = 0x2020202020202020; }
-  else FASTFLOAT_IF_CONSTEXPR17(sizeof(UC) == 2) {
-    mask = 0x0020002000200020;
-  }
-  else FASTFLOAT_IF_CONSTEXPR17(sizeof(UC) == 4) {
-    mask = 0x0000002000000020;
-  }
-  else {
-    return false;
-  }
-
-  if (cpp20_and_in_constexpr()) {
-    for (size_t i = 0; i < length; i++) {
-      if ((actual_mixedcase[i] | 32) != expected_lowercase[i]) {
-        return false;
-      }
-    }
-    return true;
-  } else {
-    uint64_t val1{0}, val2{0};
-    size_t sz{8 / (sizeof(UC))};
-    for (size_t i = 0; i < length; i += sz) {
-      val1 = val2 = 0;
-      sz = sz < (length - i) ? sz : length - i;
-      ::memcpy(&val1, actual_mixedcase + i, sz * sizeof(UC));
-      ::memcpy(&val2, expected_lowercase + i, sz * sizeof(UC));
-      val1 |= mask;
-      val2 |= mask;
-      if (val1 != val2) {
-        return false;
-      }
+  for (size_t i = 0; i < length; ++i) {
+    if ((actual_mixedcase[i] | 32) != expected_lowercase[i]) {
+      return false;
     }
-    return true;
   }
+  return true;
 }
 
 #ifndef FLT_EVAL_METHOD
@@ -681,6 +588,8 @@
   static constexpr uint64_t max_mantissa_fast_path(int64_t power);
   static constexpr uint64_t
   max_mantissa_fast_path(); // used when fegetround() == FE_TONEAREST
+  static constexpr bool fast_path_can_overflow();
+  static constexpr bool subnormal_ties_possible();
   static constexpr int largest_power_of_ten();
   static constexpr int smallest_power_of_ten();
   static constexpr T exact_power_of_ten(int64_t power);
@@ -712,6 +621,7 @@
       0x20000000000000 / (constant_55555 * constant_55555 * 5),
       0x20000000000000 / (constant_55555 * constant_55555 * 5 * 5),
       0x20000000000000 / (constant_55555 * constant_55555 * 5 * 5 * 5),
+      0x20000000000000 / (constant_55555 * constant_55555 * 5 * 5 * 5 * 5),
       0x20000000000000 / (constant_55555 * constant_55555 * constant_55555),
       0x20000000000000 / (constant_55555 * constant_55555 * constant_55555 * 
5),
       0x20000000000000 /
@@ -727,9 +637,7 @@
       0x20000000000000 / (constant_55555 * constant_55555 * constant_55555 *
                           constant_55555 * 5 * 5),
       0x20000000000000 / (constant_55555 * constant_55555 * constant_55555 *
-                          constant_55555 * 5 * 5 * 5),
-      0x20000000000000 / (constant_55555 * constant_55555 * constant_55555 *
-                          constant_55555 * 5 * 5 * 5 * 5)};
+                          constant_55555 * 5 * 5 * 5)};
 };
 
 #if FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE
@@ -946,7 +854,9 @@
 
 template <>
 inline constexpr int binary_format<std::float16_t>::min_exponent_fast_path() {
-  return 0;
+  // w / 10^k with w <= 2^11 and k <= 4 rounds correctly even when evaluated
+  // in float or double first (checked in script/format_parameters.py).
+  return -4;
 }
 
 template <>
@@ -958,7 +868,9 @@
 template <>
 inline constexpr int
 binary_format<std::float16_t>::min_exponent_round_to_even() {
-  return -22;
+  // -22 covers the normal ties; subnormal ties such as
+  // 2^-25 = 298023223876953125e-25 need q = -25 and q = -26.
+  return -26;
 }
 
 template <>
@@ -982,7 +894,8 @@
 
 template <>
 inline constexpr int binary_format<std::float16_t>::smallest_power_of_ten() {
-  return -27;
+  // (10^19 - 1) * 10^-27 < 2^-25, so any q < -26 rounds to zero.
+  return -26;
 }
 
 template <>
@@ -1069,7 +982,8 @@
 
 template <>
 inline constexpr int binary_format<std::bfloat16_t>::min_exponent_fast_path() {
-  return 0;
+  // Same argument as for std::float16_t (w <= 2^8, k <= 3).
+  return -3;
 }
 
 template <>
@@ -1105,7 +1019,8 @@
 
 template <>
 inline constexpr int binary_format<std::bfloat16_t>::smallest_power_of_ten() {
-  return -60;
+  // (10^19 - 1) * 10^-60 < 2^-134, so any q < -59 rounds to zero.
+  return -59;
 }
 
 template <>
@@ -1114,6 +1029,25 @@
 }
 #endif // __STDCPP_BFLOAT16_T__
 
+// Whether Clinger's fast path can overflow: only for std::float16_t, where
+// 2^11 * 10^4 > 65504.
+template <typename T>
+inline constexpr bool binary_format<T>::fast_path_can_overflow() {
+  return double(max_mantissa_fast_path()) *
+             double(exact_power_of_ten(max_exponent_fast_path())) >
+         double((std::numeric_limits<T>::max)());
+}
+
+// A subnormal needs w * 10^q < 2^(minimum_exponent() + 1), so q is at most
+// (minimum_exponent() + 1) * log10(2), with 1233/4096 < log10(2). Only
+// std::float16_t has such q in its round-to-even range. We compare
+// 4096 * q with (minimum_exponent() + 1) * 1233 to avoid right-shifting a
+// negative value (implementation-defined before C++20).
+template <typename T>
+inline constexpr bool binary_format<T>::subnormal_ties_possible() {
+  return min_exponent_round_to_even() * 4096 <= (minimum_exponent() + 1) * 
1233;
+}
+
 template <>
 inline constexpr uint64_t
 binary_format<double>::max_mantissa_fast_path(int64_t power) {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fast_float-8.3.0/include/fast_float/parse_number.h 
new/fast_float-8.3.1/include/fast_float/parse_number.h
--- old/fast_float-8.3.0/include/fast_float/parse_number.h      2026-09-15 
17:03:18.000000000 +0200
+++ new/fast_float-8.3.1/include/fast_float/parse_number.h      2026-10-04 
18:26:53.000000000 +0200
@@ -35,7 +35,7 @@
     ++first;
   }
   if (last - first >= 3) {
-    if (fastfloat_strncasecmp3(first, str_const_nan<UC>())) {
+    if (fastfloat_strncasecmp(first, str_const_nan<UC>(), 3)) {
       answer.ptr = (first += 3);
       value = minusSign ? -std::numeric_limits<T>::quiet_NaN()
                         : std::numeric_limits<T>::quiet_NaN();
@@ -54,9 +54,9 @@
       }
       return answer;
     }
-    if (fastfloat_strncasecmp3(first, str_const_inf<UC>())) {
+    if (fastfloat_strncasecmp(first, str_const_inf<UC>(), 3)) {
       if ((last - first >= 8) &&
-          fastfloat_strncasecmp5(first + 3, str_const_inf<UC>() + 3)) {
+          fastfloat_strncasecmp(first + 3, str_const_inf<UC>() + 3, 5)) {
         answer.ptr = first + 8;
       } else {
         answer.ptr = first + 3;
@@ -139,9 +139,10 @@
 
 template <typename T> struct from_chars_caller {
   template <typename UC>
-  FASTFLOAT_CONSTEXPR20 static from_chars_result_t<UC>
-  call(UC const *first, UC const *last, T &value,
-       parse_options_t<UC> options) noexcept {
+  fastfloat_clang_really_inline
+      FASTFLOAT_CONSTEXPR20 static from_chars_result_t<UC>
+      call(UC const *first, UC const *last, T &value,
+           parse_options_t<UC> options) noexcept {
     return from_chars_advanced(first, last, value, options);
   }
 };
@@ -180,10 +181,29 @@
 };
 #endif
 
+#ifdef __clang__
+// Parser instantiated for a format fixed at compile time, so that every test
+// on the format folds away inside it. GCC gets the same effect by inlining the
+// whole parser into each caller, where the format is a constant; clang keeps
+// it out of line and would otherwise re-test each format flag per conversion.
+template <typename T, typename UC, chars_format Fmt>
+FASTFLOAT_CONSTEXPR20 from_chars_result_t<UC>
+from_chars_fixed_format(UC const *first, UC const *last, T &value) noexcept {
+  return from_chars_caller<T>::call(first, last, value,
+                                    parse_options_t<UC>(Fmt));
+}
+#endif
+
 template <typename T, typename UC, typename>
 FASTFLOAT_CONSTEXPR20 from_chars_result_t<UC>
 from_chars(UC const *first, UC const *last, T &value,
            chars_format fmt /*= chars_format::general*/) noexcept {
+#ifdef __clang__
+  if (fmt == chars_format::general) {
+    return from_chars_fixed_format<T, UC, chars_format::general>(first, last,
+                                                                 value);
+  }
+#endif
   return from_chars_caller<T>::call(first, last, value,
                                     parse_options_t<UC>(fmt));
 }
@@ -221,6 +241,12 @@
         value = value / binary_format<T>::exact_power_of_ten(-exponent);
       } else {
         value = value * binary_format<T>::exact_power_of_ten(exponent);
+        // Only std::float16_t can overflow here (e.g., "656e2"); let the
+        // slow path report result_out_of_range.
+        if (binary_format<T>::fast_path_can_overflow() &&
+            value > (std::numeric_limits<T>::max)()) {
+          return false;
+        }
       }
       if (is_negative) {
         value = -value;
@@ -306,8 +332,41 @@
 parse_number_slow_path(UC const *first, UC const *last, T &value,
                        parse_options_t<UC> options, bool bjf) noexcept {
   parsed_number_string_t<UC> pns =
-      bjf ? parse_number_string<true, UC>(first, last, options, true)
-          : parse_number_string<false, UC>(first, last, options, true);
+      bjf ? parse_number_string_impl<true, false, UC>(first, last, options,
+                                                      true)
+          : parse_number_string_impl<false, false, UC>(first, last, options,
+                                                       true);
+  return from_chars_advanced(pns, value);
+}
+
+// Cold: the whole chars_format::javascript conversion. Kept out of line and 
out
+// of from_chars_float_advanced so the common frame holds neither the 
javascript
+// parser body nor a call that would force the parsed number string onto the
+// stack. Mirrors the main path, only with the javascript parser.
+template <typename T, typename UC>
+fastfloat_never_inline FASTFLOAT_CONSTEXPR20 from_chars_result_t<UC>
+from_chars_float_javascript(UC const *first, UC const *last, T &value,
+                            parse_options_t<UC> options) noexcept {
+  chars_format const fmt = detail::adjust_for_feature_macros(options.format);
+  if (uint64_t(fmt & chars_format::skip_white_space)) {
+    while ((first != last) && fast_float::is_space(*first)) {
+      first++;
+    }
+  }
+  from_chars_result_t<UC> answer;
+  answer.ec = std::errc::invalid_argument;
+  answer.ptr = first;
+  if (first == last) {
+    return answer;
+  }
+  parsed_number_string_t<UC> pns =
+      parse_number_string_javascript<UC>(first, last, options, true);
+  if (!pns.valid) {
+    if (uint64_t(fmt & chars_format::no_infnan)) {
+      return answer;
+    }
+    return detail::parse_infnan(first, last, value, fmt);
+  }
   return from_chars_advanced(pns, value);
 }
 
@@ -323,6 +382,13 @@
 
   chars_format const fmt = detail::adjust_for_feature_macros(options.format);
 
+  // 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.
+  if fastfloat_unlikely (uint64_t(fmt & detail::basic_javascript_fmt)) {
+    return from_chars_float_javascript<T, UC>(first, last, value, options);
+  }
+
   from_chars_result_t<UC> answer;
   if (uint64_t(fmt & chars_format::skip_white_space)) {
     while ((first != last) && fast_float::is_space(*first)) {
@@ -339,10 +405,12 @@
   // Fast path: parse WITHOUT materializing the integer/fraction spans (read
   // only by the rare slow paths). Skipping their stores keeps the fat
   // parsed_number_string_t off the hot path. store_spans is a runtime 
argument,
-  // so this reuses the single parse_number_string instantiation.
+  // so this reuses the single parse_number_string_impl instantiation.
   parsed_number_string_t<UC> pns =
-      bjf ? parse_number_string<true, UC>(first, last, options, false)
-          : parse_number_string<false, UC>(first, last, options, false);
+      bjf ? parse_number_string_impl<true, false, UC>(first, last, options,
+                                                      false)
+          : parse_number_string_impl<false, false, UC>(first, last, options,
+                                                       false);
   if (!pns.valid) {
     if (uint64_t(fmt & chars_format::no_infnan)) {
       answer.ec = std::errc::invalid_argument;
@@ -386,15 +454,19 @@
   if fastfloat_unlikely (am.power2 < 0) {
     return parse_number_slow_path<T, UC>(first, last, value, options, bjf);
   }
-#ifdef __clang__
-#pragma clang diagnostic pop
-#endif
   to_float(pns.negative, am, value);
-  // Test for over/underflow.
-  if ((pns.mantissa != 0 && am.mantissa == 0 && am.power2 == 0) ||
-      am.power2 == binary_format<T>::infinite_power()) {
+  // Test for over/underflow. Marked unlikely so that clang keeps it as a
+  // branch instead of folding it into a chain of conditional moves that
+  // every conversion pays for.
+  if fastfloat_clang_unlikely ((pns.mantissa != 0 && am.mantissa == 0 &&
+                                am.power2 == 0) ||
+                               am.power2 ==
+                                   binary_format<T>::infinite_power()) {
     answer.ec = std::errc::result_out_of_range;
   }
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif
   return answer;
 }
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fast_float-8.3.0/script/format_parameters.py 
new/fast_float-8.3.1/script/format_parameters.py
--- old/fast_float-8.3.0/script/format_parameters.py    1970-01-01 
01:00:00.000000000 +0100
+++ new/fast_float-8.3.1/script/format_parameters.py    2026-10-04 
18:26:53.000000000 +0200
@@ -0,0 +1,416 @@
+#
+# Checks the binary_format<T> constants in include/fast_float/float_common.h
+# with exact integer arithmetic, for double, float, std::float16_t and
+# std::bfloat16_t. Every string is parsed as w * 10^q with w < 10^19, so each
+# constant has a condition on (w, q) that it must satisfy.
+#
+# References:
+# Daniel Lemire, Number Parsing at a Gigabyte per Second,
+# Software: Practice and Experience 51 (8), 2021 
https://arxiv.org/abs/2101.11408
+# Noble Mushtak and Daniel Lemire, Fast Number Parsing Without Fallback,
+# Software: Practice and Experience 53 (6), 2023 
https://arxiv.org/abs/2212.06644
+#
+# Usage: python3 script/format_parameters.py [--types float16,bfloat16]
+#        [--sweep-width 3] [--jobs N]
+#
+
+import argparse
+import multiprocessing
+import os
+import re
+import sys
+
+HEADER = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..",
+                      "include", "fast_float", "float_common.h")
+MASK64 = (1 << 64) - 1
+MAX_W = 10**19
+CXX_TYPES = {"double": "double", "float": "float",
+             "float16": "std::float16_t", "bfloat16": "std::bfloat16_t"}
+
+
+class Format:
+    def __init__(self, name):
+        self.name = name
+        cxx = CXX_TYPES[name]
+        with open(HEADER) as f:
+            text = f.read()
+        pattern = r"binary_format<" + re.escape(cxx) + 
r">::(\w+)\(\)\s*\{(.*?)\n\}"
+        for fn, body in re.findall(pattern, text, re.S):
+            returns = re.findall(r"return\s+([^;]+);", body)
+            # the last return is the FLT_EVAL_METHOD 0/1 branch
+            if returns and re.fullmatch(r"-?(0x[0-9A-Fa-f]+|\d+)", 
returns[-1].strip()):
+                setattr(self, fn, int(returns[-1], 0))
+        tables = re.search(r"binary_format_lookup_tables<" + re.escape(cxx) +
+                           r", U>\s*\{(.*?)\n\};", text, re.S).group(1)
+        powers = re.search(r"powers_of_ten\[\]\s*=\s*\{(.*?)\}", tables, 
re.S).group(1)
+        self.powers_of_ten = [float(x) for x in
+                              re.sub(r"(bf16|f16|f)\b", "", powers).split(",") 
if x.strip()]
+        mant = re.search(r"max_mantissa\[\]\s*=\s*\{(.*?)\}", tables, 
re.S).group(1)
+        mant = re.sub(r"//[^\n]*", "", mant).replace("/", "//").replace("\n", 
" ")
+        self.max_mantissa = [eval(x.strip(), {"constant_55555": 5**5})
+                             for x in mant.split(",") if x.strip()]
+        self.p = self.mantissa_explicit_bits
+        self.bias = self.p - self.minimum_exponent
+        self.denorm_min_exp = 1 - self.bias  # smallest subnormal is 
2^denorm_min_exp
+        self.emin = self.minimum_exponent + 1  # leading bit of the smallest 
normal
+        self.emax = self.infinite_power - 1 + self.minimum_exponent
+
+    def grid(self):
+        """Every finite value and every midpoint as (num, exp2, kind) with
+        kind in value, tie_down (even lower neighbour), tie_up, overflow."""
+        out = []
+        two_p = 1 << self.p
+        d = self.denorm_min_exp
+        for m in range(1, two_p):
+            out.append((m, d, "value"))
+        for m in range(two_p):
+            out.append((2 * m + 1, d - 1, "tie_down" if m % 2 == 0 else 
"tie_up"))
+        for e in range(self.emin, self.emax + 1):
+            for f in range(two_p):
+                out.append((two_p + f, e - self.p, "value"))
+                kind = "tie_down" if f % 2 == 0 else "tie_up"
+                if e == self.emax and f == two_p - 1:
+                    kind = "overflow"
+                out.append((2 * (two_p + f) + 1, e - self.p - 1, kind))
+        return out
+
+
+def ratio(num, exp2):
+    return (num << exp2, 1) if exp2 >= 0 else (num, 1 << -exp2)
+
+
+def ratio10(w, q):
+    return (w * 10**q, 1) if q >= 0 else (w, 10**-q)
+
+
+def le(n1, d1, n2, d2):
+    return n1 * d2 <= n2 * d1
+
+
+def floor_log2(n, d):
+    e = n.bit_length() - d.bit_length()
+    if (d << e if e >= 0 else d) > (n if e >= 0 else n << -e):
+        e -= 1
+    return e
+
+
+def round_ties_even(n, d):
+    m, r = divmod(n, d)
+    if 2 * r > d or (2 * r == d and m & 1):
+        m += 1
+    return m
+
+
+def reference_bits(fmt, n, d):
+    """Correctly rounded bit pattern of n/d >= 0."""
+    if n == 0:
+        return 0
+    e = floor_log2(n, d)
+    s = fmt.denorm_min_exp if e < fmt.emin else e - fmt.p
+    m = round_ties_even(n, d << s) if s >= 0 else round_ties_even(n << -s, d)
+    if e < fmt.emin:
+        return m
+    if m == 2 << fmt.p:
+        m >>= 1
+        e += 1
+    if e > fmt.emax:
+        return fmt.infinite_power << fmt.p
+    return ((e - fmt.minimum_exponent) << fmt.p) | (m - (1 << fmt.p))
+
+
+def ceil_log2(x):
+    z = 0
+    while (1 << z) < x:
+        z += 1
+    return z
+
+
+def table_entry(q):
+    """T[q] as stored in fast_table.h (see table_generation.py)."""
+    if q < 0:
+        power5 = 5**-q
+        z = ceil_log2(power5)
+        b = z + 127 if q >= -27 else 2 * z + 128
+        c = 2**b // power5 + 1
+        while c >= 1 << 128:
+            c //= 2
+        return c
+    power5 = 5**q
+    while power5 < 1 << 127:
+        power5 *= 2
+    while power5 >= 1 << 128:
+        power5 //= 2
+    return power5
+
+
+TABLE = {q: table_entry(q) for q in range(-342, 309)}
+
+
+def compute_product_approximation(q, w, bit_precision):
+    t = TABLE[q]
+    first = w * (t >> 64)
+    high, low = first >> 64, first & MASK64
+    if (high & (MASK64 >> bit_precision)) == MASK64 >> bit_precision:
+        second_high = (w * (t & MASK64)) >> 64
+        low = (low + second_high) & MASK64
+        if second_high > low:
+            high += 1
+    return high, low
+
+
+def compute_float(fmt, q, w, subnormal_tie_test=True):
+    """Model of compute_float in decimal_to_binary.h. Returns
+    (mantissa, power2, truncated mantissa, k) with truncated == floor(w 10^q 
2^k)."""
+    if w == 0 or q < fmt.smallest_power_of_ten:
+        return 0, 0, None, None
+    if q > fmt.largest_power_of_ten:
+        return 0, fmt.infinite_power, None, None
+    lz = 64 - w.bit_length()
+    w <<= lz
+    high, low = compute_product_approximation(q, w, fmt.p + 3)
+    upperbit = high >> 63
+    shift = upperbit + 64 - fmt.p - 3
+    mantissa = high >> shift
+    power2 = ((152170 + 65536) * q >> 16) + 63 + upperbit - lz - 
fmt.minimum_exponent
+    tie_q = fmt.min_exponent_round_to_even <= q <= 
fmt.max_exponent_round_to_even
+    if power2 <= 0:
+        if -power2 + 1 >= 64:
+            return 0, 0, None, None
+        extra = -power2 + 1
+        mantissa >>= extra
+        truncated, k = mantissa, fmt.bias
+        if (subnormal_tie_test and low <= 1 and tie_q and (mantissa & 3) == 1
+                and ((mantissa << extra) << shift) == high):
+            mantissa &= ~1
+        mantissa += mantissa & 1
+        mantissa >>= 1
+        return mantissa, int(mantissa >= 1 << fmt.p), truncated, k
+    truncated, k = mantissa, 1 + fmt.bias - power2
+    if low <= 1 and tie_q and (mantissa & 3) == 1 and (mantissa << shift) == 
high:
+        mantissa &= ~1
+    mantissa += mantissa & 1
+    mantissa >>= 1
+    if mantissa >= 2 << fmt.p:
+        mantissa = 1 << fmt.p
+        power2 += 1
+    mantissa &= ~(1 << fmt.p)
+    if power2 >= fmt.infinite_power:
+        return 0, fmt.infinite_power, truncated, k
+    return mantissa, power2, truncated, k
+
+
+def parsed_bits(fmt, w, q, subnormal_tie_test=True):
+    """from_chars on the digits w * 10^q. Clinger's fast path is one correctly
+    rounded operation on exact operands, so it equals the reference."""
+    if fmt.min_exponent_fast_path <= q <= fmt.max_exponent_fast_path and w <= 
2 << fmt.p:
+        return reference_bits(fmt, *ratio10(w, q)), None, None
+    mantissa, power2, truncated, k = compute_float(fmt, q, w, 
subnormal_tie_test)
+    return mantissa | (power2 << fmt.p), truncated, k
+
+
+failures = 0
+
+
+def check(ok, text):
+    global failures
+    print(("  ok    " if ok else "  FAIL  ") + text)
+    failures += not ok
+
+
+def note(text):
+    print("  note  " + text)
+
+
+def continued_fraction(numer, denom):
+    cf = []
+    while denom:
+        quot, rem = divmod(numer, denom)
+        cf.append(quot)
+        numer, denom = denom, rem
+    return cf
+
+
+def convergents(cf):
+    p2, q2, p1, q1 = 0, 1, 1, 0
+    for a in cf:
+        p1, p2, q1, q2 = a * p1 + p2, p1, a * q1 + q2, q1
+        yield p1, q1
+
+
+def check_constants(fmt):
+    half_denorm = ratio(1, fmt.denorm_min_exp - 1)
+    q = fmt.smallest_power_of_ten
+    check(le(*ratio10(MAX_W - 1, q - 1), *half_denorm),
+          "smallest_power_of_ten=%d: q < %d rounds to zero" % (q, q))
+    check(not le(*ratio10(MAX_W - 1, q), *half_denorm),
+          "smallest_power_of_ten is tight")
+    threshold = ratio((4 << fmt.p) - 1, fmt.emax - fmt.p - 1)  # max + ulp/2
+    q = fmt.largest_power_of_ten
+    check(le(*threshold, *ratio10(1, q + 1)),
+          "largest_power_of_ten=%d: q > %d overflows" % (q, q))
+    check(not le(*threshold, *ratio10(1, q)), "largest_power_of_ten is tight")
+
+    two_p1 = 2 << fmt.p
+    q = fmt.max_exponent_fast_path
+    check(5**q <= two_p1 < 5 ** (q + 1),
+          "max_exponent_fast_path=%d: 10^q exact in T iff q <= %d" % (q, q))
+    check(fmt.powers_of_ten[: q + 1] == [10.0**i for i in range(q + 1)], 
"powers_of_ten")
+    check(fmt.max_mantissa[: q + 1] == [two_p1 // 5**i for i in range(q + 1)],
+          "max_mantissa[q] == floor(2^%d / 5^q)" % (fmt.p + 1))
+    overflow = not le(two_p1 * 10**q, 1, *ratio(two_p1 - 1, fmt.emax - fmt.p))
+    note("fast path product 2^%d * 10^%d %s the largest finite value"
+         % (fmt.p + 1, q, "exceeds" if overflow else "is below"))
+    k = -fmt.min_exponent_fast_path
+    check(0 <= k <= fmt.max_exponent_fast_path, "min_exponent_fast_path=%d" % 
-k)
+    if fmt.p <= 10:
+        # double rounding: w / 10^k rounded to `wide` bits, then to T
+        for wide in (24, 53, 64):
+            bad = 0
+            for kk in range(1, fmt.max_exponent_fast_path + 1):
+                for w in range(1, two_p1 + 1):
+                    e = floor_log2(w, 10**kk)
+                    s = e - wide + 1
+                    m = round_ties_even(w, 10**kk << s) if s >= 0 else 
round_ties_even(w << -s, 10**kk)
+                    if reference_bits(fmt, *ratio(m, s)) != 
reference_bits(fmt, w, 10**kk):
+                        bad += 1
+            check(bad == 0, "w / 10^k, k <= %d, via %d-bit intermediate never 
double-rounds"
+                  % (fmt.max_exponent_fast_path, wide))
+
+
+def check_ties(fmt):
+    """A tie is m * 2^e with m odd. It is w * 10^q, w < 10^19, iff q <= e and
+    w = m 5^-q 2^(e-q) (q <= 0) or 5^q | m (q > 0). Ties whose lower neighbour
+    is even (m == 1 mod 4) must fall in the round-to-even range."""
+    p = fmt.p
+    lo, hi = fmt.min_exponent_round_to_even, fmt.max_exponent_round_to_even
+    q = 0
+    while ((1 << (p + 1)) + 1) * 5 ** (q + 1) < MAX_W:
+        q += 1
+    normal_lo = -q
+    q = 0
+    while 5 ** (q + 1) < 1 << (p + 2):
+        q += 1
+    while True:
+        c = 1
+        while 5**q * c <= 1 << (p + 1):
+            c += 4
+        if 5**q * c < 1 << (p + 2) and q <= fmt.emax - p - 1:
+            break
+        q -= 1
+    normal_hi = q
+    e = fmt.denorm_min_exp - 1
+    sub = []
+    q = e
+    while 5**-q << (e - q) < MAX_W:
+        sub.append(q)
+        q -= 1
+    check(lo <= normal_lo and hi >= normal_hi,
+          "round_to_even range [%d, %d] covers normal ties [%d, %d]" % (lo, 
hi, normal_lo, normal_hi))
+    if sub:
+        check(lo <= min(sub), "and the subnormal ties at q in [%d, %d]" % 
(min(sub), max(sub)))
+    else:
+        note("no subnormal tie has fewer than 20 digits")
+    # subnormal_ties_possible() in float_common.h: an upper bound on the
+    # largest q that can give a subnormal, compared with the tie range
+    q_sub = ((fmt.minimum_exponent + 1) * 1233) >> 12
+    check(10**q_sub < 2**fmt.emin < 10 ** (q_sub + 2), "q <= %d bounds the 
subnormal exponents" % q_sub)
+    check((lo <= q_sub) == bool(sub), "subnormal_ties_possible() == %s" % 
bool(sub))
+    longest = len(str(((1 << (p + 2)) - 1) * 5**fmt.bias))
+    check(fmt.max_digits >= longest, "max_digits=%d >= %d, the longest tie" % 
(fmt.max_digits, longest))
+
+
+def check_table(fmt):
+    """Facts behind the tie test (product.low <= 1 and dropped bits zero)."""
+    q_lo, q_hi = max(fmt.smallest_power_of_ten, -27), -1
+    check(fmt.min_exponent_round_to_even >= -27, "tie test stays where 5^-q < 
2^64")
+    check(all(TABLE[q] & MASK64 for q in range(q_lo, q_hi + 1)),
+          "T[q] low word nonzero for %d <= q <= %d" % (q_lo, q_hi))
+    fake = False
+    for q in range(q_lo, q_hi + 1):
+        t_hi = TABLE[q] >> 64
+        v2 = (t_hi & -t_hi).bit_length() - 1
+        z = ceil_log2(5**-q)
+        for shift in (61 - fmt.p, 62 - fmt.p):
+            # skipped second product: w * t_hi == 0 or 1 mod 2^(shift+64)
+            if shift + 64 - v2 <= 63:
+                fake = True
+            if v2 == 0 and (1 << 63) <= pow(t_hi, -1, 1 << (shift + 64)) < 1 
<< 64:
+                fake = True
+            # computed second product: a non-tie within 2^-62 of a rational
+            # with denominator 2^(z-1-shift) is impossible
+            if z - 1 - shift > 62:
+                fake = True
+    check(not fake, "no false tie for %d <= q <= %d" % (q_lo, q_hi))
+    bad = False
+    for q in range(fmt.smallest_power_of_ten, fmt.largest_power_of_ten + 1):
+        for _, w in convergents(continued_fraction(TABLE[q], 2**137)):
+            if w >= 2**64:
+                break
+            if (TABLE[q] * w) % 2**137 > 2**137 - 2**64:
+                bad = True
+    check(not bad, "Mushtak-Lemire condition for %d <= q <= %d"
+          % (fmt.smallest_power_of_ten, fmt.largest_power_of_ten))
+
+
+def sweep_q(args):
+    fmt, grid, q, width = args
+    cands = set()
+    for num, exp2, _ in grid:
+        n, d = ratio(num, exp2)
+        if q >= 0:
+            d *= 10**q
+        else:
+            n *= 10**-q
+        cands.update(w for w in range(n // d - width, n // d + width + 2) if 1 
<= w < MAX_W)
+    wrong, wrong_without, bad_trunc = [], 0, 0
+    for w in cands:
+        n, d = ratio10(w, q)
+        want = reference_bits(fmt, n, d)
+        got, truncated, k = parsed_bits(fmt, w, q)
+        if got != want:
+            wrong.append((w, q, got, want))
+        if truncated is not None:
+            exact = (n << k) // d if k >= 0 else n // (d << -k)
+            bad_trunc += truncated != exact
+        if parsed_bits(fmt, w, q, False)[0] != want:
+            wrong_without += 1
+    return len(cands), wrong, wrong_without, bad_trunc
+
+
+def sweep(fmt, width, jobs):
+    """Compare the model with the reference on every w within `width` of
+    G / 10^q for every value, tie and the overflow threshold G."""
+    grid = fmt.grid()
+    qs = range(fmt.smallest_power_of_ten - 1, fmt.largest_power_of_ten + 2)
+    tasks = [(fmt, grid, q, width) for q in qs]
+    with multiprocessing.Pool(jobs) as pool:
+        results = pool.map(sweep_q, tasks)
+    wrong = [x for r in results for x in r[1]]
+    note("swept %d pairs (w, q)" % sum(r[0] for r in results))
+    check(sum(r[3] for r in results) == 0, "truncated mantissa is exact")
+    check(not wrong, "compute_float matches the reference")
+    for w, q, got, want in sorted(wrong)[:20]:
+        print("        w=%d q=%d got 0x%04x want 0x%04x" % (w, q, got, want))
+    note("without the subnormal tie test: %d wrong" % sum(r[2] for r in 
results))
+
+
+def main():
+    ap = argparse.ArgumentParser()
+    ap.add_argument("--types", default="double,float,float16,bfloat16")
+    ap.add_argument("--sweep-width", type=int, default=3)
+    ap.add_argument("--jobs", type=int, default=os.cpu_count() or 1)
+    args = ap.parse_args()
+    for name in args.types.split(","):
+        fmt = Format(name.strip())
+        print("== %s" % fmt.name)
+        check_constants(fmt)
+        check_table(fmt)
+        check_ties(fmt)
+        if fmt.p <= 10 and args.sweep_width > 0:
+            sweep(fmt, args.sweep_width, args.jobs)
+    print("%d failure(s)" % failures)
+    return 1 if failures else 0
+
+
+if __name__ == "__main__":
+    sys.exit(main())
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fast_float-8.3.0/script/release.py 
new/fast_float-8.3.1/script/release.py
--- old/fast_float-8.3.0/script/release.py      2026-09-15 17:03:18.000000000 
+0200
+++ new/fast_float-8.3.1/script/release.py      2026-10-04 18:26:53.000000000 
+0200
@@ -145,6 +145,18 @@
 
 print(f"{versionfile} modified")
 
+bazelfile = f"{maindir}{os.sep}MODULE.bazel"
+
+for line in fileinput.input(bazelfile, inplace=1, backup=".bak"):
+    line = re.sub(
+        r'^(\s*version = )"\d+\.\d+\.\d+",',
+        rf'\g<1>"{newversionstring}",',
+        line.rstrip(),
+    )
+    print(line)
+
+print(f"modified {bazelfile}, a backup was made")
+
 readmefile = f"{maindir}{os.sep}README.md"
 
 for line in fileinput.input(readmefile, inplace=1, backup=".bak"):
@@ -173,6 +185,7 @@
 
 if cp.returncode != 0:
     print("Failed to run amalgamate")
+    sys.exit(cp.returncode)
 else:
     print("amalgamate.py ran successfully")
     print(f"You should upload {maindir}{os.sep}fast_float.h")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fast_float-8.3.0/tests/CMakeLists.txt 
new/fast_float-8.3.1/tests/CMakeLists.txt
--- old/fast_float-8.3.0/tests/CMakeLists.txt   2026-09-15 17:03:18.000000000 
+0200
+++ new/fast_float-8.3.1/tests/CMakeLists.txt   2026-10-04 18:26:53.000000000 
+0200
@@ -95,6 +95,8 @@
 if (FASTFLOAT_FIXEDWIDTH_TESTS)
   fast_float_add_cpp_test(fixedwidthtest)
   target_compile_features(fixedwidthtest PUBLIC cxx_std_23)
+  fast_float_add_cpp_test(exhaustive16_midpoint)
+  target_compile_features(exhaustive16_midpoint PUBLIC cxx_std_23)
 endif()
 
 option(FASTFLOAT_EXHAUSTIVE "Exhaustive tests" OFF)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fast_float-8.3.0/tests/basictest.cpp 
new/fast_float-8.3.1/tests/basictest.cpp
--- old/fast_float-8.3.0/tests/basictest.cpp    2026-09-15 17:03:18.000000000 
+0200
+++ new/fast_float-8.3.1/tests/basictest.cpp    2026-10-04 18:26:53.000000000 
+0200
@@ -84,6 +84,12 @@
   }
 }
 
+// Guard against padding regressions that hurt performance.
+// See https://github.com/fastfloat/fast_float/issues/418
+static_assert(sizeof(void *) != 8 ||
+                  sizeof(fast_float::parsed_number_string_t<char>) <= 64,
+              "parsed_number_string_t should fit in 64 bytes");
+
 #define FASTFLOAT_STR(x) #x
 #define SHOW_DEFINE(x) printf("%s='%s'\n", #x, FASTFLOAT_STR(x))
 
@@ -1832,6 +1838,20 @@
          std::errc::result_out_of_range);
   verify("3.5028234666e38", std::numeric_limits<std::float16_t>::infinity(),
          std::errc::result_out_of_range);
+  // overflow in the fast path
+  verify("656e2", std::numeric_limits<std::float16_t>::infinity(),
+         std::errc::result_out_of_range);
+  verify("-656e2", -std::numeric_limits<std::float16_t>::infinity(),
+         std::errc::result_out_of_range);
+  verify("7e4", std::numeric_limits<std::float16_t>::infinity(),
+         std::errc::result_out_of_range);
+  verify("2048e4", std::numeric_limits<std::float16_t>::infinity(),
+         std::errc::result_out_of_range);
+  verify("655e2", 0x1.ffcp+15f16); // 65500 rounds to max
+  // max + ulp/2 rounds to even
+  verify("6552e1", std::numeric_limits<std::float16_t>::infinity(),
+         std::errc::result_out_of_range);
+  verify("65519", 0x1.ffcp+15f16);
 }
 
 TEST_CASE("float16.general") {
@@ -1852,6 +1872,22 @@
   verify("-0.000000059604644775390625", -0x1p-24f16);
   verify("-5.9604644775390625e-8", -0x1p-24f16);
 
+  // subnormal ties round to even
+  verify("2.98023223876953125e-8", 0.0f16, std::errc::result_out_of_range);
+  verify("0.0000000298023223876953125", 0.0f16, 
std::errc::result_out_of_range);
+  verify("-2.98023223876953125e-8", -0.0f16, std::errc::result_out_of_range);
+  verify("8.94069671630859375e-8", 0x1p-23f16);
+  verify("1.490116119384765625e-7", 0x1p-23f16);
+  verify("9.834766387939453125e-7", 0x1p-20f16);
+  // just above or below a tie
+  verify("2.980232238769531251e-8", 0x1p-24f16);
+  verify("2.980232238769531249e-8", 0.0f16, std::errc::result_out_of_range);
+  verify("1.4901161193847656251e-7", 0x1.8p-23f16);
+  verify("1.4901161193847656249e-7", 0x1p-23f16);
+  // more than 19 digits
+  verify("2.9802322387695312500e-8", 0.0f16, std::errc::result_out_of_range);
+  verify("1.4901161193847656250000e-7", 0x1p-23f16);
+
   verify("-1e-999", -0.0f16, std::errc::result_out_of_range);
   verify("6.0975551605224609375", 0x1.864p+2f16);
   verify_runtime(append_zeros("6.0975551605224609375", 655), 0x1.864p+2f16);
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/fast_float-8.3.0/tests/exhaustive16_midpoint.cpp 
new/fast_float-8.3.1/tests/exhaustive16_midpoint.cpp
--- old/fast_float-8.3.0/tests/exhaustive16_midpoint.cpp        1970-01-01 
01:00:00.000000000 +0100
+++ new/fast_float-8.3.1/tests/exhaustive16_midpoint.cpp        2026-10-04 
18:26:53.000000000 +0200
@@ -0,0 +1,163 @@
+// For std::float16_t and std::bfloat16_t, parse the exact decimal expansion
+// of every finite value and of every midpoint between adjacent values, plus
+// strings just above and below each midpoint and midpoints padded past 19
+// digits. The expected bits follow from the bit pattern that generated the
+// string, so no C library reference is needed.
+#include "fast_float/fast_float.h"
+
+#include <cstdint>
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include <stdfloat>
+#include <string>
+#include <system_error>
+
+namespace {
+
+// Decimal digit strings, most significant digit first, no leading zeros.
+std::string times(std::string const &digits, unsigned factor) {
+  std::string out;
+  unsigned carry = 0;
+  for (size_t i = digits.size(); i-- > 0;) {
+    unsigned v = static_cast<unsigned>(digits[i] - '0') * factor + carry;
+    out.push_back(static_cast<char>('0' + v % 10));
+    carry = v / 10;
+  }
+  while (carry != 0) {
+    out.push_back(static_cast<char>('0' + carry % 10));
+    carry /= 10;
+  }
+  return std::string(out.rbegin(), out.rend());
+}
+
+std::string minus_one(std::string digits) {
+  size_t i = digits.size();
+  while (i-- > 0) {
+    if (digits[i] != '0') {
+      digits[i] = static_cast<char>(digits[i] - 1);
+      break;
+    }
+    digits[i] = '9';
+  }
+  return digits;
+}
+
+// num * 2^exp2 written as "<digits>e<exponent>" with an exact expansion.
+struct Decimal {
+  std::string digits;
+  int exponent;
+
+  std::string str() const { return digits + "e" + std::to_string(exponent); }
+
+  Decimal above() const { return {digits + "1", exponent - 1}; }
+
+  Decimal below() const { return {minus_one(digits) + "9", exponent - 1}; }
+
+  Decimal padded(size_t count) const {
+    return {digits + std::string(count, '0'),
+            exponent - static_cast<int>(count)};
+  }
+};
+
+Decimal exact_decimal(uint64_t num, int exp2) {
+  std::string digits = std::to_string(num);
+  if (exp2 >= 0) {
+    for (int i = 0; i < exp2; i++) {
+      digits = times(digits, 2);
+    }
+    return {digits, 0};
+  }
+  for (int i = 0; i < -exp2; i++) {
+    digits = times(digits, 5);
+  }
+  return {digits, exp2};
+}
+
+template <typename T> struct Layout;
+
+template <> struct Layout<std::float16_t> {
+  static constexpr char const *name = "float16";
+  static constexpr int p = 10;
+  static constexpr int bias = 25; // value = mantissa * 2^(field - bias)
+  static constexpr uint16_t infinity = 0x7C00;
+};
+
+template <> struct Layout<std::bfloat16_t> {
+  static constexpr char const *name = "bfloat16";
+  static constexpr int p = 7;
+  static constexpr int bias = 134;
+  static constexpr uint16_t infinity = 0x7F80;
+};
+
+struct Failure {
+  long count = 0;
+  long checked = 0;
+};
+
+template <typename T>
+void expect(Failure &f, std::string const &s, uint16_t want) {
+  T value{};
+  auto r = fast_float::from_chars(s.data(), s.data() + s.size(), value);
+  uint16_t got = 0;
+  std::memcpy(&got, &value, sizeof(got));
+  bool zero_or_inf = want == 0 || want == Layout<T>::infinity;
+  std::errc want_ec =
+      zero_or_inf ? std::errc::result_out_of_range : std::errc();
+  f.checked++;
+  if (got != want || r.ec != want_ec || r.ptr != s.data() + s.size()) {
+    f.count++;
+    if (f.count <= 20) {
+      std::printf(
+          "%s: \"%s\" parsed to 0x%04x (ec %d), expected 0x%04x (ec %d)\n",
+          Layout<T>::name, s.c_str(), got, static_cast<int>(r.ec), want,
+          static_cast<int>(want_ec));
+    }
+  }
+}
+
+template <typename T> Failure sweep() {
+  Failure f;
+  constexpr int p = Layout<T>::p;
+  constexpr uint16_t mantissa_mask = static_cast<uint16_t>((1u << p) - 1);
+  for (uint32_t b = 0; b < Layout<T>::infinity; b++) {
+    uint16_t bits = static_cast<uint16_t>(b);
+    int field = bits >> p;
+    uint64_t num = bits & mantissa_mask;
+    int exp2 = (field == 0 ? 1 : field) - Layout<T>::bias;
+    if (field != 0) {
+      num |= uint64_t(1) << p;
+    }
+    if (num != 0) {
+      Decimal d = exact_decimal(num, exp2);
+      expect<T>(f, d.str(), bits);
+      expect<T>(f, d.padded(21).str(), bits);
+    }
+    // The tie between bits and bits + 1 is (2 num + 1) * 2^(exp2 - 1); the
+    // last one (bits + 1 == infinity) is the overflow threshold.
+    Decimal tie = exact_decimal(2 * num + 1, exp2 - 1);
+    uint16_t lower = bits;
+    uint16_t upper = static_cast<uint16_t>(bits + 1);
+    uint16_t even = (bits & 1) ? upper : lower;
+    expect<T>(f, tie.str(), even);
+    expect<T>(f, tie.padded(21).str(), even);
+    expect<T>(f, tie.above().str(), upper);
+    expect<T>(f, tie.below().str(), lower);
+  }
+  return f;
+}
+
+} // namespace
+
+int main() {
+  Failure f16 = sweep<std::float16_t>();
+  std::printf("float16: %ld strings, %ld failures\n", f16.checked, f16.count);
+  Failure bf16 = sweep<std::bfloat16_t>();
+  std::printf("bfloat16: %ld strings, %ld failures\n", bf16.checked,
+              bf16.count);
+  if (f16.count != 0 || bf16.count != 0) {
+    return EXIT_FAILURE;
+  }
+  std::printf("all ok\n");
+  return EXIT_SUCCESS;
+}

Reply via email to