diff --git a/libc/src/stdio/printf_core/CMakeLists.txt b/libc/src/stdio/printf_core/CMakeLists.txt --- a/libc/src/stdio/printf_core/CMakeLists.txt +++ b/libc/src/stdio/printf_core/CMakeLists.txt @@ -65,9 +65,11 @@ ptr_converter.h oct_converter.h write_int_converter.h + float_hex_converter.h DEPENDS .writer .core_structs + libc.src.__support.CPP.limits ) diff --git a/libc/src/stdio/printf_core/converter.cpp b/libc/src/stdio/printf_core/converter.cpp --- a/libc/src/stdio/printf_core/converter.cpp +++ b/libc/src/stdio/printf_core/converter.cpp @@ -44,19 +44,20 @@ case 'x': case 'X': return convert_hex(writer, to_conv); - // TODO(michaelrj): add a flag to disable float point values here - case 'f': - case 'F': - // return convert_float_decimal(writer, to_conv); - case 'e': - case 'E': - // return convert_float_dec_exp(writer, to_conv); +#ifndef LLVM_LIBC_PRINTF_DISABLE_FLOAT + // case 'f': + // case 'F': + // return convert_float_decimal(writer, to_conv); + // case 'e': + // case 'E': + // return convert_float_dec_exp(writer, to_conv); case 'a': case 'A': - // return convert_float_hex_exp(writer, to_conv); - case 'g': - case 'G': + return convert_float_hex_exp(writer, to_conv); + // case 'g': + // case 'G': // return convert_float_mixed(writer, to_conv); +#endif // LLVM_LIBC_PRINTF_DISABLE_FLOAT #ifndef LLVM_LIBC_PRINTF_DISABLE_WRITE_INT case 'n': return convert_write_int(writer, to_conv); diff --git a/libc/src/stdio/printf_core/converter_atlas.h b/libc/src/stdio/printf_core/converter_atlas.h --- a/libc/src/stdio/printf_core/converter_atlas.h +++ b/libc/src/stdio/printf_core/converter_atlas.h @@ -27,11 +27,13 @@ // defines convert_hex #include "src/stdio/printf_core/hex_converter.h" -// TODO(michaelrj): add a flag to disable float point values here +#ifndef LLVM_LIBC_PRINTF_DISABLE_FLOAT // defines convert_float_decimal // defines convert_float_dec_exp // defines convert_float_hex_exp +#include "src/stdio/printf_core/float_hex_converter.h" // defines convert_float_mixed +#endif // LLVM_LIBC_PRINTF_DISABLE_FLOAT #ifndef LLVM_LIBC_PRINTF_DISABLE_WRITE_INT #include "src/stdio/printf_core/write_int_converter.h" diff --git a/libc/src/stdio/printf_core/float_hex_converter.h b/libc/src/stdio/printf_core/float_hex_converter.h new file mode 100644 --- /dev/null +++ b/libc/src/stdio/printf_core/float_hex_converter.h @@ -0,0 +1,273 @@ +//===-- Hexadecimal Converter for printf ------------------------*- 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_STDIO_PRINTF_CORE_FLOAT_HEX_CONVERTER_H +#define LLVM_LIBC_SRC_STDIO_PRINTF_CORE_FLOAT_HEX_CONVERTER_H + +#include "src/__support/FPUtil/FPBits.h" +#include "src/stdio/printf_core/converter_utils.h" +#include "src/stdio/printf_core/core_structs.h" +#include "src/stdio/printf_core/writer.h" + +#include +#include + +namespace __llvm_libc { +namespace printf_core { + +using MantissaInt = fputil::FPBits::UIntType; + +int inline convert_float_hex_exp(Writer *writer, const FormatSection &to_conv) { + // All of the letters will be defined relative to variable a, which will be + // the appropriate case based on the name of the conversion. + // Since the name of the conversion is also 'a', we can just use it directly. + const char a = to_conv.conv_name; + + bool is_negative; + int exponent; + MantissaInt mantissa; + bool is_inf_or_nan; + uint32_t mantissa_width; + int exponent_bias; + if (to_conv.length_modifier == LengthModifier::L) { + mantissa_width = fputil::MantissaWidth::VALUE; + exponent_bias = fputil::FPBits::EXPONENT_BIAS; + fputil::FPBits::UIntType float_raw = to_conv.conv_val_raw; + fputil::FPBits float_bits(float_raw); + is_negative = float_bits.get_sign(); + exponent = float_bits.get_exponent(); + mantissa = float_bits.get_explicit_mantissa(); + is_inf_or_nan = float_bits.is_inf_or_nan(); + } else { + mantissa_width = fputil::MantissaWidth::VALUE; + exponent_bias = fputil::FPBits::EXPONENT_BIAS; + fputil::FPBits::UIntType float_raw = to_conv.conv_val_raw; + fputil::FPBits float_bits(float_raw); + is_negative = float_bits.get_sign(); + exponent = float_bits.get_exponent(); + mantissa = float_bits.get_explicit_mantissa(); + is_inf_or_nan = float_bits.is_inf_or_nan(); + } + + char sign_char = 0; + + if (is_negative) + sign_char = '-'; + else if ((to_conv.flags & FormatFlags::FORCE_SIGN) == FormatFlags::FORCE_SIGN) + sign_char = '+'; // FORCE_SIGN has precedence over SPACE_PREFIX + else if ((to_conv.flags & FormatFlags::SPACE_PREFIX) == + FormatFlags::SPACE_PREFIX) + sign_char = ' '; + + // TODO: move the inf/nan handling to a seperate conversion, since the + // functionality is identical accross all float conversions. + if (is_inf_or_nan) { + // Both "inf" and "nan" are the same number of characters, being 3. + int padding = to_conv.min_width - (sign_char > 0 ? 1 : 0) - 3; + + // The right justified pattern is (spaces), (sign), inf/nan + // The left justified pattern is (sign), inf/nan, (spaces) + + if (padding > 0 && ((to_conv.flags & FormatFlags::LEFT_JUSTIFIED) != + FormatFlags::LEFT_JUSTIFIED)) + RET_IF_RESULT_NEGATIVE(writer->write_chars(' ', padding)); + + if (sign_char) + RET_IF_RESULT_NEGATIVE(writer->write(&sign_char, 1)); + if (mantissa == 0) { // inf + RET_IF_RESULT_NEGATIVE(writer->write((a == 'a' ? "inf" : "INF"), 3)); + } else { // nan + RET_IF_RESULT_NEGATIVE(writer->write((a == 'a' ? "nan" : "NAN"), 3)); + } + + if (padding > 0 && ((to_conv.flags & FormatFlags::LEFT_JUSTIFIED) == + FormatFlags::LEFT_JUSTIFIED)) + RET_IF_RESULT_NEGATIVE(writer->write_chars(' ', padding)); + + return 0; + } + + // Handle the exponent for numbers with a 0 exponent + if (exponent == -exponent_bias) { + if (mantissa > 0) // Subnormals + ++exponent; + else // Zeroes + exponent = 0; + } + + constexpr size_t BITS_IN_HEX_DIGIT = 4; + + // 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_len = (mantissa_width / BITS_IN_HEX_DIGIT) + 1; + + // Precision only tracks the number of digits after the hexadecimal point, so + // we have to add one to account for the digit before the hexadecimal point. + if (to_conv.precision + 1 < static_cast(mant_len) && + to_conv.precision + 1 > 0) { + const size_t intended_digits = to_conv.precision + 1; + const size_t shift_amount = + (mant_len - intended_digits) * BITS_IN_HEX_DIGIT; + + const MantissaInt truncated_bits = + mantissa & ((MantissaInt(1) << shift_amount) - 1); + const MantissaInt halfway_const = MantissaInt(1) << (shift_amount - 1); + + mantissa >>= shift_amount; + + // Round to nearest, if it's exactly halfway then round to even. + if (truncated_bits > halfway_const) + ++mantissa; + else if (truncated_bits == halfway_const) + mantissa = mantissa + (mantissa & 1); + + // If the rounding caused an overflow, shift the mantissa and adjust the + // exponent to match. + if (mantissa >= (MantissaInt(1) << (intended_digits * BITS_IN_HEX_DIGIT))) { + mantissa >>= BITS_IN_HEX_DIGIT; + exponent += BITS_IN_HEX_DIGIT; + } + + mant_len = intended_digits; + } + + size_t mant_cur = mant_len; + 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; + if (to_conv.precision >= 0) + mant_digits = mant_len; + + // This approximates the number of digits it will take to represent the + // exponent. The calculation is ceil((bits * 5) / 16). Floor also works, but + // only on exact multiples of 16. We add 1 for the sign. + // Relevant sizes: + // 15 -> 5 + // 11 -> 4 + // 8 -> 3 + constexpr size_t EXP_LEN = + (((fputil::ExponentWidth::VALUE * 5) + 15) / 16) + 1; + char exp_buffer[EXP_LEN]; + + bool exp_is_negative = false; + if (exponent < 0) { + exp_is_negative = true; + exponent = -exponent; + } + + size_t exp_cur = EXP_LEN; + for (; exponent > 0; --exp_cur, exponent /= 10) { + exp_buffer[exp_cur - 1] = (exponent % 10) + '0'; + } + if (exp_cur == EXP_LEN) { // if nothing else was written, write a 0. + exp_buffer[EXP_LEN - 1] = '0'; + exp_cur = EXP_LEN - 1; + } + + exp_buffer[exp_cur - 1] = exp_is_negative ? '-' : '+'; + --exp_cur; + + // these are signed to prevent underflow due to negative values. The eventual + // values will always be non-negative. + int trailing_zeroes = 0; + int padding; + + // prefix is "0x", and always appears. + constexpr size_t PREFIX_LEN = 2; + char prefix[PREFIX_LEN]; + prefix[0] = '0'; + prefix[1] = a + ('x' - 'a'); + + // If the precision is greater than the actual result, pad with 0s + if (to_conv.precision > static_cast(mant_digits - 1)) + trailing_zeroes = to_conv.precision - (mant_digits - 1); + + bool has_hexadecimal_point = + (mant_digits > 1) || ((to_conv.flags & FormatFlags::ALTERNATE_FORM) == + FormatFlags::ALTERNATE_FORM); + constexpr char HEXADECIMAL_POINT = '.'; + + // This is for the letter 'p' before the exponent. + const char exp_seperator = a + ('p' - 'a'); + constexpr int EXP_SEPERATOR_LEN = 1; + + padding = to_conv.min_width - (sign_char > 0 ? 1 : 0) - PREFIX_LEN - + mant_digits - (has_hexadecimal_point ? 1 : 0) - EXP_SEPERATOR_LEN - + (EXP_LEN - exp_cur); + if (padding < 0) + padding = 0; + + if ((to_conv.flags & FormatFlags::LEFT_JUSTIFIED) == + FormatFlags::LEFT_JUSTIFIED) { + // The pattern is (sign), 0x, digit, (.), (other digits), (zeroes), p, + // exponent, (spaces) + if (sign_char > 0) + RET_IF_RESULT_NEGATIVE(writer->write(&sign_char, 1)); + RET_IF_RESULT_NEGATIVE(writer->write(prefix, PREFIX_LEN)); + RET_IF_RESULT_NEGATIVE(writer->write(mant_buffer, 1)); + if (has_hexadecimal_point) + RET_IF_RESULT_NEGATIVE(writer->write(&HEXADECIMAL_POINT, 1)); + if (mant_digits > 1) + RET_IF_RESULT_NEGATIVE(writer->write(mant_buffer + 1, mant_digits - 1)); + if (trailing_zeroes > 0) + RET_IF_RESULT_NEGATIVE(writer->write_chars('0', trailing_zeroes)); + RET_IF_RESULT_NEGATIVE(writer->write(&exp_seperator, EXP_SEPERATOR_LEN)); + RET_IF_RESULT_NEGATIVE( + writer->write(exp_buffer + exp_cur, EXP_LEN - exp_cur)); + if (padding > 0) + RET_IF_RESULT_NEGATIVE(writer->write_chars(' ', padding)); + } else { + // The pattern is (spaces), (sign), 0x, (zeroes), digit, (.), (other + // digits), (zeroes), p, exponent + if ((padding > 0) && ((to_conv.flags & FormatFlags::LEADING_ZEROES) != + FormatFlags::LEADING_ZEROES)) + RET_IF_RESULT_NEGATIVE(writer->write_chars(' ', padding)); + if (sign_char > 0) + RET_IF_RESULT_NEGATIVE(writer->write(&sign_char, 1)); + RET_IF_RESULT_NEGATIVE(writer->write(prefix, PREFIX_LEN)); + if ((padding > 0) && ((to_conv.flags & FormatFlags::LEADING_ZEROES) == + FormatFlags::LEADING_ZEROES)) + RET_IF_RESULT_NEGATIVE(writer->write_chars('0', padding)); + RET_IF_RESULT_NEGATIVE(writer->write(mant_buffer, 1)); + if (has_hexadecimal_point) + RET_IF_RESULT_NEGATIVE(writer->write(&HEXADECIMAL_POINT, 1)); + if (mant_digits > 1) + RET_IF_RESULT_NEGATIVE(writer->write(mant_buffer + 1, mant_digits - 1)); + if (trailing_zeroes > 0) + RET_IF_RESULT_NEGATIVE(writer->write_chars('0', trailing_zeroes)); + RET_IF_RESULT_NEGATIVE(writer->write(&exp_seperator, EXP_SEPERATOR_LEN)); + RET_IF_RESULT_NEGATIVE( + writer->write(exp_buffer + exp_cur, EXP_LEN - exp_cur)); + } + return 0; +} + +} // namespace printf_core +} // namespace __llvm_libc + +#endif // LLVM_LIBC_SRC_STDIO_PRINTF_CORE_FLOAT_HEX_CONVERTER_H diff --git a/libc/src/stdio/printf_core/parser.cpp b/libc/src/stdio/printf_core/parser.cpp --- a/libc/src/stdio/printf_core/parser.cpp +++ b/libc/src/stdio/printf_core/parser.cpp @@ -121,7 +121,7 @@ break; } break; - // TODO(michaelrj): add a flag to disable float point values here +#ifndef LLVM_LIBC_PRINTF_DISABLE_FLOAT case ('f'): case ('F'): case ('e'): @@ -137,6 +137,7 @@ section.conv_val_raw = bit_cast::UIntType>( GET_ARG_VAL_SIMPLEST(long double, conv_index)); break; +#endif // LLVM_LIBC_PRINTF_DISABLE_FLOAT #ifndef LLVM_LIBC_PRINTF_DISABLE_WRITE_INT case ('n'): #endif // LLVM_LIBC_PRINTF_DISABLE_WRITE_INT @@ -339,7 +340,7 @@ break; } break; - // TODO(michaelrj): add a flag to disable float point values here +#ifndef LLVM_LIBC_PRINTF_DISABLE_FLOAT case ('f'): case ('F'): case ('e'): @@ -353,6 +354,7 @@ else conv_size = TYPE_DESC; break; +#endif // LLVM_LIBC_PRINTF_DISABLE_FLOAT #ifndef LLVM_LIBC_PRINTF_DISABLE_WRITE_INT case ('n'): #endif // LLVM_LIBC_PRINTF_DISABLE_WRITE_INT @@ -395,12 +397,13 @@ args_cur.next_var(); else if (cur_type_desc == TYPE_DESC) args_cur.next_var(); - // TODO(michaelrj): add a flag to disable float point values here +#ifndef LLVM_LIBC_PRINTF_DISABLE_FLOAT // Floating point numbers are stored separately from the other arguments. else if (cur_type_desc == TYPE_DESC) args_cur.next_var(); else if (cur_type_desc == TYPE_DESC) args_cur.next_var(); +#endif // LLVM_LIBC_PRINTF_DISABLE_FLOAT // pointers may be stored separately from normal values. else if (cur_type_desc == TYPE_DESC) args_cur.next_var(); diff --git a/libc/test/src/stdio/sprintf_test.cpp b/libc/test/src/stdio/sprintf_test.cpp --- a/libc/test/src/stdio/sprintf_test.cpp +++ b/libc/test/src/stdio/sprintf_test.cpp @@ -8,8 +8,15 @@ #include "src/stdio/sprintf.h" +#include "src/__support/FPUtil/FPBits.h" +#include "src/__support/FPUtil/PlatformDefs.h" #include "utils/UnitTest/Test.h" +// Subtract 1 from sizeof(expected_str) to account for the null byte. +#define ASSERT_STREQ_LEN(actual_written, actual_str, expected_str) \ + EXPECT_EQ(actual_written, static_cast(sizeof(expected_str) - 1)); \ + EXPECT_STREQ(actual_str, expected_str); + TEST(LlvmLibcSPrintfTest, SimpleNoConv) { char buff[64]; int written; @@ -483,6 +490,251 @@ ASSERT_STREQ(buff, "0077 01000000000000 002 "); } +#ifndef LLVM_LIBC_PRINTF_DISABLE_FLOAT +TEST(LlvmLibcSPrintfTest, FloatHexExpConv) { + char buff[64]; + int written; + double inf = __llvm_libc::fputil::FPBits::inf().get_val(); + double nan = __llvm_libc::fputil::FPBits::build_nan(1); + + written = __llvm_libc::sprintf(buff, "%a", 1.0); + ASSERT_STREQ_LEN(written, buff, "0x1p+0"); + + written = __llvm_libc::sprintf(buff, "%A", -1.0); + ASSERT_STREQ_LEN(written, buff, "-0X1P+0"); + + written = __llvm_libc::sprintf(buff, "%a", -0x1.abcdef12345p0); + ASSERT_STREQ_LEN(written, buff, "-0x1.abcdef12345p+0"); + + written = __llvm_libc::sprintf(buff, "%A", 0x1.abcdef12345p0); + ASSERT_STREQ_LEN(written, buff, "0X1.ABCDEF12345P+0"); + + written = __llvm_libc::sprintf(buff, "%a", 0.0); + ASSERT_STREQ_LEN(written, buff, "0x0p+0"); + + written = __llvm_libc::sprintf(buff, "%a", 1.0e100); + ASSERT_STREQ_LEN(written, buff, "0x1.249ad2594c37dp+332"); + + written = __llvm_libc::sprintf(buff, "%a", 0.1); + ASSERT_STREQ_LEN(written, buff, "0x1.999999999999ap-4"); + + // Subnormal Tests. + + written = __llvm_libc::sprintf(buff, "%a", 0x1.0p-1027); + ASSERT_STREQ_LEN(written, buff, "0x0.08p-1022"); + + written = __llvm_libc::sprintf(buff, "%a", 0x1.0p-1025); + ASSERT_STREQ_LEN(written, buff, "0x0.2p-1022"); + + written = __llvm_libc::sprintf(buff, "%a", 0x1.0p-1023); + ASSERT_STREQ_LEN(written, buff, "0x0.8p-1022"); + + written = __llvm_libc::sprintf(buff, "%a", 0x1.0p-1022); + ASSERT_STREQ_LEN(written, buff, "0x1p-1022"); + + written = __llvm_libc::sprintf(buff, "%a", 0x1.0p-1074); + ASSERT_STREQ_LEN(written, buff, "0x0.0000000000001p-1022"); + + // Inf/Nan Tests. + + written = __llvm_libc::sprintf(buff, "%a", inf); + ASSERT_STREQ_LEN(written, buff, "inf"); + + written = __llvm_libc::sprintf(buff, "%A", -inf); + ASSERT_STREQ_LEN(written, buff, "-INF"); + + written = __llvm_libc::sprintf(buff, "%a", nan); + ASSERT_STREQ_LEN(written, buff, "nan"); + + written = __llvm_libc::sprintf(buff, "%A", -nan); + ASSERT_STREQ_LEN(written, buff, "-NAN"); + + // Length Modifier Tests. + + written = __llvm_libc::sprintf(buff, "%La", 0.1L); +#if defined(SPECIAL_X86_LONG_DOUBLE) + ASSERT_STREQ_LEN(written, buff, "0xc.ccccccccccccccdp-7"); +#elif defined(LONG_DOUBLE_IS_DOUBLE) + ASSERT_STREQ_LEN(written, buff, "0x1.999999999999ap-4"); +#else // 128 bit long double + ASSERT_STREQ_LEN(written, buff, "0x1.999999999999999999999999999ap-4"); +#endif + + written = __llvm_libc::sprintf(buff, "%La", 1.0e1000L); +#if defined(SPECIAL_X86_LONG_DOUBLE) + ASSERT_STREQ_LEN(written, buff, "0xf.38db1f9dd3dac05p+3318"); +#elif defined(LONG_DOUBLE_IS_DOUBLE) + ASSERT_STREQ_LEN(written, buff, "inf"); +#else // 128 bit long double + ASSERT_STREQ_LEN(written, buff, "0x1.e71b63f3ba7b580af1a52d2a7379p+3321"); +#endif + + written = __llvm_libc::sprintf(buff, "%La", 1.0e-1000L); +#if defined(SPECIAL_X86_LONG_DOUBLE) + ASSERT_STREQ_LEN(written, buff, "0x8.68a9188a89e1467p-3325"); +#elif defined(LONG_DOUBLE_IS_DOUBLE) + ASSERT_STREQ_LEN(written, buff, "0x0p+0"); +#else // 128 bit long double + ASSERT_STREQ_LEN(written, buff, "0x1.0d152311513c28ce202627c06ec2p-3322"); +#endif + + // Min Width Tests. + + written = __llvm_libc::sprintf(buff, "%15a", 1.0); + ASSERT_STREQ_LEN(written, buff, " 0x1p+0"); + + written = __llvm_libc::sprintf(buff, "%15a", -1.0); + ASSERT_STREQ_LEN(written, buff, " -0x1p+0"); + + written = __llvm_libc::sprintf(buff, "%15a", 1.0e10); + ASSERT_STREQ_LEN(written, buff, " 0x1.2a05f2p+33"); + + written = __llvm_libc::sprintf(buff, "%15a", -1.0e10); + ASSERT_STREQ_LEN(written, buff, "-0x1.2a05f2p+33"); + + written = __llvm_libc::sprintf(buff, "%10a", 1.0e10); + ASSERT_STREQ_LEN(written, buff, "0x1.2a05f2p+33"); + + written = __llvm_libc::sprintf(buff, "%5a", inf); + ASSERT_STREQ_LEN(written, buff, " inf"); + + written = __llvm_libc::sprintf(buff, "%5a", -nan); + ASSERT_STREQ_LEN(written, buff, " -nan"); + + // Precision Tests. + + written = __llvm_libc::sprintf(buff, "%.1a", 1.0); + ASSERT_STREQ_LEN(written, buff, "0x1.0p+0"); + + written = __llvm_libc::sprintf(buff, "%.1a", 0.0); + ASSERT_STREQ_LEN(written, buff, "0x0.0p+0"); + + written = __llvm_libc::sprintf(buff, "%.1a", 0.1); + ASSERT_STREQ_LEN(written, buff, "0x1.ap-4"); + + written = __llvm_libc::sprintf(buff, "%.1a", 0x1.0fp0); + ASSERT_STREQ_LEN(written, buff, "0x1.1p+0"); + + written = __llvm_libc::sprintf(buff, "%.1a", 0x1.07p0); + ASSERT_STREQ_LEN(written, buff, "0x1.0p+0"); + + written = __llvm_libc::sprintf(buff, "%.1a", 0x1.08p0); + ASSERT_STREQ_LEN(written, buff, "0x1.0p+0"); + + written = __llvm_libc::sprintf(buff, "%.1a", 0x1.18p0); + ASSERT_STREQ_LEN(written, buff, "0x1.2p+0"); + + written = __llvm_libc::sprintf(buff, "%.1a", 0x1.ffp0); + ASSERT_STREQ_LEN(written, buff, "0x2.0p+0"); + + written = __llvm_libc::sprintf(buff, "%.5a", 1.25); + ASSERT_STREQ_LEN(written, buff, "0x1.40000p+0"); + + written = __llvm_libc::sprintf(buff, "%.0a", 1.25); + ASSERT_STREQ_LEN(written, buff, "0x1p+0"); + + written = __llvm_libc::sprintf(buff, "%.0a", 1.75); + ASSERT_STREQ_LEN(written, buff, "0x2p+0"); + + written = __llvm_libc::sprintf(buff, "%.1a", 0x1.0p-1023); + ASSERT_STREQ_LEN(written, buff, "0x0.8p-1022"); + + written = __llvm_libc::sprintf(buff, "%.1a", 0x1.8p-1023); + ASSERT_STREQ_LEN(written, buff, "0x0.cp-1022"); + + written = __llvm_libc::sprintf(buff, "%.1a", 0x1.0p-1024); + ASSERT_STREQ_LEN(written, buff, "0x0.4p-1022"); + + written = __llvm_libc::sprintf(buff, "%.0a", 0x1.0p-1023); + ASSERT_STREQ_LEN(written, buff, "0x0p-1022"); + + written = __llvm_libc::sprintf(buff, "%.0a", 0x1.8p-1023); + ASSERT_STREQ_LEN(written, buff, "0x1p-1022"); + + written = __llvm_libc::sprintf(buff, "%.0a", 0x1.0p-1024); + ASSERT_STREQ_LEN(written, buff, "0x0p-1022"); + + written = __llvm_libc::sprintf(buff, "%.2a", 0x1.0p-1027); + ASSERT_STREQ_LEN(written, buff, "0x0.08p-1022"); + + written = __llvm_libc::sprintf(buff, "%.1a", 0x1.0p-1027); + ASSERT_STREQ_LEN(written, buff, "0x0.0p-1022"); + + written = __llvm_libc::sprintf(buff, "%.5a", 0.0); + ASSERT_STREQ_LEN(written, buff, "0x0.00000p+0"); + + written = __llvm_libc::sprintf(buff, "%.5a", 0x1.008p0); + ASSERT_STREQ_LEN(written, buff, "0x1.00800p+0"); + + written = __llvm_libc::sprintf(buff, "%.5a", 0x1.008p10); + ASSERT_STREQ_LEN(written, buff, "0x1.00800p+10"); + + written = __llvm_libc::sprintf(buff, "%.5a", nan); + ASSERT_STREQ_LEN(written, buff, "nan"); + + written = __llvm_libc::sprintf(buff, "%.1La", 0.1L); +#if defined(SPECIAL_X86_LONG_DOUBLE) + ASSERT_STREQ_LEN(written, buff, "0xc.dp-7"); +#elif defined(LONG_DOUBLE_IS_DOUBLE) + ASSERT_STREQ_LEN(written, buff, "0x1.ap-4"); +#else // 128 bit long double + ASSERT_STREQ_LEN(written, buff, "0x1.ap-4"); +#endif + + written = __llvm_libc::sprintf(buff, "%.1La", 0xf.fffffffffffffffp16380L); +#if defined(SPECIAL_X86_LONG_DOUBLE) + ASSERT_STREQ_LEN(written, buff, "0x1.0p+16384"); +#elif defined(LONG_DOUBLE_IS_DOUBLE) + ASSERT_STREQ_LEN(written, buff, "inf"); +#else // 128 bit long double + ASSERT_STREQ_LEN(written, buff, "0x2.0p+16383"); +#endif + + // Flag Tests. + + written = __llvm_libc::sprintf(buff, "%+a", nan); + ASSERT_STREQ_LEN(written, buff, "+nan"); + + written = __llvm_libc::sprintf(buff, "% A", inf); + ASSERT_STREQ_LEN(written, buff, " INF"); + + written = __llvm_libc::sprintf(buff, "%-5a", inf); + ASSERT_STREQ_LEN(written, buff, "inf "); + + written = __llvm_libc::sprintf(buff, "%+-5A", nan); + ASSERT_STREQ_LEN(written, buff, "+NAN "); + + written = __llvm_libc::sprintf(buff, "%+a", 1.0); + ASSERT_STREQ_LEN(written, buff, "+0x1p+0"); + + written = __llvm_libc::sprintf(buff, "% a", 0.0); + ASSERT_STREQ_LEN(written, buff, " 0x0p+0"); + + written = __llvm_libc::sprintf(buff, "%-10a", 1.5); + ASSERT_STREQ_LEN(written, buff, "0x1.8p+0 "); + + written = __llvm_libc::sprintf(buff, "%#a", 1.0); + ASSERT_STREQ_LEN(written, buff, "0x1.p+0"); + + written = __llvm_libc::sprintf(buff, "%#.0a", 1.5); + ASSERT_STREQ_LEN(written, buff, "0x2.p+0"); + + written = __llvm_libc::sprintf(buff, "%010a", 1.5); + ASSERT_STREQ_LEN(written, buff, "0x001.8p+0"); + + written = __llvm_libc::sprintf(buff, "%+- #0a", 0.0); + ASSERT_STREQ_LEN(written, buff, "+0x0.p+0"); + + // Combined Tests. + + written = __llvm_libc::sprintf(buff, "%12.3a %-12.3A", 0.1, 256.0); + ASSERT_STREQ_LEN(written, buff, " 0x1.99ap-4 0X1.000P+8 "); + + written = __llvm_libc::sprintf(buff, "%+-#12.3a % 012.3a", 0.1256, 1256.0); + ASSERT_STREQ_LEN(written, buff, "+0x1.014p-3 0x1.3a0p+10"); +} +#endif // LLVM_LIBC_PRINTF_DISABLE_FLOAT + #ifndef LLVM_LIBC_PRINTF_DISABLE_WRITE_INT TEST(LlvmLibcSPrintfTest, WriteIntConv) { char buff[64];