Skip to content

Conversation

@vonosmas
Copy link
Contributor

These are simply implemented as specializations of strtofloatingpoint for double / long double and for wchar_t. The unit tests are copied from the strtod / strtold ones.

These are simply implemented as specializations of strtofloatingpoint
for double / long double and for wchar_t. The unit tests are copied from
the strtod / strtold ones.
@llvmbot
Copy link
Member

llvmbot commented Nov 14, 2025

@llvm/pr-subscribers-libc

Author: Alexey Samsonov (vonosmas)

Changes

These are simply implemented as specializations of strtofloatingpoint for double / long double and for wchar_t. The unit tests are copied from the strtod / strtold ones.


Patch is 60.14 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/168020.diff

10 Files Affected:

  • (modified) libc/config/linux/x86_64/entrypoints.txt (+2)
  • (modified) libc/include/wchar.yaml (+14)
  • (modified) libc/src/wchar/CMakeLists.txt (+22)
  • (added) libc/src/wchar/wcstod.cpp (+30)
  • (added) libc/src/wchar/wcstod.h (+20)
  • (added) libc/src/wchar/wcstold.cpp (+30)
  • (added) libc/src/wchar/wcstold.h (+21)
  • (modified) libc/test/src/wchar/CMakeLists.txt (+28-1)
  • (added) libc/test/src/wchar/wcstod_test.cpp (+585)
  • (added) libc/test/src/wchar/wcstold_test.cpp (+262)
diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt
index d3bcad470b3e1..5036c9438a503 100644
--- a/libc/config/linux/x86_64/entrypoints.txt
+++ b/libc/config/linux/x86_64/entrypoints.txt
@@ -398,9 +398,11 @@ set(TARGET_LIBC_ENTRYPOINTS
     libc.src.wchar.wmemchr
     libc.src.wchar.wcpcpy
     libc.src.wchar.wcpncpy
+    libc.src.wchar.wcstod
     libc.src.wchar.wcstof
     libc.src.wchar.wcstok
     libc.src.wchar.wcstol
+    libc.src.wchar.wcstold
     libc.src.wchar.wcstoll
     libc.src.wchar.wcstoul
     libc.src.wchar.wcstoull
diff --git a/libc/include/wchar.yaml b/libc/include/wchar.yaml
index faceb9bb4e12d..a524c7f56bed0 100644
--- a/libc/include/wchar.yaml
+++ b/libc/include/wchar.yaml
@@ -367,3 +367,17 @@ functions:
     arguments:
       - type: const wchar_t *__restrict
       - type: wchar_t **__restrict
+  - name: wcstod
+    standards:
+      - stdc
+    return_type: double
+    arguments:
+      - type: const wchar_t *__restrict
+      - type: wchar_t **__restrict
+  - name: wcstold
+    standards:
+      - stdc
+    return_type: long double
+    arguments:
+      - type: const wchar_t *__restrict
+      - type: wchar_t **__restrict
diff --git a/libc/src/wchar/CMakeLists.txt b/libc/src/wchar/CMakeLists.txt
index e3fac9fb80529..e6d9af9eacf73 100644
--- a/libc/src/wchar/CMakeLists.txt
+++ b/libc/src/wchar/CMakeLists.txt
@@ -110,6 +110,28 @@ add_entrypoint_object(
     libc.src.errno.errno
 )
 
+add_entrypoint_object(
+  wcstod
+  SRCS
+    wcstod.cpp
+  HDRS
+    wcstod.h
+  DEPENDS
+    libc.src.__support.str_to_float
+    libc.src.errno.errno
+)
+
+add_entrypoint_object(
+  wcstold
+  SRCS
+    wcstold.cpp
+  HDRS
+    wcstold.h
+  DEPENDS
+    libc.src.__support.str_to_float
+    libc.src.errno.errno
+)
+
 add_entrypoint_object(
   wcstok
   SRCS
diff --git a/libc/src/wchar/wcstod.cpp b/libc/src/wchar/wcstod.cpp
new file mode 100644
index 0000000000000..95351c304c0ff
--- /dev/null
+++ b/libc/src/wchar/wcstod.cpp
@@ -0,0 +1,30 @@
+//===-- Implementation of wcstod ------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "src/wchar/wcstod.h"
+#include "src/__support/common.h"
+#include "src/__support/libc_errno.h"
+#include "src/__support/macros/config.h"
+#include "src/__support/str_to_float.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+LLVM_LIBC_FUNCTION(double, wcstod,
+                   (const wchar_t *__restrict str,
+                    wchar_t **__restrict str_end)) {
+  auto result = internal::strtofloatingpoint<double>(str);
+  if (result.has_error())
+    libc_errno = result.error;
+
+  if (str_end != nullptr)
+    *str_end = const_cast<wchar_t *>(str + result.parsed_len);
+
+  return result.value;
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/wchar/wcstod.h b/libc/src/wchar/wcstod.h
new file mode 100644
index 0000000000000..ff397b93d405d
--- /dev/null
+++ b/libc/src/wchar/wcstod.h
@@ -0,0 +1,20 @@
+//===-- Implementation header for wcstod ------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_WCHAR_WCSTOD_H
+#define LLVM_LIBC_SRC_WCHAR_WCSTOD_H
+
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+double wcstod(const wchar_t *__restrict str, wchar_t **__restrict str_end);
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_WCHAR_WCSTOD_H
diff --git a/libc/src/wchar/wcstold.cpp b/libc/src/wchar/wcstold.cpp
new file mode 100644
index 0000000000000..ffbc3f248b883
--- /dev/null
+++ b/libc/src/wchar/wcstold.cpp
@@ -0,0 +1,30 @@
+//===-- Implementation of wcstold -----------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "src/wchar/wcstold.h"
+#include "src/__support/common.h"
+#include "src/__support/libc_errno.h"
+#include "src/__support/macros/config.h"
+#include "src/__support/str_to_float.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+LLVM_LIBC_FUNCTION(long double, wcstold,
+                   (const wchar_t *__restrict str,
+                    wchar_t **__restrict str_end)) {
+  auto result = internal::strtofloatingpoint<long double>(str);
+  if (result.has_error())
+    libc_errno = result.error;
+
+  if (str_end != nullptr)
+    *str_end = const_cast<wchar_t *>(str + result.parsed_len);
+
+  return result.value;
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/wchar/wcstold.h b/libc/src/wchar/wcstold.h
new file mode 100644
index 0000000000000..1525362b33571
--- /dev/null
+++ b/libc/src/wchar/wcstold.h
@@ -0,0 +1,21 @@
+//===-- Implementation header for wcstold -----------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_WCHAR_WCSTOLD_H
+#define LLVM_LIBC_SRC_WCHAR_WCSTOLD_H
+
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+long double wcstold(const wchar_t *__restrict str,
+                    wchar_t **__restrict str_end);
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_WCHAR_WCSTOLD_H
diff --git a/libc/test/src/wchar/CMakeLists.txt b/libc/test/src/wchar/CMakeLists.txt
index 122cad2575327..a62a30fe00124 100644
--- a/libc/test/src/wchar/CMakeLists.txt
+++ b/libc/test/src/wchar/CMakeLists.txt
@@ -538,5 +538,32 @@ add_libc_test(
   DEPENDS
     libc.src.wchar.wcstof
     libc.test.UnitTest.ErrnoCheckingTest
-    libc.test.UnitTest.LibcFPTestHelpers
+  LINK_LIBRARIES
+    LibcFPTestHelpers
+)
+
+add_libc_test(
+  wcstod_test
+  SUITE
+    libc_wchar_unittests
+  SRCS
+    wcstod_test.cpp
+  DEPENDS
+    libc.src.wchar.wcstod
+    libc.test.UnitTest.ErrnoCheckingTest
+  LINK_LIBRARIES
+    LibcFPTestHelpers
+)
+
+add_libc_test(
+  wcstold_test
+  SUITE
+    libc_wchar_unittests
+  SRCS
+    wcstold_test.cpp
+  DEPENDS
+    libc.src.__support.FPUtil.fp_bits
+    libc.src.__support.uint128
+    libc.src.wchar.wcstold
+    libc.test.UnitTest.ErrnoCheckingTest
 )
diff --git a/libc/test/src/wchar/wcstod_test.cpp b/libc/test/src/wchar/wcstod_test.cpp
new file mode 100644
index 0000000000000..ac6c48d39d287
--- /dev/null
+++ b/libc/test/src/wchar/wcstod_test.cpp
@@ -0,0 +1,585 @@
+//===-- Unittests for wcstod ----------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "src/wchar/wcstod.h"
+
+#include "src/__support/FPUtil/FPBits.h"
+#include "test/UnitTest/ErrnoCheckingTest.h"
+#include "test/UnitTest/ErrnoSetterMatcher.h"
+#include "test/UnitTest/RoundingModeUtils.h"
+#include "test/UnitTest/Test.h"
+
+#include <stddef.h>
+
+using LIBC_NAMESPACE::fputil::testing::ForceRoundingModeTest;
+using LIBC_NAMESPACE::fputil::testing::RoundingMode;
+
+using LIBC_NAMESPACE::testing::ErrnoSetterMatcher::Fails;
+using LIBC_NAMESPACE::testing::ErrnoSetterMatcher::Succeeds;
+
+class LlvmLibcWcstodTest : public LIBC_NAMESPACE::testing::ErrnoCheckingTest,
+                           ForceRoundingModeTest<RoundingMode::Nearest> {
+public:
+  void run_test(const wchar_t *inputString, const ptrdiff_t expectedStrLen,
+                const uint64_t expectedRawData, const int expectedErrno = 0) {
+    // expectedRawData is the expected double result as a uint64_t, organized
+    // according to IEEE754:
+    //
+    // +-- 1 Sign Bit                        +-- 52 Mantissa bits
+    // |                                     |
+    // |           +-------------------------+------------------------+
+    // |           |                                                  |
+    // SEEEEEEEEEEEMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
+    //  |         |
+    //  +----+----+
+    //       |
+    //       +-- 11 Exponent Bits
+    //
+    //  This is so that the result can be compared in parts.
+    wchar_t *str_end = nullptr;
+
+    LIBC_NAMESPACE::fputil::FPBits<double> expected_fp =
+        LIBC_NAMESPACE::fputil::FPBits<double>(expectedRawData);
+
+    double result = LIBC_NAMESPACE::wcstod(inputString, &str_end);
+    if (expectedErrno == 0)
+      EXPECT_THAT(result, Succeeds<double>(expected_fp.get_val()));
+    else
+      EXPECT_THAT(result, Fails<double>(expectedErrno, expected_fp.get_val()));
+    EXPECT_EQ(str_end - inputString, expectedStrLen);
+  }
+};
+
+TEST_F(LlvmLibcWcstodTest, SimpleTest) {
+  run_test(L"123", 3, uint64_t(0x405ec00000000000));
+
+  // This should fail on Eisel-Lemire, forcing a fallback to simple decimal
+  // conversion.
+  run_test(L"12345678901234549760", 20, uint64_t(0x43e56a95319d63d8));
+
+  // Found while looking for difficult test cases here:
+  // https://github.com/nigeltao/parse-number-fxx-test-data/blob/main/more-test-cases/golang-org-issue-36657.txt
+  run_test(L"1090544144181609348835077142190", 31, uint64_t(0x462b8779f2474dfb));
+
+  run_test(L"0x123", 5, uint64_t(0x4072300000000000));
+}
+
+// These are tests that have caused problems in the past.
+TEST_F(LlvmLibcWcstodTest, SpecificFailures) {
+  run_test(L"3E70000000000000", 16, uint64_t(0x7FF0000000000000), ERANGE);
+  run_test(L"358416272e-33", 13, uint64_t(0x3adbbb2a68c9d0b9));
+  run_test(L"2.16656806400000023841857910156251e9", 36,
+           uint64_t(0x41e0246690000001));
+  run_test(L"27949676547093071875", 20, uint64_t(0x43f83e132bc608c9));
+  run_test(
+      L"10000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "0000000000e-800",
+      806, 0x3ff0000000000000);
+  run_test(
+      L"10000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "0000000000e-799",
+      806, 0x4024000000000000);
+  run_test(
+      L"10000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "00000000000e-800",
+      807, 0x4024000000000000);
+  run_test(
+      L"10000000000000000000000000000000000000000000000000000000000000000e-64",
+      69, 0x3ff0000000000000);
+  run_test(
+      L"10000000000000000000000000000000000000000000000000000000000000000000000"
+      "0000000000000000000000000000000000000000000000000000000000e-128",
+      134, 0x3ff0000000000000);
+  run_test(L"100000000000000000000000000000000000000000000000000000000000000000"
+           "0000000000000000000000000000000000000000000000000000000000000000000"
+           "0000000000000000000000000000000000000000000000000000000000000000000"
+           "000000000000000000000000000000000000000000000000000000000e-256",
+           262, 0x3ff0000000000000);
+  run_test(L"100000000000000000000000000000000000000000000000000000000000000000"
+           "0000000000000000000000000000000000000000000000000000000000000000000"
+           "0000000000000000000000000000000000000000000000000000000000000000000"
+           "0000000000000000000000000000000000000000000000000000000000000000000"
+           "0000000000000000000000000000000000000000000000000000000000000000000"
+           "0000000000000000000000000000000000000000000000000000000000000000000"
+           "0000000000000000000000000000000000000000000000000000000000000000000"
+           "000000000000000000000000000000000000000000000e-512",
+           518, 0x3ff0000000000000);
+  run_test(
+      L"10000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000e-1024",
+      1031, 0x3ff0000000000000);
+  run_test(
+      L"0"
+      "100000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "00000000000000000e-1024",
+      1032, 0x3ff0000000000000);
+}
+
+TEST_F(LlvmLibcWcstodTest, FuzzFailures) {
+  run_test(L"-\xff\xff\xff\xff\xff\xff\xff\x01", 0, uint64_t(0));
+  run_test(L"-.????", 0, uint64_t(0));
+  run_test(
+      L"44444444444444444444444444444444444444444444444444A44444444444444444"
+      "44444444444*\x99\xff\xff\xff\xff",
+      50, uint64_t(0x4a3e68fdd0e0b2d8));
+  run_test(L"-NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNKNNNNNNNNNNNNNNNNNN?"
+           "NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN?",
+           0, uint64_t(0));
+  run_test(L"0x.666E40", 9, uint64_t(0x3fd99b9000000000));
+
+  // glibc version 2.36 and higher (not tested with lower versions) disagrees
+  // with this result, but ours is correct for the nearest rounding mode. See
+  // this bug: https://sourceware.org/bugzilla/show_bug.cgi?id=30220
+  run_test(L"0x30000002222225p-1077", 22, uint64_t(0x0006000000444445), ERANGE);
+
+  // This value triggered a bug by having an exponent exactly equal to the
+  // maximum. The overflow checks would accept a value less than the max value
+  // as valid and greater than the max value as invalid (and set it to the max),
+  // but an exponent of exactly max value hit the else condition which is
+  // intended for underflow and set the exponent to the min exponent.
+  run_test(
+      L"18477446000000000000000000000000000005230000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000935166201543003765631683711878842"
+      "388777446000000000000430037600000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000005238581124701719460000000"
+      "000000000017194600000000000000000070046000000000000000000000000100000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000002000000000000000"
+      "000000000000056316837118788423887774460000000000000000000000000000052385"
+      "811247017194600000000000000000171946000000000000000000700460000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000002000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000523858112470171946000000"
+      "000000000001719460000000000000000007004600000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "0200000000000000000E608",
+      1462, uint64_t(0x7ff0000000000000), ERANGE);
+
+  // Same as above but for hex.
+  run_test(L"0x0164810157p2047", 17, uint64_t(0x7ff0000000000000), ERANGE);
+
+  // This test ensures that only the correct number of characters is accepted.
+  // An exponent symbol followed by a sign isn't a valid expon...
[truncated]

@github-actions
Copy link

github-actions bot commented Nov 14, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

Copy link
Contributor

@michaelrj-google michaelrj-google left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@vonosmas vonosmas merged commit 92c8c87 into llvm:main Nov 17, 2025
19 of 20 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants