[libc-commits] [libc] [libc] Implement strfromd() and strfroml() (PR #86113)

Vinayak Dev via libc-commits libc-commits at lists.llvm.org
Thu Mar 21 04:33:35 PDT 2024


https://github.com/vinayakdsci created https://github.com/llvm/llvm-project/pull/86113

Follow up to #85438.

Implements the functions `strfromd()` and `strfroml()` introduced in C23, and makes changes in `strfromf_test.cpp` to make all the `strfrom*_test` files consistent with each other.

>From 6de62aa2c26eb2f2b76c115e144de383b3b643b5 Mon Sep 17 00:00:00 2001
From: Vinayak Dev <vinayakdev.sci at gmail.com>
Date: Thu, 21 Mar 2024 16:46:46 +0530
Subject: [PATCH] [libc] Implement strfromd() and strfroml()

---
 libc/config/linux/x86_64/entrypoints.txt |   2 +
 libc/spec/stdc.td                        |   2 +
 libc/src/stdlib/CMakeLists.txt           |  20 ++
 libc/src/stdlib/strfromd.cpp             |  42 +++++
 libc/src/stdlib/strfromd.h               |  21 +++
 libc/src/stdlib/strfromf.h               |   2 +-
 libc/src/stdlib/strfroml.cpp             |  44 +++++
 libc/src/stdlib/strfroml.h               |  22 +++
 libc/test/src/stdlib/CMakeLists.txt      |  20 ++
 libc/test/src/stdlib/strfromd_test.cpp   | 224 +++++++++++++++++++++++
 libc/test/src/stdlib/strfromf_test.cpp   |  97 +++++-----
 libc/test/src/stdlib/strfroml_test.cpp   | 159 ++++++++++++++++
 12 files changed, 608 insertions(+), 47 deletions(-)
 create mode 100644 libc/src/stdlib/strfromd.cpp
 create mode 100644 libc/src/stdlib/strfromd.h
 create mode 100644 libc/src/stdlib/strfroml.cpp
 create mode 100644 libc/src/stdlib/strfroml.h
 create mode 100644 libc/test/src/stdlib/strfromd_test.cpp
 create mode 100644 libc/test/src/stdlib/strfroml_test.cpp

diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt
index c216f43496270b..e863ba56604e77 100644
--- a/libc/config/linux/x86_64/entrypoints.txt
+++ b/libc/config/linux/x86_64/entrypoints.txt
@@ -180,7 +180,9 @@ set(TARGET_LIBC_ENTRYPOINTS
     libc.src.stdlib.qsort_r
     libc.src.stdlib.rand
     libc.src.stdlib.srand
+    libc.src.stdlib.strfromd
     libc.src.stdlib.strfromf
+    libc.src.stdlib.strfroml
     libc.src.stdlib.strtod
     libc.src.stdlib.strtof
     libc.src.stdlib.strtol
diff --git a/libc/spec/stdc.td b/libc/spec/stdc.td
index 920036adfed5c1..79c65738142436 100644
--- a/libc/spec/stdc.td
+++ b/libc/spec/stdc.td
@@ -956,7 +956,9 @@ def StdC : StandardSpec<"stdc"> {
           FunctionSpec<"rand", RetValSpec<IntType>, [ArgSpec<VoidType>]>,
           FunctionSpec<"srand", RetValSpec<VoidType>, [ArgSpec<UnsignedIntType>]>,
 
+          FunctionSpec<"strfromd", RetValSpec<IntType>, [ArgSpec<CharRestrictedPtr>, ArgSpec<SizeTType>, ArgSpec<ConstCharRestrictedPtr>, ArgSpec<DoubleType>]>,
           FunctionSpec<"strfromf", RetValSpec<IntType>, [ArgSpec<CharRestrictedPtr>, ArgSpec<SizeTType>, ArgSpec<ConstCharRestrictedPtr>, ArgSpec<FloatType>]>,
+          FunctionSpec<"strfroml", RetValSpec<IntType>, [ArgSpec<CharRestrictedPtr>, ArgSpec<SizeTType>, ArgSpec<ConstCharRestrictedPtr>, ArgSpec<LongDoubleType>]>,
 
           FunctionSpec<"strtof", RetValSpec<FloatType>, [ArgSpec<ConstCharRestrictedPtr>, ArgSpec<CharRestrictedPtrPtr>]>,
           FunctionSpec<"strtod", RetValSpec<DoubleType>, [ArgSpec<ConstCharRestrictedPtr>, ArgSpec<CharRestrictedPtrPtr>]>,
diff --git a/libc/src/stdlib/CMakeLists.txt b/libc/src/stdlib/CMakeLists.txt
index 22f7f990fb08a8..a2b16e73f35321 100644
--- a/libc/src/stdlib/CMakeLists.txt
+++ b/libc/src/stdlib/CMakeLists.txt
@@ -62,6 +62,26 @@ add_entrypoint_object(
     .str_from_util
 )
 
+add_entrypoint_object(
+  strfromd
+  SRCS
+    strfromd.cpp
+  HDRS
+    strfromd.h
+  DEPENDS
+    .str_from_util
+)
+
+add_entrypoint_object(
+  strfroml
+  SRCS
+    strfroml.cpp
+  HDRS
+    strfroml.h
+  DEPENDS
+    .str_from_util
+)
+
 add_header_library(
   str_from_util
   HDRS
diff --git a/libc/src/stdlib/strfromd.cpp b/libc/src/stdlib/strfromd.cpp
new file mode 100644
index 00000000000000..1d02a7ad112442
--- /dev/null
+++ b/libc/src/stdlib/strfromd.cpp
@@ -0,0 +1,42 @@
+//===-- Implementation of strfromd ------------------------------*- 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "src/stdlib/strfromd.h"
+#include "src/stdlib/str_from_util.h"
+
+#include <stdarg.h>
+#include <stddef.h>
+
+namespace LIBC_NAMESPACE {
+
+LLVM_LIBC_FUNCTION(int, strfromd,
+                   (char *__restrict s, size_t n, const char *__restrict format,
+                    double fp)) {
+  LIBC_ASSERT(s != nullptr);
+
+  printf_core::FormatSection section =
+      internal::parse_format_string(format, fp);
+  printf_core::WriteBuffer wb(s, (n > 0 ? n - 1 : 0));
+  printf_core::Writer writer(&wb);
+
+  int result = 0;
+  if (section.has_conv)
+    result = internal::strfromfloat_convert<double>(&writer, section);
+  else
+    result = writer.write(section.raw_string);
+
+  if (result < 0)
+    return result;
+
+  if (n > 0)
+    wb.buff[wb.buff_cur] = '\0';
+
+  return writer.get_chars_written();
+}
+
+} // namespace LIBC_NAMESPACE
diff --git a/libc/src/stdlib/strfromd.h b/libc/src/stdlib/strfromd.h
new file mode 100644
index 00000000000000..d2c3fefb6300dc
--- /dev/null
+++ b/libc/src/stdlib/strfromd.h
@@ -0,0 +1,21 @@
+//===-- Implementation header for strfromd ------------------------*- 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_STDLIB_STRFROMD_H
+#define LLVM_LIBC_SRC_STDLIB_STRFROMD_H
+
+#include <stddef.h>
+
+namespace LIBC_NAMESPACE {
+
+int strfromd(char *__restrict s, size_t n, const char *__restrict format,
+             double fp);
+
+} // namespace LIBC_NAMESPACE
+
+#endif // LLVM_LIBC_SRC_STDLIB_STRFROMD_H
diff --git a/libc/src/stdlib/strfromf.h b/libc/src/stdlib/strfromf.h
index b551a58af05a3e..492c2c33cf080b 100644
--- a/libc/src/stdlib/strfromf.h
+++ b/libc/src/stdlib/strfromf.h
@@ -18,4 +18,4 @@ int strfromf(char *__restrict s, size_t n, const char *__restrict format,
 
 } // namespace LIBC_NAMESPACE
 
-#endif // LLVM_LIBC_SRC_STDLIB_STRTOF_H
+#endif // LLVM_LIBC_SRC_STDLIB_STRFROMF_H
diff --git a/libc/src/stdlib/strfroml.cpp b/libc/src/stdlib/strfroml.cpp
new file mode 100644
index 00000000000000..40459651353077
--- /dev/null
+++ b/libc/src/stdlib/strfroml.cpp
@@ -0,0 +1,44 @@
+//===-- Implementation of strfroml ------------------------------*- 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "src/stdlib/strfroml.h"
+#include "src/stdlib/str_from_util.h"
+
+#include <stdarg.h>
+#include <stddef.h>
+
+namespace LIBC_NAMESPACE {
+
+LLVM_LIBC_FUNCTION(int, strfroml,
+                   (char *__restrict s, size_t n, const char *__restrict format,
+                    long double fp)) {
+  LIBC_ASSERT(s != nullptr);
+
+  printf_core::FormatSection section =
+      internal::parse_format_string(format, fp);
+  section.length_modifier = printf_core::LengthModifier::L;
+
+  printf_core::WriteBuffer wb(s, (n > 0 ? n - 1 : 0));
+  printf_core::Writer writer(&wb);
+
+  int result = 0;
+  if (section.has_conv)
+    result = internal::strfromfloat_convert<long double>(&writer, section);
+  else
+    result = writer.write(section.raw_string);
+
+  if (result < 0)
+    return result;
+
+  if (n > 0)
+    wb.buff[wb.buff_cur] = '\0';
+
+  return writer.get_chars_written();
+}
+
+} // namespace LIBC_NAMESPACE
diff --git a/libc/src/stdlib/strfroml.h b/libc/src/stdlib/strfroml.h
new file mode 100644
index 00000000000000..05f939e187b885
--- /dev/null
+++ b/libc/src/stdlib/strfroml.h
@@ -0,0 +1,22 @@
+
+//===-- Implementation header for strfroml ------------------------*- 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_STDLIB_STRFROML_H
+#define LLVM_LIBC_SRC_STDLIB_STRFROML_H
+
+#include <stddef.h>
+
+namespace LIBC_NAMESPACE {
+
+int strfroml(char *__restrict s, size_t n, const char *__restrict format,
+             long double fp);
+
+} // namespace LIBC_NAMESPACE
+
+#endif // LLVM_LIBC_SRC_STDLIB_STRFROML_H
diff --git a/libc/test/src/stdlib/CMakeLists.txt b/libc/test/src/stdlib/CMakeLists.txt
index cb42bc56f51c5f..79113d943d1ae4 100644
--- a/libc/test/src/stdlib/CMakeLists.txt
+++ b/libc/test/src/stdlib/CMakeLists.txt
@@ -178,6 +178,26 @@ add_libc_test(
     libc.src.stdlib.strfromf
 )
 
+add_libc_test(
+  strfromd_test
+  SUITE
+    libc-stdlib-tests
+  SRCS
+    strfromd_test.cpp
+  DEPENDS
+    libc.src.stdlib.strfromd
+)
+
+add_libc_test(
+  strfroml_test
+  SUITE
+    libc-stdlib-tests
+  SRCS
+    strfroml_test.cpp
+  DEPENDS
+    libc.src.stdlib.strfroml
+)
+
 add_libc_test(
   abs_test
   SUITE
diff --git a/libc/test/src/stdlib/strfromd_test.cpp b/libc/test/src/stdlib/strfromd_test.cpp
new file mode 100644
index 00000000000000..60fadca597569b
--- /dev/null
+++ b/libc/test/src/stdlib/strfromd_test.cpp
@@ -0,0 +1,224 @@
+//===-- Unittests for strfromd --------------------------------------------===//
+//
+// 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/stdlib/strfromd.h"
+#include "test/UnitTest/Test.h"
+
+// Use when the buffsize is sufficient to hold all of the result string,
+// including the null byte.
+#define ASSERT_STREQ_LEN_STRFROMD(actual_written, actual_str, expected_str)    \
+  EXPECT_EQ(actual_written, static_cast<int>(sizeof(expected_str) - 1));       \
+  EXPECT_STREQ(actual_str, expected_str);
+
+TEST(LlvmLibcStrfromdTest, FloatDecimalFormat) {
+  char buff[500];
+  int written;
+
+  written = LIBC_NAMESPACE::strfromd(buff, 99, "%f", 1.0);
+  ASSERT_STREQ_LEN_STRFROMD(written, buff, "1.000000");
+
+  written = LIBC_NAMESPACE::strfromd(buff, 99, "%F", -1.0);
+  ASSERT_STREQ_LEN_STRFROMD(written, buff, "-1.000000");
+
+  written = LIBC_NAMESPACE::strfromd(buff, 99, "%f", -1.234567);
+  ASSERT_STREQ_LEN_STRFROMD(written, buff, "-1.234567");
+
+  written = LIBC_NAMESPACE::strfromd(buff, 99, "%f", 0.0);
+  ASSERT_STREQ_LEN_STRFROMD(written, buff, "0.000000");
+
+  written = LIBC_NAMESPACE::strfromd(buff, 99, "%f", 1.5);
+  ASSERT_STREQ_LEN_STRFROMD(written, buff, "1.500000");
+
+  written = LIBC_NAMESPACE::strfromd(buff, 499, "%f", 1e300);
+  ASSERT_STREQ_LEN_STRFROMD(
+      written, buff,
+      "100000000000000005250476025520442024870446858110815915491585411551180245"
+      "798890819578637137508044786404370444383288387817694252323536043057564479"
+      "218478670698284838720092657580373783023379478809005936895323497079994508"
+      "111903896764088007465274278014249457925878882005684283811566947219638686"
+      "5459400540160.000000");
+
+  written = LIBC_NAMESPACE::strfromd(buff, 99, "%f", 0.1);
+  ASSERT_STREQ_LEN_STRFROMD(written, buff, "0.100000");
+
+  written = LIBC_NAMESPACE::strfromd(buff, 99, "%f", 1234567890123456789.0);
+  ASSERT_STREQ_LEN_STRFROMD(written, buff, "1234567890123456768.000000");
+
+  written = LIBC_NAMESPACE::strfromd(buff, 99, "%f", 9999999999999.99);
+  ASSERT_STREQ_LEN_STRFROMD(written, buff, "9999999999999.990234");
+
+  written = LIBC_NAMESPACE::strfromd(buff, 99, "%f", 0.1);
+  ASSERT_STREQ_LEN_STRFROMD(written, buff, "0.100000");
+
+  written = LIBC_NAMESPACE::strfromd(buff, 99, "%f", 1234567890123456789.0);
+  ASSERT_STREQ_LEN_STRFROMD(written, buff, "1234567890123456768.000000");
+
+  written = LIBC_NAMESPACE::strfromd(buff, 99, "%f", 9999999999999.99);
+  ASSERT_STREQ_LEN_STRFROMD(written, buff, "9999999999999.990234");
+
+  // Precision Tests
+  written = LIBC_NAMESPACE::strfromd(buff, 100, "%.2f", 9999999999999.99);
+  ASSERT_STREQ_LEN_STRFROMD(written, buff, "9999999999999.99");
+
+  written = LIBC_NAMESPACE::strfromd(buff, 100, "%.1f", 9999999999999.99);
+  ASSERT_STREQ_LEN_STRFROMD(written, buff, "10000000000000.0");
+
+  written = LIBC_NAMESPACE::strfromd(buff, 100, "%.5f", 1.25);
+  ASSERT_STREQ_LEN_STRFROMD(written, buff, "1.25000");
+
+  written = LIBC_NAMESPACE::strfromd(buff, 100, "%.0f", 1.25);
+  ASSERT_STREQ_LEN_STRFROMD(written, buff, "1");
+
+  written = LIBC_NAMESPACE::strfromd(buff, 100, "%.20f", 1.234e-10);
+  ASSERT_STREQ_LEN_STRFROMD(written, buff, "0.00000000012340000000");
+}
+
+TEST(LlvmLibcStrfromdTest, FloatHexExpFormat) {
+  char buff[101];
+  int written;
+
+  written = LIBC_NAMESPACE::strfromd(buff, 100, "%a", 1.0);
+  ASSERT_STREQ_LEN_STRFROMD(written, buff, "0x1p+0");
+
+  written = LIBC_NAMESPACE::strfromd(buff, 100, "%A", -1.0);
+  ASSERT_STREQ_LEN_STRFROMD(written, buff, "-0X1P+0");
+
+  written = LIBC_NAMESPACE::strfromd(buff, 100, "%a", -0x1.abcdef12345p0);
+  ASSERT_STREQ_LEN_STRFROMD(written, buff, "-0x1.abcdef12345p+0");
+
+  written = LIBC_NAMESPACE::strfromd(buff, 100, "%A", 0x1.abcdef12345p0);
+  ASSERT_STREQ_LEN_STRFROMD(written, buff, "0X1.ABCDEF12345P+0");
+
+  written = LIBC_NAMESPACE::strfromd(buff, 100, "%a", 0.0);
+  ASSERT_STREQ_LEN_STRFROMD(written, buff, "0x0p+0");
+
+  written = LIBC_NAMESPACE::strfromd(buff, 100, "%a", 1.0e100);
+  ASSERT_STREQ_LEN_STRFROMD(written, buff, "0x1.249ad2594c37dp+332");
+
+  written = LIBC_NAMESPACE::strfromd(buff, 100, "%a", 0.1);
+  ASSERT_STREQ_LEN_STRFROMD(written, buff, "0x1.999999999999ap-4");
+}
+
+TEST(LlvmLibcStrfromdTest, FloatDecimalExpFormat) {
+  char buff[101];
+  int written;
+
+  written = LIBC_NAMESPACE::strfromd(buff, 100, "%e", 1.0);
+  ASSERT_STREQ_LEN_STRFROMD(written, buff, "1.000000e+00");
+
+  written = LIBC_NAMESPACE::strfromd(buff, 100, "%E", -1.0);
+  ASSERT_STREQ_LEN_STRFROMD(written, buff, "-1.000000E+00");
+
+  written = LIBC_NAMESPACE::strfromd(buff, 100, "%e", -1.234567);
+  ASSERT_STREQ_LEN_STRFROMD(written, buff, "-1.234567e+00");
+
+  written = LIBC_NAMESPACE::strfromd(buff, 100, "%e", 0.0);
+  ASSERT_STREQ_LEN_STRFROMD(written, buff, "0.000000e+00");
+
+  written = LIBC_NAMESPACE::strfromd(buff, 100, "%e", 1.5);
+  ASSERT_STREQ_LEN_STRFROMD(written, buff, "1.500000e+00");
+
+  written = LIBC_NAMESPACE::strfromd(buff, 100, "%e", 1e300);
+  ASSERT_STREQ_LEN_STRFROMD(written, buff, "1.000000e+300");
+
+  written = LIBC_NAMESPACE::strfromd(buff, 100, "%e", 1234567890123456789.0);
+  ASSERT_STREQ_LEN_STRFROMD(written, buff, "1.234568e+18");
+
+  // Precision Tests
+  written = LIBC_NAMESPACE::strfromd(buff, 100, "%.1e", 1.0);
+  ASSERT_STREQ_LEN_STRFROMD(written, buff, "1.0e+00");
+
+  written = LIBC_NAMESPACE::strfromd(buff, 100, "%.1e", 1.99);
+  ASSERT_STREQ_LEN_STRFROMD(written, buff, "2.0e+00");
+
+  written = LIBC_NAMESPACE::strfromd(buff, 100, "%.1e", 9.99);
+  ASSERT_STREQ_LEN_STRFROMD(written, buff, "1.0e+01");
+}
+
+TEST(LlvmLibcStrfromdTest, FloatDecimalAutoFormat) {
+  char buff[120];
+  int written;
+
+  written = LIBC_NAMESPACE::strfromd(buff, 100, "%g", 1234567890123456789.0);
+  ASSERT_STREQ_LEN_STRFROMD(written, buff, "1.23457e+18");
+
+  written = LIBC_NAMESPACE::strfromd(buff, 100, "%g", 9999990000000.00);
+  ASSERT_STREQ_LEN_STRFROMD(written, buff, "9.99999e+12");
+
+  written = LIBC_NAMESPACE::strfromd(buff, 100, "%g", 9999999000000.00);
+  ASSERT_STREQ_LEN_STRFROMD(written, buff, "1e+13");
+
+  written = LIBC_NAMESPACE::strfromd(buff, 100, "%g", 0xa.aaaaaaaaaaaaaabp-7);
+  ASSERT_STREQ_LEN_STRFROMD(written, buff, "0.0833333");
+
+  written = LIBC_NAMESPACE::strfromd(buff, 100, "%g", 0.00001);
+  ASSERT_STREQ_LEN_STRFROMD(written, buff, "1e-05");
+
+  // Precision Tests
+  written = LIBC_NAMESPACE::strfromd(buff, 100, "%.0g", 0.0);
+  ASSERT_STREQ_LEN_STRFROMD(written, buff, "0");
+
+  written = LIBC_NAMESPACE::strfromd(buff, 100, "%.2g", 0.1);
+  ASSERT_STREQ_LEN_STRFROMD(written, buff, "0.1");
+
+  written = LIBC_NAMESPACE::strfromd(buff, 100, "%.2g", 1.09);
+  ASSERT_STREQ_LEN_STRFROMD(written, buff, "1.1");
+
+  written = LIBC_NAMESPACE::strfromd(buff, 100, "%.15g", 22.25);
+  ASSERT_STREQ_LEN_STRFROMD(written, buff, "22.25");
+
+  written = LIBC_NAMESPACE::strfromd(buff, 100, "%.20g", 1.234e-10);
+  ASSERT_STREQ_LEN_STRFROMD(written, buff, "1.2340000000000000814e-10");
+}
+
+TEST(LlvmLibcStrfromdTest, ImproperFormatString) {
+  char buff[100];
+  int written;
+
+  written = LIBC_NAMESPACE::strfromd(
+      buff, 37, "A simple string with no conversions.", 1.0);
+  ASSERT_STREQ_LEN_STRFROMD(written, buff,
+                            "A simple string with no conversions.");
+
+  written = LIBC_NAMESPACE::strfromd(
+      buff, 37, "%A simple string with one conversion, should overwrite.", 1.0);
+  ASSERT_STREQ_LEN_STRFROMD(written, buff, "0X1P+0");
+
+  written =
+      LIBC_NAMESPACE::strfromd(buff, 74,
+                               "A simple string with one conversion in %A "
+                               "between, writes string as it is",
+                               1.0);
+  ASSERT_STREQ_LEN_STRFROMD(
+      written, buff,
+      "A simple string with one conversion in %A between, "
+      "writes string as it is");
+
+  written = LIBC_NAMESPACE::strfromd(
+      buff, 36, "A simple string with one conversion", 1.0);
+  ASSERT_STREQ_LEN_STRFROMD(written, buff,
+                            "A simple string with one conversion");
+}
+
+// Test the result when the buffsize is not sufficient to hold
+// the result string.
+TEST(LlvmLibcStrfromdTest, InsufficientBuffsize) {
+  char buff[20];
+  int written;
+
+  written = LIBC_NAMESPACE::strfromd(buff, 5, "%f", 1234567890.0);
+  EXPECT_EQ(written, 17);
+  ASSERT_STREQ(buff, "1234");
+
+  written = LIBC_NAMESPACE::strfromd(buff, 5, "%.5f", 1.05);
+  EXPECT_EQ(written, 7);
+  ASSERT_STREQ(buff, "1.05");
+
+  written = LIBC_NAMESPACE::strfromd(buff, 0, "%g", 1.0);
+  EXPECT_EQ(written, 1);
+}
diff --git a/libc/test/src/stdlib/strfromf_test.cpp b/libc/test/src/stdlib/strfromf_test.cpp
index c5489f5f3af2ac..86edeaab853443 100644
--- a/libc/test/src/stdlib/strfromf_test.cpp
+++ b/libc/test/src/stdlib/strfromf_test.cpp
@@ -9,29 +9,22 @@
 #include "src/stdlib/strfromf.h"
 #include "test/UnitTest/Test.h"
 
+#define ASSERT_STREQ_LEN_STRFROMF(actual_written, actual_str, expected_str)    \
+  EXPECT_EQ(actual_written, static_cast<int>(sizeof(expected_str) - 1));       \
+  EXPECT_STREQ(actual_str, expected_str);
+
 TEST(LlvmLibcStrfromfTest, DecimalFloatFormat) {
   char buff[100];
   int written;
 
   written = LIBC_NAMESPACE::strfromf(buff, 16, "%f", 1.0);
-  EXPECT_EQ(written, 8);
-  ASSERT_STREQ(buff, "1.000000");
+  ASSERT_STREQ_LEN_STRFROMF(written, buff, "1.000000");
 
   written = LIBC_NAMESPACE::strfromf(buff, 20, "%f", 1234567890.0);
-  EXPECT_EQ(written, 17);
-  ASSERT_STREQ(buff, "1234567936.000000");
-
-  written = LIBC_NAMESPACE::strfromf(buff, 5, "%f", 1234567890.0);
-  EXPECT_EQ(written, 17);
-  ASSERT_STREQ(buff, "1234");
+  ASSERT_STREQ_LEN_STRFROMF(written, buff, "1234567936.000000");
 
   written = LIBC_NAMESPACE::strfromf(buff, 67, "%.3f", 1.0);
-  EXPECT_EQ(written, 5);
-  ASSERT_STREQ(buff, "1.000");
-
-  written = LIBC_NAMESPACE::strfromf(buff, 20, "%1f", 1234567890.0);
-  EXPECT_EQ(written, 3);
-  ASSERT_STREQ(buff, "%1f");
+  ASSERT_STREQ_LEN_STRFROMF(written, buff, "1.000");
 }
 
 TEST(LlvmLibcStrfromfTest, HexExpFloatFormat) {
@@ -54,12 +47,10 @@ TEST(LlvmLibcStrfromfTest, DecimalExpFloatFormat) {
   char buff[100];
   int written;
   written = LIBC_NAMESPACE::strfromf(buff, 20, "%.9e", 1234567890.0);
-  EXPECT_EQ(written, 15);
-  ASSERT_STREQ(buff, "1.234567936e+09");
+  ASSERT_STREQ_LEN_STRFROMF(written, buff, "1.234567936e+09");
 
   written = LIBC_NAMESPACE::strfromf(buff, 20, "%.9E", 1234567890.0);
-  EXPECT_EQ(written, 15);
-  ASSERT_STREQ(buff, "1.234567936E+09");
+  ASSERT_STREQ_LEN_STRFROMF(written, buff, "1.234567936E+09");
 }
 
 TEST(LlvmLibcStrfromfTest, AutoDecimalFloatFormat) {
@@ -67,41 +58,55 @@ TEST(LlvmLibcStrfromfTest, AutoDecimalFloatFormat) {
   int written;
 
   written = LIBC_NAMESPACE::strfromf(buff, 20, "%.9g", 1234567890.0);
-  EXPECT_EQ(written, 14);
-  ASSERT_STREQ(buff, "1.23456794e+09");
+  ASSERT_STREQ_LEN_STRFROMF(written, buff, "1.23456794e+09");
 
   written = LIBC_NAMESPACE::strfromf(buff, 20, "%.9G", 1234567890.0);
-  EXPECT_EQ(written, 14);
-  ASSERT_STREQ(buff, "1.23456794E+09");
-
-  written = LIBC_NAMESPACE::strfromf(buff, 0, "%G", 1.0);
-  EXPECT_EQ(written, 1);
+  ASSERT_STREQ_LEN_STRFROMF(written, buff, "1.23456794E+09");
 }
 
 TEST(LlvmLibcStrfromfTest, ImproperFormatString) {
-
   char buff[100];
-  int retval;
-  retval = LIBC_NAMESPACE::strfromf(
+  int written;
+  written = LIBC_NAMESPACE::strfromf(
       buff, 37, "A simple string with no conversions.", 1.0);
-  EXPECT_EQ(retval, 36);
-  ASSERT_STREQ(buff, "A simple string with no conversions.");
+  ASSERT_STREQ_LEN_STRFROMF(written, buff,
+                            "A simple string with no conversions.");
 
-  retval = LIBC_NAMESPACE::strfromf(
+  written = LIBC_NAMESPACE::strfromf(
       buff, 37, "%A simple string with one conversion, should overwrite.", 1.0);
-  EXPECT_EQ(retval, 6);
-  ASSERT_STREQ(buff, "0X1P+0");
-
-  retval = LIBC_NAMESPACE::strfromf(buff, 74,
-                                    "A simple string with one conversion in %A "
-                                    "between, writes string as it is",
-                                    1.0);
-  EXPECT_EQ(retval, 73);
-  ASSERT_STREQ(buff, "A simple string with one conversion in %A between, "
-                     "writes string as it is");
-
-  retval = LIBC_NAMESPACE::strfromf(buff, 36,
-                                    "A simple string with one conversion", 1.0);
-  EXPECT_EQ(retval, 35);
-  ASSERT_STREQ(buff, "A simple string with one conversion");
+  ASSERT_STREQ_LEN_STRFROMF(written, buff, "0X1P+0");
+
+  written =
+      LIBC_NAMESPACE::strfromf(buff, 74,
+                               "A simple string with one conversion in %A "
+                               "between, writes string as it is",
+                               1.0);
+  ASSERT_STREQ_LEN_STRFROMF(
+      written, buff,
+      "A simple string with one conversion in %A between, "
+      "writes string as it is");
+
+  written = LIBC_NAMESPACE::strfromf(
+      buff, 36, "A simple string with one conversion", 1.0);
+  ASSERT_STREQ_LEN_STRFROMF(written, buff,
+                            "A simple string with one conversion");
+
+  written = LIBC_NAMESPACE::strfromf(buff, 20, "%1f", 1234567890.0);
+  ASSERT_STREQ_LEN_STRFROMF(written, buff, "%1f");
+}
+
+TEST(LlvmLibcStrfromfTest, InsufficientBufsize) {
+  char buff[20];
+  int written;
+
+  written = LIBC_NAMESPACE::strfromf(buff, 5, "%f", 1234567890.0);
+  EXPECT_EQ(written, 17);
+  ASSERT_STREQ(buff, "1234");
+
+  written = LIBC_NAMESPACE::strfromf(buff, 5, "%.5f", 1.05);
+  EXPECT_EQ(written, 7);
+  ASSERT_STREQ(buff, "1.05");
+
+  written = LIBC_NAMESPACE::strfromf(buff, 0, "%g", 1.0);
+  EXPECT_EQ(written, 1);
 }
diff --git a/libc/test/src/stdlib/strfroml_test.cpp b/libc/test/src/stdlib/strfroml_test.cpp
new file mode 100644
index 00000000000000..55faae4f12d518
--- /dev/null
+++ b/libc/test/src/stdlib/strfroml_test.cpp
@@ -0,0 +1,159 @@
+//===-- Unittests for strfroml --------------------------------------------===//
+//
+// 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/stdlib/strfroml.h"
+#include "test/UnitTest/Test.h"
+
+#define ASSERT_STREQ_LEN_STRFROML(actual_written, actual_str, expected_str)    \
+  EXPECT_EQ(actual_written, static_cast<int>(sizeof(expected_str) - 1));       \
+  EXPECT_STREQ(actual_str, expected_str);
+
+TEST(LlvmLibcStrfromlTest, FloatDecimalFormat) {
+  char buff[100];
+  int written;
+
+  written = LIBC_NAMESPACE::strfroml(buff, 40, "%f", 1.0L);
+  ASSERT_STREQ_LEN_STRFROML(written, buff, "1.000000");
+
+  written = LIBC_NAMESPACE::strfroml(buff, 10, "%.f", -2.5L);
+  ASSERT_STREQ_LEN_STRFROML(written, buff, "-2");
+}
+
+TEST(LlvmLibcStrfromlTest, FloatExpFormat) {
+  char buff[100];
+  int written;
+#if defined(LIBC_TYPES_LONG_DOUBLE_IS_X86_FLOAT80)
+  written = LIBC_NAMESPACE::strfroml(buff, 90, "%.9e", 1000000000500000000.1L);
+  ASSERT_STREQ_LEN_STRFROML(written, buff, "1.000000001e+18");
+
+  written = LIBC_NAMESPACE::strfroml(buff, 90, "%.9e", 1000000000500000000.0L);
+  ASSERT_STREQ_LEN_STRFROML(written, buff, "1.000000000e+18");
+
+  written =
+      LIBC_NAMESPACE::strfroml(buff, 90, "%e", 0xf.fffffffffffffffp+16380L);
+  ASSERT_STREQ_LEN_STRFROML(written, buff, "1.189731e+4932");
+#endif
+}
+
+TEST(LlvmLibcStrfromlTest, FloatDecimalAutoFormat) {
+  char buff[100];
+  int written;
+
+#if defined(LIBC_TYPES_LONG_DOUBLE_IS_X86_FLOAT80)
+  written =
+      LIBC_NAMESPACE::strfroml(buff, 99, "%g", 0xf.fffffffffffffffp+16380L);
+  ASSERT_STREQ_LEN_STRFROML(written, buff, "1.18973e+4932");
+
+  written = LIBC_NAMESPACE::strfroml(buff, 99, "%g", 0xa.aaaaaaaaaaaaaabp-7L);
+  ASSERT_STREQ_LEN_STRFROML(written, buff, "0.0833333");
+
+  written = LIBC_NAMESPACE::strfroml(buff, 99, "%g", 9.99999999999e-100L);
+  ASSERT_STREQ_LEN_STRFROML(written, buff, "1e-99");
+#endif // LIBC_TYPES_LONG_DOUBLE_IS_X86_FLOAT80
+}
+
+TEST(LlvmLibcStrfromlTest, FloatHexExpFormat) {
+  char buff[100];
+  int written;
+
+  written = LIBC_NAMESPACE::strfroml(buff, 50, "%a", 0.1L);
+#if defined(LIBC_TYPES_LONG_DOUBLE_IS_X86_FLOAT80)
+  ASSERT_STREQ_LEN_STRFROML(written, buff, "0xc.ccccccccccccccdp-7");
+#elif defined(LIBC_TYPES_LONG_DOUBLE_IS_FLOAT64)
+  ASSERT_STREQ_LEN_STRFROML(written, buff, "0x1.999999999999ap-4");
+#elif defined(LIBC_TYPES_LONG_DOUBLE_IS_FLOAT128)
+  ASSERT_STREQ_LEN_STRFROML(written, buff,
+                            "0x1.999999999999999999999999999ap-4");
+#endif
+
+  written = LIBC_NAMESPACE::strfroml(buff, 20, "%.1a", 0.1L);
+#if defined(LIBC_TYPES_LONG_DOUBLE_IS_X86_FLOAT80)
+  ASSERT_STREQ_LEN_STRFROML(written, buff, "0xc.dp-7");
+#elif defined(LIBC_TYPES_LONG_DOUBLE_IS_FLOAT64)
+  ASSERT_STREQ_LEN_STRFROML(written, buff, "0x1.ap-4");
+#elif defined(LIBC_TYPES_LONG_DOUBLE_IS_FLOAT128)
+  ASSERT_STREQ_LEN_STRFROML(written, buff, "0x1.ap-4");
+#endif
+
+  written = LIBC_NAMESPACE::strfroml(buff, 50, "%a", 1.0e1000L);
+#if defined(LIBC_TYPES_LONG_DOUBLE_IS_X86_FLOAT80)
+  ASSERT_STREQ_LEN_STRFROML(written, buff, "0xf.38db1f9dd3dac05p+3318");
+#elif defined(LIBC_TYPES_LONG_DOUBLE_IS_FLOAT64)
+  ASSERT_STREQ_LEN_STRFROML(written, buff, "inf");
+#elif defined(LIBC_TYPES_LONG_DOUBLE_IS_FLOAT128)
+  ASSERT_STREQ_LEN_STRFROML(written, buff,
+                            "0x1.e71b63f3ba7b580af1a52d2a7379p+3321");
+#endif
+
+  written = LIBC_NAMESPACE::strfroml(buff, 50, "%a", 1.0e-1000L);
+#if defined(LIBC_TYPES_LONG_DOUBLE_IS_X86_FLOAT80)
+  ASSERT_STREQ_LEN_STRFROML(written, buff, "0x8.68a9188a89e1467p-3325");
+#elif defined(LIBC_TYPES_LONG_DOUBLE_IS_FLOAT64)
+  ASSERT_STREQ_LEN_STRFROML(written, buff, "0x0p+0");
+#elif defined(LIBC_TYPES_LONG_DOUBLE_IS_FLOAT128)
+  ASSERT_STREQ_LEN_STRFROML(written, buff,
+                            "0x1.0d152311513c28ce202627c06ec2p-3322");
+#endif
+
+  written =
+      LIBC_NAMESPACE::strfroml(buff, 50, "%.1a", 0xf.fffffffffffffffp16380L);
+#if defined(LIBC_TYPES_LONG_DOUBLE_IS_X86_FLOAT80)
+  ASSERT_STREQ_LEN_STRFROML(written, buff, "0x1.0p+16384");
+#elif defined(LIBC_TYPES_LONG_DOUBLE_IS_FLOAT64)
+  ASSERT_STREQ_LEN_STRFROML(written, buff, "inf");
+#elif defined(LIBC_TYPES_LONG_DOUBLE_IS_FLOAT128)
+  ASSERT_STREQ_LEN_STRFROML(written, buff, "0x2.0p+16383");
+#endif
+}
+
+TEST(LlvmLibcStrfromfTest, ImproperFormatString) {
+  char buff[100];
+  int written;
+  written = LIBC_NAMESPACE::strfroml(
+      buff, 37, "A simple string with no conversions.", 1.0);
+  ASSERT_STREQ_LEN_STRFROML(written, buff,
+                            "A simple string with no conversions.");
+
+  written = LIBC_NAMESPACE::strfroml(
+      buff, 37, "%A simple string with one conversion, should overwrite.", 1.0);
+  ASSERT_STREQ_LEN_STRFROML(written, buff, "0X8P-3");
+
+  written =
+      LIBC_NAMESPACE::strfroml(buff, 74,
+                               "A simple string with one conversion in %A "
+                               "between, writes string as it is",
+                               1.0);
+  ASSERT_STREQ_LEN_STRFROML(
+      written, buff,
+      "A simple string with one conversion in %A between, "
+      "writes string as it is");
+
+  written = LIBC_NAMESPACE::strfroml(
+      buff, 36, "A simple string with one conversion", 1.0);
+  ASSERT_STREQ_LEN_STRFROML(written, buff,
+                            "A simple string with one conversion");
+
+  written = LIBC_NAMESPACE::strfroml(buff, 20, "%1f", 1234567890.0);
+  ASSERT_STREQ_LEN_STRFROML(written, buff, "%1f");
+}
+
+TEST(LlvmLibcStrfromfTest, InsufficientBufsize) {
+  char buff[20];
+  int written;
+
+  written = LIBC_NAMESPACE::strfroml(buff, 5, "%f", 1234567890.0);
+  EXPECT_EQ(written, 17);
+  ASSERT_STREQ(buff, "1234");
+
+  written = LIBC_NAMESPACE::strfroml(buff, 5, "%.5f", 1.05);
+  EXPECT_EQ(written, 7);
+  ASSERT_STREQ(buff, "1.05");
+
+  written = LIBC_NAMESPACE::strfroml(buff, 0, "%g", 1.0);
+  EXPECT_EQ(written, 1);
+}



More information about the libc-commits mailing list