From 5a2b876258ab5843d5e1dfde695b127baf9e354a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Branimir=20Karad=C5=BEi=C4=87?= Date: Tue, 21 Jul 2026 23:43:24 -0700 Subject: [PATCH] fromString: Parse hex float literals. (#408) --- src/dtoa.cpp | 116 ++++++++++++++++++++++++++++++++++++++++++ tests/string_test.cpp | 35 +++++++++++++ 2 files changed, 151 insertions(+) diff --git a/src/dtoa.cpp b/src/dtoa.cpp index 1d66d8c..b903c4f 100644 --- a/src/dtoa.cpp +++ b/src/dtoa.cpp @@ -1059,8 +1059,124 @@ namespace bx return result; } + static bool toHexDigit(uint32_t* _out, char _ch) + { + if (_ch >= '0' && _ch <= '9') + { + *_out = uint32_t(_ch - '0'); + return true; + } + + if (_ch >= 'a' && _ch <= 'f') + { + *_out = uint32_t(_ch - 'a' + 10); + return true; + } + + if (_ch >= 'A' && _ch <= 'F') + { + *_out = uint32_t(_ch - 'A' + 10); + return true; + } + + return false; + } + + static bool fromStringHex(double* _out, const StringView& _str) + { + const char* ptr = _str.getPtr(); + const char* end = _str.getTerm(); + + bool negative = false; + + if (ptr < end + && ('-' == *ptr || '+' == *ptr) ) + { + negative = ('-' == *ptr); + ++ptr; + } + + if ( (end - ptr) < 2 + || '0' != ptr[0] + || ('x' != ptr[1] && 'X' != ptr[1]) ) + { + return false; + } + + ptr += 2; + + double mantissa = 0.0; + bool anyDigit = false; + uint32_t digit; + + for (; ptr < end && toHexDigit(&digit, *ptr); ++ptr) + { + mantissa = mantissa * 16.0 + double(digit); + anyDigit = true; + } + + if (ptr < end && '.' == *ptr) + { + ++ptr; + double scale = 1.0 / 16.0; + for (; ptr < end && toHexDigit(&digit, *ptr); ++ptr) + { + mantissa += double(digit) * scale; + scale *= 1.0 / 16.0; + anyDigit = true; + } + } + + if (!anyDigit) + { + return false; // "0x" with no hex digits + } + + int32_t exponent = 0; + if (ptr < end + && ('p' == *ptr || 'P' == *ptr) ) + { + ++ptr; + + int32_t expSign = 1; + if (ptr < end && ('+' == *ptr || '-' == *ptr) ) + { + expSign = ('-' == *ptr) ? -1 : 1; ++ptr; + } + + int32_t magnitude = 0; + for (; ptr < end && *ptr >= '0' && *ptr <= '9'; ++ptr) + { + magnitude = magnitude * 10 + int32_t(*ptr - '0'); + } + + exponent = expSign * magnitude; + } + + double value = mantissa; + + for (int32_t ii = 0; ii < exponent; ++ii) + { + value *= 2.0; + } + + for (int32_t ii = 0; ii > exponent; --ii) + { + value *= 0.5; + } + + *_out = negative ? -value : value; + + return true; + } + bool fromString(double* _out, const StringView& _str) { + if (fromStringHex(_out, _str) ) + { + return true; + } + PrepNumber pn; pn.mantissa = 0; pn.negative = 0; diff --git a/tests/string_test.cpp b/tests/string_test.cpp index 6da9aaf..d155760 100644 --- a/tests/string_test.cpp +++ b/tests/string_test.cpp @@ -4,6 +4,7 @@ */ #include "test.h" +#include #include #include #include @@ -479,6 +480,40 @@ TEST_CASE("fromString double", "[string]") REQUIRE(testFromString(std::numeric_limits::max(), "1.7976931348623158e+308") ); } +TEST_CASE("fromString hex float", "[string]") +{ + double d = 1.0; + + REQUIRE(bx::fromString(&d, "0x1p+0") ); REQUIRE(d == 1.0); + REQUIRE(bx::fromString(&d, "0x1P0") ); REQUIRE(d == 1.0); + REQUIRE(bx::fromString(&d, "0x1.8p+1") ); REQUIRE(d == 3.0); + REQUIRE(bx::fromString(&d, "0x1p-1") ); REQUIRE(d == 0.5); + REQUIRE(bx::fromString(&d, "-0x1.4p+2")); REQUIRE(d == -5.0); + REQUIRE(bx::fromString(&d, "0x0.8p+1") ); REQUIRE(d == 1.0); + REQUIRE(bx::fromString(&d, "0x1.8") ); REQUIRE(d == 1.5); + REQUIRE(bx::fromString(&d, "0x10") ); REQUIRE(d == 16.0); + + REQUIRE(bx::fromString(&d, "0x1.fffffep+127") ); + REQUIRE(d == double(bx::kFloatLargest) ); + + REQUIRE(bx::fromString(&d, "0x1p-126") ); + REQUIRE(d == double(bx::kFloatSmallest) ); + + REQUIRE(bx::fromString(&d, "0x1.fffffffffffffp+1023") ); + REQUIRE(d == bx::kDoubleLargest); + + REQUIRE(bx::fromString(&d, "0x1p-1022") ); + REQUIRE(d == bx::kDoubleSmallest); + + float f = 0.0f; + + REQUIRE(bx::fromString(&f, "0x1.fffffep+127") ); + REQUIRE(f == bx::kFloatLargest); + + REQUIRE(bx::fromString(&f, "0x1p-126") ); + REQUIRE(f == bx::kFloatSmallest); +} + static bool testFromString(int32_t _value, const char* _input) { char tmp[1024];