From 6de62aa2c26eb2f2b76c115e144de383b3b643b5 Mon Sep 17 00:00:00 2001 From: Vinayak Dev 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 c216f43496270..e863ba56604e7 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 920036adfed5c..79c6573814243 100644 --- a/libc/spec/stdc.td +++ b/libc/spec/stdc.td @@ -956,7 +956,9 @@ def StdC : StandardSpec<"stdc"> { FunctionSpec<"rand", RetValSpec, [ArgSpec]>, FunctionSpec<"srand", RetValSpec, [ArgSpec]>, + FunctionSpec<"strfromd", RetValSpec, [ArgSpec, ArgSpec, ArgSpec, ArgSpec]>, FunctionSpec<"strfromf", RetValSpec, [ArgSpec, ArgSpec, ArgSpec, ArgSpec]>, + FunctionSpec<"strfroml", RetValSpec, [ArgSpec, ArgSpec, ArgSpec, ArgSpec]>, FunctionSpec<"strtof", RetValSpec, [ArgSpec, ArgSpec]>, FunctionSpec<"strtod", RetValSpec, [ArgSpec, ArgSpec]>, diff --git a/libc/src/stdlib/CMakeLists.txt b/libc/src/stdlib/CMakeLists.txt index 22f7f990fb08a..a2b16e73f3532 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 0000000000000..1d02a7ad11244 --- /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 +#include + +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(&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 0000000000000..d2c3fefb6300d --- /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 + +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 b551a58af05a3..492c2c33cf080 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 0000000000000..4045965135307 --- /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 +#include + +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(&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 0000000000000..05f939e187b88 --- /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 + +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 cb42bc56f51c5..79113d943d1ae 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 0000000000000..60fadca597569 --- /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(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 c5489f5f3af2a..86edeaab85344 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(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 0000000000000..55faae4f12d51 --- /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(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); +}