[libc-commits] [libc] [libc] Bound the decimal point check in simple_decimal_conversion (PR #238179)

via libc-commits libc-commits at lists.llvm.org
Tue Nov 3 09:49:55 PST 2026


https://github.com/MacchaCat updated https://github.com/llvm/llvm-project/pull/238179

>From 253fd3e9ba0da9c7cca9a27c909e44d191e26726 Mon Sep 17 00:00:00 2001
From: MacchaCat <macchacat at icloud.com>
Date: Fri, 23 Oct 2026 12:58:14 +0900
Subject: [PATCH] [libc] Bound the decimal point check in
 simple_decimal_conversion

HighPrecisionDecimal clamps the decimal point to 1 << 30, but exp10_to_exp2
is only valid for inputs below 646456636. For larger decimal points the
result wrapped to a negative value, so the "too large" and "too small"
checks did not trigger and the shift loops ran tens of millions of
iterations until exp_2 overflowed (signed integer overflow).

Treat decimal points at or beyond the valid range of exp10_to_exp2 as out of
range, and use add_overflow/sub_overflow for exp_2 in the shift loops so
that they stay well defined.

Assisted-by: Claude (Anthropic)
---
 libc/src/__support/str_to_float.h             | 29 +++++++++++++++----
 .../test/src/__support/str_to_double_test.cpp | 15 ++++++++++
 libc/test/src/__support/str_to_float_test.cpp | 10 +++++++
 3 files changed, 49 insertions(+), 5 deletions(-)

diff --git a/libc/src/__support/str_to_float.h b/libc/src/__support/str_to_float.h
index 0c9c216ce88a5e..f70f9d5bd26b8f 100644
--- a/libc/src/__support/str_to_float.h
+++ b/libc/src/__support/str_to_float.h
@@ -30,6 +30,7 @@
 #include "src/__support/macros/config.h"
 #include "src/__support/macros/null_check.h"
 #include "src/__support/macros/optimization.h"
+#include "src/__support/math_extras.h"
 #include "src/__support/str_to_integer.h"
 #include "src/__support/str_to_num_result.h"
 #include "src/__support/uint128.h"
@@ -353,18 +354,26 @@ LIBC_INLINE FloatConvertReturn<T> simple_decimal_conversion(
     return output;
   }
 
+  // exp10_to_exp2 is only valid for inputs below 646 456 636, but
+  // HighPrecisionDecimal clamps the decimal point to 1 << 30. Decimal points at
+  // or beyond that limit are far outside the range of any float type, so they
+  // are handled explicitly instead of letting exp10_to_exp2 wrap around.
+  constexpr int32_t EXP10_TO_EXP2_LIMIT = 646456636;
+
   // If the exponent is too large and can't be represented in this size of
   // float, return inf.
   if (hpd.get_decimal_point() > 0 &&
-      exp10_to_exp2(hpd.get_decimal_point() - 1) > FPBits::EXP_BIAS) {
+      (hpd.get_decimal_point() - 1 >= EXP10_TO_EXP2_LIMIT ||
+       exp10_to_exp2(hpd.get_decimal_point() - 1) > FPBits::EXP_BIAS)) {
     output.num = {0, fputil::FPBits<T>::MAX_BIASED_EXPONENT};
     output.error = ERANGE;
     return output;
   }
   // If the exponent is too small even for a subnormal, return 0.
   if (hpd.get_decimal_point() < 0 &&
-      exp10_to_exp2(-hpd.get_decimal_point()) >
-          (FPBits::EXP_BIAS + static_cast<int32_t>(FPBits::FRACTION_LEN))) {
+      (-hpd.get_decimal_point() >= EXP10_TO_EXP2_LIMIT ||
+       exp10_to_exp2(-hpd.get_decimal_point()) >
+           (FPBits::EXP_BIAS + static_cast<int32_t>(FPBits::FRACTION_LEN)))) {
     output.num = {0, 0};
     output.error = ERANGE;
     return output;
@@ -378,7 +387,13 @@ LIBC_INLINE FloatConvertReturn<T> simple_decimal_conversion(
     } else {
       shift_amount = POWERS_OF_TWO[hpd.get_decimal_point()];
     }
-    exp_2 += shift_amount;
+    // The bounds checks above keep exp_2 small, but stay well defined if they
+    // are ever loosened.
+    if (LIBC_UNLIKELY(add_overflow(exp_2, shift_amount, exp_2))) {
+      output.num = {0, FPBits::MAX_BIASED_EXPONENT};
+      output.error = ERANGE;
+      return output;
+    }
     hpd.shift(-shift_amount);
   }
 
@@ -394,7 +409,11 @@ LIBC_INLINE FloatConvertReturn<T> simple_decimal_conversion(
     } else { // This handles the case of the number being between .1 and .5
       shift_amount = 1;
     }
-    exp_2 -= shift_amount;
+    if (LIBC_UNLIKELY(sub_overflow(exp_2, shift_amount, exp_2))) {
+      output.num = {0, 0};
+      output.error = ERANGE;
+      return output;
+    }
     hpd.shift(shift_amount);
   }
 
diff --git a/libc/test/src/__support/str_to_double_test.cpp b/libc/test/src/__support/str_to_double_test.cpp
index 23812ef3b2978b..be9aab62812070 100644
--- a/libc/test/src/__support/str_to_double_test.cpp
+++ b/libc/test/src/__support/str_to_double_test.cpp
@@ -85,6 +85,21 @@ TEST_F(LlvmLibcStrToDblTest, SimpleDecimalConversion64BasicExponents) {
   simple_decimal_conversion_test("1e-300", 0x156e1fc2f8f359, 26);
 }
 
+TEST_F(LlvmLibcStrToDblTest, SimpleDecimalConversion64HugeExponents) {
+  // Decimal points beyond the valid input range of exp10_to_exp2 (646456636)
+  // must still be classified as out of range, and do so without looping.
+  simple_decimal_conversion_test("1e646456635", 0, 0x7ff, ERANGE);
+  simple_decimal_conversion_test("1e646456636", 0, 0x7ff, ERANGE);
+  simple_decimal_conversion_test("1e646456637", 0, 0x7ff, ERANGE);
+  simple_decimal_conversion_test("1e1000000000", 0, 0x7ff, ERANGE);
+  simple_decimal_conversion_test("9007199254740993e2147483647", 0, 0x7ff,
+                                 ERANGE);
+  simple_decimal_conversion_test("1e-646456636", 0, 0, ERANGE);
+  simple_decimal_conversion_test("1e-646456637", 0, 0, ERANGE);
+  simple_decimal_conversion_test("1e-1000000000", 0, 0, ERANGE);
+  simple_decimal_conversion_test("1e-2147483647", 0, 0, ERANGE);
+}
+
 TEST_F(LlvmLibcStrToDblTest, SimpleDecimalConversion64BasicSubnormals) {
   simple_decimal_conversion_test("1e-320", 0x7e8, 0, ERANGE);
   simple_decimal_conversion_test("1e-308", 0x730d67819e8d2, 0, ERANGE);
diff --git a/libc/test/src/__support/str_to_float_test.cpp b/libc/test/src/__support/str_to_float_test.cpp
index eb1830cfe6a0f2..7a52fb6a81d1ab 100644
--- a/libc/test/src/__support/str_to_float_test.cpp
+++ b/libc/test/src/__support/str_to_float_test.cpp
@@ -55,6 +55,16 @@ TEST_F(LlvmLibcStrToFltTest, SimpleDecimalConversion32SpecificFailures) {
       0x0, 0, ERANGE);
 }
 
+TEST_F(LlvmLibcStrToFltTest, SimpleDecimalConversion32HugeExponents) {
+  // Decimal points beyond the valid input range of exp10_to_exp2 (646456636)
+  // must still be classified as out of range, and do so without looping.
+  simple_decimal_conversion_test("1e646456636", 0, 0xff, ERANGE);
+  simple_decimal_conversion_test("1e1000000000", 0, 0xff, ERANGE);
+  simple_decimal_conversion_test("16777217e2147483647", 0, 0xff, ERANGE);
+  simple_decimal_conversion_test("1e-646456636", 0, 0, ERANGE);
+  simple_decimal_conversion_test("1e-2147483647", 0, 0, ERANGE);
+}
+
 TEST(LlvmLibcStrToFltTest, SimpleDecimalConversionExtraTypes) {
   uint32_t float_output_mantissa = 0;
   uint32_t output_exp2 = 0;



More information about the libc-commits mailing list