//===-- 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/FEnvImpl.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/float_inf_nan_converter.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(); } if (is_inf_or_nan) return convert_inf_nan(writer, to_conv); 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 = ' '; // 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; switch (fputil::get_round()) { case FE_TONEAREST: // 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); break; case FE_DOWNWARD: if (truncated_bits > 0 && is_negative) ++mantissa; break; case FE_UPWARD: if (truncated_bits > 0 && !is_negative) ++mantissa; break; case FE_TOWARDZERO: break; } // 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 WRITE_OK; } } // namespace printf_core } // namespace __llvm_libc #endif // LLVM_LIBC_SRC_STDIO_PRINTF_CORE_FLOAT_HEX_CONVERTER_H