diff --git a/libc/src/__support/CMakeLists.txt b/libc/src/__support/CMakeLists.txt --- a/libc/src/__support/CMakeLists.txt +++ b/libc/src/__support/CMakeLists.txt @@ -100,6 +100,22 @@ libc.src.__support.common ) +add_header_library( + float_to_hex_string + HDRS + float_to_hex_string.h + DEPENDS + libc.src.__support.CPP.optional + libc.src.__support.CPP.span + libc.src.__support.CPP.string_view + libc.src.__support.CPP.type_traits + libc.src.__support.FPUtil.fp_bits + .integer_to_string + .libc_assert + libc.src.__support.common +) + + add_header_library( high_precision_decimal HDRS diff --git a/libc/src/__support/CPP/CMakeLists.txt b/libc/src/__support/CPP/CMakeLists.txt --- a/libc/src/__support/CPP/CMakeLists.txt +++ b/libc/src/__support/CPP/CMakeLists.txt @@ -63,6 +63,7 @@ .string_view libc.src.__support.common libc.src.__support.integer_to_string + libc.src.__support.float_to_hex_string libc.src.string.memory_utils.memcpy_implementation libc.src.string.memory_utils.memset_implementation libc.src.string.string_utils @@ -76,6 +77,7 @@ .span .string_view libc.src.__support.integer_to_string + libc.src.__support.float_to_hex_string ) add_header_library( diff --git a/libc/src/__support/CPP/string.h b/libc/src/__support/CPP/string.h --- a/libc/src/__support/CPP/string.h +++ b/libc/src/__support/CPP/string.h @@ -10,7 +10,8 @@ #define LLVM_LIBC_SRC_SUPPORT_CPP_STRING_H #include "src/__support/CPP/string_view.h" -#include "src/__support/integer_to_string.h" // IntegerToString +#include "src/__support/float_to_hex_string.h" // FloatToHexString +#include "src/__support/integer_to_string.h" // IntegerToString #include "src/string/memory_utils/memcpy_implementations.h" #include "src/string/memory_utils/memset_implementations.h" #include "src/string/string_utils.h" // string_length @@ -194,6 +195,14 @@ const auto &string_view = *maybe_string_view; return string(string_view.data(), string_view.size()); } + +template string float_to_hex_string(T value) { + char buf[FloatToHexString::bufsize()]; + auto maybe_string_view = FloatToHexString::convert(value, buf); + const auto &string_view = *maybe_string_view; + return string(string_view.data(), string_view.size()); +} + } // namespace internal LIBC_INLINE string to_string(int value) { @@ -215,10 +224,15 @@ return internal::to_dec_string(value); } -// TODO: Support floating point -// LIBC_INLINE string to_string(float value); -// LIBC_INLINE string to_string(double value); -// LIBC_INLINE string to_string(long double value); +LIBC_INLINE string to_string(float value) { + return internal::float_to_hex_string(value); +} +LIBC_INLINE string to_string(double value) { + return internal::float_to_hex_string(value); +} +LIBC_INLINE string to_string(long double value) { + return internal::float_to_hex_string(value); +} } // namespace cpp } // namespace __llvm_libc diff --git a/libc/src/__support/CPP/stringstream.h b/libc/src/__support/CPP/stringstream.h --- a/libc/src/__support/CPP/stringstream.h +++ b/libc/src/__support/CPP/stringstream.h @@ -9,10 +9,11 @@ #ifndef LLVM_LIBC_SRC_SUPPORT_CPP_STRINGSTREAM_H #define LLVM_LIBC_SRC_SUPPORT_CPP_STRINGSTREAM_H -#include "string_view.h" #include "span.h" +#include "string_view.h" #include "type_traits.h" +#include "src/__support/float_to_hex_string.h" #include "src/__support/integer_to_string.h" namespace __llvm_libc { @@ -66,11 +67,11 @@ } template , int> = 0> - StringStream &operator<<(T) { - // If this specialization gets activated, then the static_assert will - // trigger a compile error about missing floating point number support. - static_assert(!is_floating_point_v, - "Writing floating point numbers is not yet supported"); + StringStream &operator<<(T val) { + char buffer[FloatToHexString::bufsize()]; + auto float_to_hex_str = FloatToHexString::convert(val, buffer); + if (float_to_hex_str) + return operator<<(*float_to_hex_str); return *this; } diff --git a/libc/src/__support/float_to_hex_string.h b/libc/src/__support/float_to_hex_string.h new file mode 100644 --- /dev/null +++ b/libc/src/__support/float_to_hex_string.h @@ -0,0 +1,228 @@ +//===-- Hexadecimal Float Converter Utility ---------------------*- 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_SUPPORT_FLOAT_TO_HEX_STRING_H +#define LLVM_LIBC_SRC_SUPPORT_FLOAT_TO_HEX_STRING_H + +#include "src/__support/CPP/optional.h" +#include "src/__support/CPP/span.h" +#include "src/__support/CPP/string_view.h" +#include "src/__support/CPP/type_traits.h" +#include "src/__support/FPUtil/FPBits.h" +#include "src/__support/common.h" +#include "src/__support/integer_to_string.h" +#include "src/__support/libc_assert.h" + +#include +#include + +namespace __llvm_libc { + +// Convert float values to their hexadecimal string representation. +// TODO: examples + +class FloatToHexString { + using MantissaInt = fputil::FPBits::UIntType; + constexpr static size_t BITS_IN_HEX_DIGIT = 4; + + // This takes an explicit mantissa and exponent for a number that is not NAN + // or INF. It is assumed that subnormals are converted to this form. + LIBC_INLINE static cpp::string_view + convert_components(MantissaInt mantissa, int32_t exponent, bool is_negative, + size_t mantissa_width, cpp::span &buffer, + bool lowercase) { + const char a = lowercase ? 'a' : 'A'; + + // This is to handle situations where the mantissa isn't an even number of + // hex digits. This is primarily relevant for x86 80 bit long doubles, which + // have 63 bit mantissas. + if (mantissa_width % BITS_IN_HEX_DIGIT != 0) { + exponent -= mantissa_width % BITS_IN_HEX_DIGIT; + } + + // This is the max number of digits it can take to represent the mantissa. + // Since the number is in bits, we divide by 4, and then add one to account + // for the extra implicit bit. We use the larger of the two possible values + // since the size must be constant. + constexpr size_t MANT_BUFF_LEN = + (fputil::MantissaWidth::VALUE / BITS_IN_HEX_DIGIT) + 1; + char mant_buffer[MANT_BUFF_LEN]; + + size_t mant_cur = (mantissa_width / BITS_IN_HEX_DIGIT) + 1; + size_t first_non_zero = 1; + for (; mant_cur > 0; --mant_cur, mantissa /= 16) { + char new_digit = ((mantissa % 16) > 9) ? ((mantissa % 16) - 10 + a) + : ((mantissa % 16) + '0'); + mant_buffer[mant_cur - 1] = new_digit; + if (new_digit != '0' && first_non_zero < mant_cur) + first_non_zero = mant_cur; + } + size_t mant_digits = first_non_zero; + + char exp_buffer[IntegerToString::dec_bufsize()]; + + bool exponent_is_negative = false; + if (exponent < 0) { + exponent = -exponent; + exponent_is_negative = true; + } + + auto raw_exp_str = IntegerToString::dec(exponent, exp_buffer); + LIBC_ASSERT(raw_exp_str.has_value() && + "Converting the exponent to string should never fail."); + auto exp_str = *raw_exp_str; + + constexpr size_t PREFIX_LEN = 2; + char prefix[PREFIX_LEN]; + prefix[0] = '0'; + prefix[1] = a + ('x' - 'a'); + const cpp::string_view prefix_str(prefix, PREFIX_LEN); + + bool has_hexadecimal_point = mant_digits > 1; + constexpr char HEXADECIMAL_POINT = '.'; + + // This is for the letter 'p' before the exponent. + const char exp_seperator = a + ('p' - 'a'); + + size_t buff_cur = 0; + + // Sign + if (is_negative) { + buffer[buff_cur] = '-'; + ++buff_cur; + } + + // Prefix + buffer[buff_cur] = prefix[0]; + buffer[buff_cur + 1] = prefix[1]; + buff_cur += 2; + + // Leading Digit + buffer[buff_cur] = mant_buffer[0]; + ++buff_cur; + + // Point + if (has_hexadecimal_point) { + buffer[buff_cur] = HEXADECIMAL_POINT; + ++buff_cur; + } + + // Other Digits + if (mant_digits > 1) { + // TODO: proper memcpy + for (size_t i = 1; i < mant_digits; ++i, ++buff_cur) { + buffer[buff_cur] = mant_buffer[i]; + } + } + + // p + buffer[buff_cur] = exp_seperator; + ++buff_cur; + + // exponent sign (always displayed). + buffer[buff_cur] = exponent_is_negative ? '-' : '+'; + ++buff_cur; + + // exponent + // TODO: proper memcpy + for (size_t i = 0; i < exp_str.size(); ++i, ++buff_cur) { + buffer[buff_cur] = exp_str[i]; + } + + return cpp::string_view(buffer.data(), buff_cur); + } + + // This takes a float and handles the NAN and INF cases. If the float + // isn't one of those it decomposes it into its components for + // convert_components. + template + LIBC_INLINE static cpp::string_view + convert_raw_float(T raw_float, cpp::span &buffer, bool lowercase) { + fputil::FPBits float_bits(raw_float); + bool is_negative = float_bits.get_sign(); + + if (float_bits.is_inf_or_nan()) { + const char *float_str; + // if the mantissa is zero, this is INF, else NAN + if (float_bits.get_explicit_mantissa() == 0) { + float_str = lowercase ? "inf" : "INF"; + } else { + float_str = lowercase ? "nan" : "NAN"; + } + + size_t buff_cur = 0; + // Sign + if (is_negative) { + buffer[buff_cur] = '-'; + ++buff_cur; + } + + // INF or NAN. Since they're both three characters, we just write three + // characters. + buffer[buff_cur] = float_str[0]; + buffer[buff_cur + 1] = float_str[1]; + buffer[buff_cur + 2] = float_str[2]; + buff_cur += 3; + + return cpp::string_view(buffer.data(), buff_cur); + } + + int32_t exponent = float_bits.get_exponent(); + MantissaInt mantissa = float_bits.get_explicit_mantissa(); + + // Handle the exponent for numbers with a 0 exponent + if (exponent == -fputil::FPBits::EXPONENT_BIAS) { + if (mantissa > 0) // Subnormals + ++exponent; + else // Zeroes + exponent = 0; + } + + return convert_components(mantissa, exponent, is_negative, + fputil::MantissaWidth::VALUE, buffer, + lowercase); + } + +public: + template LIBC_INLINE static constexpr size_t bufsize() { + size_t sign_len = 1; + size_t prefix_len = 2; + size_t max_mantissa_digits = + (fputil::MantissaWidth::VALUE / BITS_IN_HEX_DIGIT) + 1; + size_t exp_seperator_len = 1; + size_t max_exponent_len = IntegerToString::dec_bufsize(); + + return sign_len + prefix_len + max_mantissa_digits + exp_seperator_len + + max_exponent_len; + } + + LIBC_INLINE static constexpr size_t float_bufsize() { + return bufsize(); + } + + LIBC_INLINE static constexpr size_t double_bufsize() { + return bufsize(); + } + + LIBC_INLINE static constexpr size_t long_double_bufsize() { + return bufsize(); + } + + template , int> = 0> + LIBC_INLINE static cpp::optional + convert(T val, cpp::span buffer, bool lowercase = true) { + if (buffer.size() < bufsize()) { + return cpp::optional(); + } + return convert_raw_float(val, buffer, lowercase); + } +}; + +} // namespace __llvm_libc + +#endif // LLVM_LIBC_SRC_SUPPORT_FLOAT_TO_HEX_STRING_H diff --git a/libc/test/UnitTest/TestLogger.cpp b/libc/test/UnitTest/TestLogger.cpp --- a/libc/test/UnitTest/TestLogger.cpp +++ b/libc/test/UnitTest/TestLogger.cpp @@ -1,3 +1,11 @@ +//===-- Utilities to log to standard output during tests ------------------===// +// +// 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 "test/UnitTest/TestLogger.h" #include "src/__support/CPP/string.h" #include "src/__support/CPP/string_view.h" @@ -53,7 +61,10 @@ template TestLogger & TestLogger::operator<< (unsigned long long); -// TODO: Add floating point formatting once it's supported by StringStream. +// is_floating_point specializations +template TestLogger &TestLogger::operator<< (float); +template TestLogger &TestLogger::operator<< (double); +template TestLogger &TestLogger::operator<< (long double); TestLogger tlog; diff --git a/libc/test/src/__support/CPP/string_test.cpp b/libc/test/src/__support/CPP/string_test.cpp --- a/libc/test/src/__support/CPP/string_test.cpp +++ b/libc/test/src/__support/CPP/string_test.cpp @@ -186,11 +186,16 @@ } } -TEST(LlvmLibcStringTest, ToString) { +TEST(LlvmLibcStringTest, IntToString) { struct CStringPair { const int value; const string str; - } kTestPairs[] = {{123, "123"}, {0, "0"}, {-321, "-321"}}; + } kTestPairs[] = { + // Int tests. + {123, "123"}, + {0, "0"}, + {-321, "-321"}, + }; for (const auto &[value, str] : kTestPairs) { ASSERT_EQ(to_string((int)(value)), str); ASSERT_EQ(to_string((long)(value)), str); @@ -202,3 +207,26 @@ } } } + +TEST(LlvmLibcStringTest, FloatToString) { + const double inf = 0x1.0p+1022 * 0x1.0p+1022; + const double nan = (inf / inf); + // Double tests + EXPECT_EQ(to_string(0x0p+0), string("0x0p+0")); + EXPECT_EQ(to_string(0x1000p-50), string("0x1p-38")); + EXPECT_EQ(to_string(0.1), string("0x1.999999999999ap-4")); + EXPECT_EQ(to_string(0x1p+1022), string("0x1p+1022")); + EXPECT_EQ(to_string(0x1p-1022), string("0x1p-1022")); + EXPECT_EQ(to_string(0x1p-1070), string("0x0.000000000001p-1022")); + EXPECT_EQ(to_string(inf), string("inf")); + EXPECT_EQ(to_string(nan), string("nan")); + + // Long double tests +#if defined(SPECIAL_X86_LONG_DOUBLE) + EXPECT_EQ(to_string(1.0L), string("0x8p-3")); +#elif defined(LONG_DOUBLE_IS_DOUBLE) + EXPECT_EQ(to_string(1.0L), string("0x1p+0")); +#else // 128 bit long double + EXPECT_EQ(to_string(1.0L), string("0x1p+0")); +#endif +}