aboutsummaryrefslogtreecommitdiff
path: root/libc/src
diff options
context:
space:
mode:
Diffstat (limited to 'libc/src')
-rw-r--r--libc/src/stdio/printf_core/CMakeLists.txt4
-rw-r--r--libc/src/stdio/printf_core/converter.cpp8
-rw-r--r--libc/src/stdio/printf_core/converter_atlas.h5
-rw-r--r--libc/src/stdio/printf_core/converter_utils.h3
-rw-r--r--libc/src/stdio/printf_core/core_structs.h24
-rw-r--r--libc/src/stdio/printf_core/fixed_converter.h309
-rw-r--r--libc/src/stdio/printf_core/float_dec_converter.h3
-rw-r--r--libc/src/stdio/printf_core/parser.h65
-rw-r--r--libc/src/stdio/printf_core/printf_config.h7
9 files changed, 421 insertions, 7 deletions
diff --git a/libc/src/stdio/printf_core/CMakeLists.txt b/libc/src/stdio/printf_core/CMakeLists.txt
index 9b81a7a..02819ea 100644
--- a/libc/src/stdio/printf_core/CMakeLists.txt
+++ b/libc/src/stdio/printf_core/CMakeLists.txt
@@ -10,6 +10,9 @@ endif()
if(LIBC_CONF_PRINTF_FLOAT_TO_STR_USE_MEGA_LONG_DOUBLE_TABLE)
list(APPEND printf_config_copts "-DLIBC_COPT_FLOAT_TO_STR_USE_MEGA_LONG_DOUBLE_TABLE")
endif()
+if(LIBC_CONF_PRINTF_DISABLE_FIXED_POINT)
+ list(APPEND printf_config_copts "-DLIBC_COPT_PRINTF_DISABLE_FIXED_POINT")
+endif()
if(printf_config_copts)
list(PREPEND printf_config_copts "COMPILE_OPTIONS")
endif()
@@ -76,6 +79,7 @@ add_object_library(
float_inf_nan_converter.h
float_hex_converter.h
float_dec_converter.h
+ fixed_converter.h #TODO: Check if this should be disabled when fixed unavail
DEPENDS
.writer
.core_structs
diff --git a/libc/src/stdio/printf_core/converter.cpp b/libc/src/stdio/printf_core/converter.cpp
index 52412ae..613d693 100644
--- a/libc/src/stdio/printf_core/converter.cpp
+++ b/libc/src/stdio/printf_core/converter.cpp
@@ -9,6 +9,7 @@
#include "src/stdio/printf_core/converter.h"
#include "src/stdio/printf_core/core_structs.h"
+#include "src/stdio/printf_core/printf_config.h"
#include "src/stdio/printf_core/writer.h"
// This option allows for replacing all of the conversion functions with custom
@@ -75,6 +76,13 @@ int convert(Writer *writer, const FormatSection &to_conv) {
case 'G':
return convert_float_dec_auto(writer, to_conv);
#endif // LIBC_COPT_PRINTF_DISABLE_FLOAT
+#ifdef LIBC_INTERNAL_PRINTF_HAS_FIXED_POINT
+ case 'r':
+ case 'R':
+ case 'k':
+ case 'K':
+ return convert_fixed(writer, to_conv);
+#endif // LIBC_INTERNAL_PRINTF_HAS_FIXED_POINT
#ifndef LIBC_COPT_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
index 6471f3f..2189ed1 100644
--- a/libc/src/stdio/printf_core/converter_atlas.h
+++ b/libc/src/stdio/printf_core/converter_atlas.h
@@ -31,6 +31,11 @@
#include "src/stdio/printf_core/float_hex_converter.h"
#endif // LIBC_COPT_PRINTF_DISABLE_FLOAT
+#ifdef LIBC_INTERNAL_PRINTF_HAS_FIXED_POINT
+// defines convert_fixed
+#include "src/stdio/printf_core/fixed_converter.h"
+#endif // LIBC_INTERNAL_PRINTF_HAS_FIXED_POINT
+
#ifndef LIBC_COPT_PRINTF_DISABLE_WRITE_INT
#include "src/stdio/printf_core/write_int_converter.h"
#endif // LIBC_COPT_PRINTF_DISABLE_WRITE_INT
diff --git a/libc/src/stdio/printf_core/converter_utils.h b/libc/src/stdio/printf_core/converter_utils.h
index 54f0a87..948fe81 100644
--- a/libc/src/stdio/printf_core/converter_utils.h
+++ b/libc/src/stdio/printf_core/converter_utils.h
@@ -51,6 +51,9 @@ LIBC_INLINE uintmax_t apply_length_modifier(uintmax_t num, LengthModifier lm) {
return result; \
}
+// This is used to represent which direction the number should be rounded.
+enum class RoundDirection { Up, Down, Even };
+
} // namespace printf_core
} // namespace LIBC_NAMESPACE
diff --git a/libc/src/stdio/printf_core/core_structs.h b/libc/src/stdio/printf_core/core_structs.h
index 7634d45..d3718b4 100644
--- a/libc/src/stdio/printf_core/core_structs.h
+++ b/libc/src/stdio/printf_core/core_structs.h
@@ -10,7 +10,9 @@
#define LLVM_LIBC_SRC_STDIO_PRINTF_CORE_CORE_STRUCTS_H
#include "src/__support/CPP/string_view.h"
+#include "src/__support/CPP/type_traits.h"
#include "src/__support/FPUtil/FPBits.h"
+#include "src/stdio/printf_core/printf_config.h"
#include <inttypes.h>
#include <stddef.h>
@@ -77,7 +79,13 @@ struct FormatSection {
}
};
-enum PrimaryType : uint8_t { Unknown = 0, Float = 1, Pointer = 2, Integer = 3 };
+enum PrimaryType : uint8_t {
+ Unknown = 0,
+ Float = 1,
+ Pointer = 2,
+ Integer = 3,
+ FixedPoint = 4,
+};
// TypeDesc stores the information about a type that is relevant to printf in
// a relatively compact manner.
@@ -95,9 +103,16 @@ template <typename T> LIBC_INLINE constexpr TypeDesc type_desc_from_type() {
} else {
constexpr bool IS_POINTER = cpp::is_pointer_v<T>;
constexpr bool IS_FLOAT = cpp::is_floating_point_v<T>;
- return TypeDesc{sizeof(T), IS_POINTER ? PrimaryType::Pointer
- : IS_FLOAT ? PrimaryType::Float
- : PrimaryType::Integer};
+#ifdef LIBC_INTERNAL_PRINTF_HAS_FIXED_POINT
+ constexpr bool IS_FIXED_POINT = cpp::is_fixed_point_v<T>;
+#else
+ constexpr bool IS_FIXED_POINT = false;
+#endif // LIBC_INTERNAL_PRINTF_HAS_FIXED_POINT
+
+ return TypeDesc{sizeof(T), IS_POINTER ? PrimaryType::Pointer
+ : IS_FLOAT ? PrimaryType::Float
+ : IS_FIXED_POINT ? PrimaryType::FixedPoint
+ : PrimaryType::Integer};
}
}
@@ -109,6 +124,7 @@ constexpr int FILE_WRITE_ERROR = -1;
constexpr int FILE_STATUS_ERROR = -2;
constexpr int NULLPTR_WRITE_ERROR = -3;
constexpr int INT_CONVERSION_ERROR = -4;
+constexpr int FIXED_POINT_CONVERSION_ERROR = -5;
} // namespace printf_core
} // namespace LIBC_NAMESPACE
diff --git a/libc/src/stdio/printf_core/fixed_converter.h b/libc/src/stdio/printf_core/fixed_converter.h
new file mode 100644
index 0000000..de69c60
--- /dev/null
+++ b/libc/src/stdio/printf_core/fixed_converter.h
@@ -0,0 +1,309 @@
+//===-- Fixed Point 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_FIXED_CONVERTER_H
+#define LLVM_LIBC_SRC_STDIO_PRINTF_CORE_FIXED_CONVERTER_H
+
+#include "include/llvm-libc-macros/stdfix-macros.h"
+#include "src/__support/CPP/string_view.h"
+#include "src/__support/fixed_point/fx_bits.h"
+#include "src/__support/fixed_point/fx_rep.h"
+#include "src/__support/integer_to_string.h"
+#include "src/__support/libc_assert.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 <inttypes.h>
+#include <stddef.h>
+
+namespace LIBC_NAMESPACE {
+namespace printf_core {
+
+// This is just for assertions. It will be compiled out for release builds.
+LIBC_INLINE constexpr uint32_t const_ten_exp(uint32_t exponent) {
+ uint32_t result = 1;
+ LIBC_ASSERT(exponent < 11);
+ for (uint32_t i = 0; i < exponent; ++i)
+ result *= 10;
+
+ return result;
+}
+
+#define READ_FX_BITS(TYPE) \
+ do { \
+ auto fixed_bits = fixed_point::FXBits<TYPE>( \
+ fixed_point::FXRep<TYPE>::StorageType(to_conv.conv_val_raw)); \
+ integral = fixed_bits.get_integral(); \
+ fractional = fixed_bits.get_fraction(); \
+ exponent = fixed_bits.get_exponent(); \
+ is_negative = fixed_bits.get_sign(); \
+ } while (false)
+
+#define APPLY_FX_LENGTH_MODIFIER(LENGTH_MODIFIER) \
+ do { \
+ if (to_conv.conv_name == 'r') { \
+ READ_FX_BITS(LENGTH_MODIFIER fract); \
+ } else if (to_conv.conv_name == 'R') { \
+ READ_FX_BITS(unsigned LENGTH_MODIFIER fract); \
+ } else if (to_conv.conv_name == 'k') { \
+ READ_FX_BITS(LENGTH_MODIFIER accum); \
+ } else if (to_conv.conv_name == 'K') { \
+ READ_FX_BITS(unsigned LENGTH_MODIFIER accum); \
+ } else { \
+ LIBC_ASSERT(false && "Invalid conversion name passed to convert_fixed"); \
+ return FIXED_POINT_CONVERSION_ERROR; \
+ } \
+ } while (false)
+
+LIBC_INLINE int convert_fixed(Writer *writer, const FormatSection &to_conv) {
+ // Long accum should be the largest type, so we can store all the smaller
+ // numbers in things sized for it.
+ using LARep = fixed_point::FXRep<unsigned long accum>;
+ using StorageType = LARep::StorageType;
+
+ // All of the letters will be defined relative to variable a, which will be
+ // the appropriate case based on the name of the conversion. This converts any
+ // conversion name into the letter 'a' with the appropriate case.
+ const char a = (to_conv.conv_name & 32) | 'A';
+ FormatFlags flags = to_conv.flags;
+
+ bool is_negative;
+ int exponent;
+ StorageType integral;
+ StorageType fractional;
+
+ // r = fract
+ // k = accum
+ // lowercase = signed
+ // uppercase = unsigned
+ // h = short
+ // l = long
+ // any other length modifier has no effect
+
+ if (to_conv.length_modifier == LengthModifier::h) {
+ APPLY_FX_LENGTH_MODIFIER(short);
+ } else if (to_conv.length_modifier == LengthModifier::l) {
+ APPLY_FX_LENGTH_MODIFIER(long);
+ } else {
+ APPLY_FX_LENGTH_MODIFIER();
+ }
+
+ LIBC_ASSERT(static_cast<size_t>(exponent) <=
+ (sizeof(StorageType) - sizeof(uint32_t)) * CHAR_BIT &&
+ "StorageType must be large enough to hold the fractional "
+ "component multiplied by a 32 bit number.");
+
+ // If to_conv doesn't specify a precision, the precision defaults to 6.
+ const size_t precision = to_conv.precision < 0 ? 6 : to_conv.precision;
+ bool has_decimal_point =
+ (precision > 0) || ((flags & FormatFlags::ALTERNATE_FORM) != 0);
+
+ // The number of non-zero digits below the decimal point for a negative power
+ // of 2 in base 10 is equal to the magnitude of the power of 2.
+
+ // A quick proof:
+ // Let p be any positive integer.
+ // Let e = 2^(-p)
+ // Let t be a positive integer such that e * 10^t is an integer.
+ // By definition: The smallest allowed value of t must be equal to the number
+ // of non-zero digits below the decimal point in e.
+ // If we evaluate e * 10^t we get the following:
+ // e * 10^t = 2^(-p) * 10*t = 2^(-p) * 2^t * 5^t = 5^t * 2^(t-p)
+ // For 5^t * 2^(t-p) to be an integer, both exponents must be non-negative,
+ // since 5 and 2 are coprime.
+ // The smallest value of t such that t-p is non-negative is p.
+ // Therefor, the number of non-zero digits below the decimal point for a given
+ // negative power of 2 "p" is equal to the value of p.
+
+ constexpr size_t MAX_FRACTION_DIGITS = LARep::FRACTION_LEN;
+
+ char fraction_digits[MAX_FRACTION_DIGITS];
+
+ size_t valid_fraction_digits = 0;
+
+ // TODO: Factor this part out
+ while (fractional > 0) {
+ uint32_t cur_digits = 0;
+ // 10^9 is used since it's the largest power of 10 that fits in a uint32_t
+ constexpr uint32_t TEN_EXP_NINE = 1000000000;
+ constexpr size_t DIGITS_PER_BLOCK = 9;
+
+ // Multiply by 10^9, then grab the digits above the decimal point, then
+ // clear those digits in fractional.
+ fractional = fractional * TEN_EXP_NINE;
+ cur_digits = static_cast<uint32_t>(fractional >> exponent);
+ fractional = fractional % (StorageType(1) << exponent);
+
+ // we add TEN_EXP_NINE to force leading zeroes to show up, then we skip the
+ // first digit in the loop.
+ const IntegerToString<uint32_t> cur_fractional_digits(cur_digits +
+ TEN_EXP_NINE);
+ for (size_t i = 0;
+ i < DIGITS_PER_BLOCK && valid_fraction_digits < MAX_FRACTION_DIGITS;
+ ++i, ++valid_fraction_digits)
+ fraction_digits[valid_fraction_digits] =
+ cur_fractional_digits.view()[i + 1];
+
+ if (valid_fraction_digits >= MAX_FRACTION_DIGITS) {
+ LIBC_ASSERT(fractional == 0 && "If the fraction digit buffer is full, "
+ "there should be no remaining digits.");
+ /*
+ A visual explanation of what this assert is checking:
+
+ 32 digits (max for 32 bit fract)
+ +------------------------------++--+--- must be zero
+ | || |
+ 123456789012345678901234567890120000
+ | || || || |
+ +-------++-------++-------++-------+
+ 9 digit blocks
+ */
+ LIBC_ASSERT(cur_digits % const_ten_exp(
+ DIGITS_PER_BLOCK -
+ (MAX_FRACTION_DIGITS % DIGITS_PER_BLOCK)) ==
+ 0 &&
+ "Digits after the MAX_FRACTION_DIGITS should all be zero.");
+ valid_fraction_digits = MAX_FRACTION_DIGITS;
+ }
+ }
+
+ if (precision < valid_fraction_digits) {
+ // Handle rounding. Just do round to nearest, tie to even since it's
+ // unspecified.
+ RoundDirection round;
+ char first_digit_after = fraction_digits[precision];
+ if (first_digit_after > '5') {
+ round = RoundDirection::Up;
+ } else if (first_digit_after < '5') {
+ round = RoundDirection::Down;
+ } else {
+ // first_digit_after == '5'
+ // need to check the remaining digits, but default to even.
+ round = RoundDirection::Even;
+ for (size_t cur_digit_index = precision + 1;
+ cur_digit_index + 1 < valid_fraction_digits; ++cur_digit_index) {
+ if (fraction_digits[cur_digit_index] != '0') {
+ round = RoundDirection::Up;
+ break;
+ }
+ }
+ }
+
+ // If we need to actually perform rounding, do so.
+ if (round == RoundDirection::Up || round == RoundDirection::Even) {
+ bool keep_rounding = true;
+ int digit_to_round = static_cast<int>(precision) - 1;
+ for (; digit_to_round >= 0 && keep_rounding; --digit_to_round) {
+ keep_rounding = false;
+ char cur_digit = fraction_digits[digit_to_round];
+ // if the digit should not be rounded up
+ if (round == RoundDirection::Even && ((cur_digit - '0') % 2) == 0) {
+ // break out of the loop
+ break;
+ }
+ fraction_digits[digit_to_round] += 1;
+
+ // if the digit was a 9, instead replace with a 0.
+ if (cur_digit == '9') {
+ fraction_digits[digit_to_round] = '0';
+ keep_rounding = true;
+ }
+ }
+
+ // if every digit below the decimal point was rounded up but we need to
+ // keep rounding
+ if (keep_rounding &&
+ (round == RoundDirection::Up ||
+ (round == RoundDirection::Even && ((integral % 2) == 1)))) {
+ // add one to the integral portion to round it up.
+ ++integral;
+ }
+ }
+
+ valid_fraction_digits = precision;
+ }
+
+ const IntegerToString<StorageType> integral_str(integral);
+
+ // these are signed to prevent underflow due to negative values. The
+ // eventual values will always be non-negative.
+ size_t trailing_zeroes = 0;
+ int padding;
+
+ // If the precision is greater than the actual result, pad with 0s
+ if (precision > valid_fraction_digits)
+ trailing_zeroes = precision - (valid_fraction_digits);
+
+ constexpr cpp::string_view DECIMAL_POINT(".");
+
+ char sign_char = 0;
+
+ // Check if the conv name is uppercase
+ if (a == 'A') {
+ // These flags are only for signed conversions, so this removes them if the
+ // conversion is unsigned.
+ flags = FormatFlags(flags &
+ ~(FormatFlags::FORCE_SIGN | FormatFlags::SPACE_PREFIX));
+ }
+
+ if (is_negative)
+ sign_char = '-';
+ else if ((flags & FormatFlags::FORCE_SIGN) == FormatFlags::FORCE_SIGN)
+ sign_char = '+'; // FORCE_SIGN has precedence over SPACE_PREFIX
+ else if ((flags & FormatFlags::SPACE_PREFIX) == FormatFlags::SPACE_PREFIX)
+ sign_char = ' ';
+
+ padding = static_cast<int>(to_conv.min_width - (sign_char > 0 ? 1 : 0) -
+ integral_str.size() -
+ static_cast<int>(has_decimal_point) -
+ valid_fraction_digits - trailing_zeroes);
+ if (padding < 0)
+ padding = 0;
+
+ if ((flags & FormatFlags::LEFT_JUSTIFIED) == FormatFlags::LEFT_JUSTIFIED) {
+ // The pattern is (sign), integral, (.), (fraction), (zeroes), (spaces)
+ if (sign_char > 0)
+ RET_IF_RESULT_NEGATIVE(writer->write(sign_char));
+ RET_IF_RESULT_NEGATIVE(writer->write(integral_str.view()));
+ if (has_decimal_point)
+ RET_IF_RESULT_NEGATIVE(writer->write(DECIMAL_POINT));
+ if (valid_fraction_digits > 0)
+ RET_IF_RESULT_NEGATIVE(
+ writer->write({fraction_digits, valid_fraction_digits}));
+ if (trailing_zeroes > 0)
+ RET_IF_RESULT_NEGATIVE(writer->write('0', trailing_zeroes));
+ if (padding > 0)
+ RET_IF_RESULT_NEGATIVE(writer->write(' ', padding));
+ } else {
+ // The pattern is (spaces), (sign), (zeroes), integral, (.), (fraction),
+ // (zeroes)
+ if ((padding > 0) &&
+ ((flags & FormatFlags::LEADING_ZEROES) != FormatFlags::LEADING_ZEROES))
+ RET_IF_RESULT_NEGATIVE(writer->write(' ', padding));
+ if (sign_char > 0)
+ RET_IF_RESULT_NEGATIVE(writer->write(sign_char));
+ if ((padding > 0) &&
+ ((flags & FormatFlags::LEADING_ZEROES) == FormatFlags::LEADING_ZEROES))
+ RET_IF_RESULT_NEGATIVE(writer->write('0', padding));
+ RET_IF_RESULT_NEGATIVE(writer->write(integral_str.view()));
+ if (has_decimal_point)
+ RET_IF_RESULT_NEGATIVE(writer->write(DECIMAL_POINT));
+ if (valid_fraction_digits > 0)
+ RET_IF_RESULT_NEGATIVE(
+ writer->write({fraction_digits, valid_fraction_digits}));
+ if (trailing_zeroes > 0)
+ RET_IF_RESULT_NEGATIVE(writer->write('0', trailing_zeroes));
+ }
+ return WRITE_OK;
+}
+
+} // namespace printf_core
+} // namespace LIBC_NAMESPACE
+
+#endif // LLVM_LIBC_SRC_STDIO_PRINTF_CORE_FIXED_CONVERTER_H
diff --git a/libc/src/stdio/printf_core/float_dec_converter.h b/libc/src/stdio/printf_core/float_dec_converter.h
index b54526d..a6c6832 100644
--- a/libc/src/stdio/printf_core/float_dec_converter.h
+++ b/libc/src/stdio/printf_core/float_dec_converter.h
@@ -45,9 +45,6 @@ constexpr uint32_t MAX_BLOCK = 999999999;
// constexpr uint32_t MAX_BLOCK = 999999999999999999;
constexpr char DECIMAL_POINT = '.';
-// This is used to represent which direction the number should be rounded.
-enum class RoundDirection { Up, Down, Even };
-
LIBC_INLINE RoundDirection get_round_direction(int last_digit, bool truncated,
fputil::Sign sign) {
switch (fputil::quick_get_round()) {
diff --git a/libc/src/stdio/printf_core/parser.h b/libc/src/stdio/printf_core/parser.h
index 1e7d2e58..0876116 100644
--- a/libc/src/stdio/printf_core/parser.h
+++ b/libc/src/stdio/printf_core/parser.h
@@ -9,13 +9,19 @@
#ifndef LLVM_LIBC_SRC_STDIO_PRINTF_CORE_PARSER_H
#define LLVM_LIBC_SRC_STDIO_PRINTF_CORE_PARSER_H
+#include "include/llvm-libc-macros/stdfix-macros.h"
#include "src/__support/CPP/optional.h"
+#include "src/__support/CPP/type_traits.h"
#include "src/__support/str_to_integer.h"
#include "src/stdio/printf_core/core_structs.h"
#include "src/stdio/printf_core/printf_config.h"
#include <stddef.h>
+#ifdef LIBC_INTERNAL_PRINTF_HAS_FIXED_POINT
+#include "src/__support/fixed_point/fx_rep.h"
+#endif // LIBC_INTERNAL_PRINTF_HAS_FIXED_POINT
+
namespace LIBC_NAMESPACE {
namespace printf_core {
@@ -28,6 +34,14 @@ template <> struct int_type_of<double> {
template <> struct int_type_of<long double> {
using type = fputil::FPBits<long double>::StorageType;
};
+
+#ifdef LIBC_INTERNAL_PRINTF_HAS_FIXED_POINT
+template <typename T>
+struct int_type_of<cpp::enable_if<cpp::is_fixed_point_v<T>, T>> {
+ using type = typename fixed_point::FXRep<T>::StorageType;
+};
+#endif // LIBC_INTERNAL_PRINTF_HAS_FIXED_POINT
+
template <typename T> using int_type_of_v = typename int_type_of<T>::type;
#ifndef LIBC_COPT_PRINTF_DISABLE_INDEX_MODE
@@ -206,6 +220,25 @@ public:
}
break;
#endif // LIBC_COPT_PRINTF_DISABLE_FLOAT
+#ifdef LIBC_INTERNAL_PRINTF_HAS_FIXED_POINT
+ // Capitalization represents sign, but we only need to get the right
+ // bitwidth here so we ignore that.
+ case ('r'):
+ case ('R'):
+ // all fract sizes we support are less than 32 bits, and currently doing
+ // va_args with fixed point types just doesn't work.
+ // TODO: Move to fixed point types once va_args supports it.
+ WRITE_ARG_VAL_SIMPLEST(section.conv_val_raw, uint32_t, conv_index);
+ break;
+ case ('k'):
+ case ('K'):
+ if (lm == LengthModifier::l) {
+ WRITE_ARG_VAL_SIMPLEST(section.conv_val_raw, uint64_t, conv_index);
+ } else {
+ WRITE_ARG_VAL_SIMPLEST(section.conv_val_raw, uint32_t, conv_index);
+ }
+ break;
+#endif // LIBC_INTERNAL_PRINTF_HAS_FIXED_POINT
#ifndef LIBC_COPT_PRINTF_DISABLE_WRITE_INT
case ('n'):
#endif // LIBC_COPT_PRINTF_DISABLE_WRITE_INT
@@ -399,6 +432,22 @@ private:
else if (cur_type_desc == type_desc_from_type<long double>())
args_cur.template next_var<long double>();
#endif // LIBC_COPT_PRINTF_DISABLE_FLOAT
+#ifdef LIBC_INTERNAL_PRINTF_HAS_FIXED_POINT
+ // Floating point numbers may be stored separately from the other
+ // arguments.
+ else if (cur_type_desc == type_desc_from_type<short fract>())
+ args_cur.template next_var<short fract>();
+ else if (cur_type_desc == type_desc_from_type<fract>())
+ args_cur.template next_var<fract>();
+ else if (cur_type_desc == type_desc_from_type<long fract>())
+ args_cur.template next_var<long fract>();
+ else if (cur_type_desc == type_desc_from_type<short accum>())
+ args_cur.template next_var<short accum>();
+ else if (cur_type_desc == type_desc_from_type<accum>())
+ args_cur.template next_var<accum>();
+ else if (cur_type_desc == type_desc_from_type<long accum>())
+ args_cur.template next_var<long accum>();
+#endif // LIBC_INTERNAL_PRINTF_HAS_FIXED_POINT
// pointers may be stored separately from normal values.
else if (cur_type_desc == type_desc_from_type<void *>())
args_cur.template next_var<void *>();
@@ -528,6 +577,22 @@ private:
conv_size = type_desc_from_type<long double>();
break;
#endif // LIBC_COPT_PRINTF_DISABLE_FLOAT
+#ifdef LIBC_INTERNAL_PRINTF_HAS_FIXED_POINT
+ // Capitalization represents sign, but we only need to get the right
+ // bitwidth here so we ignore that.
+ case ('r'):
+ case ('R'):
+ conv_size = type_desc_from_type<uint32_t>();
+ break;
+ case ('k'):
+ case ('K'):
+ if (lm == LengthModifier::l) {
+ conv_size = type_desc_from_type<uint64_t>();
+ } else {
+ conv_size = type_desc_from_type<uint32_t>();
+ }
+ break;
+#endif // LIBC_INTERNAL_PRINTF_HAS_FIXED_POINT
#ifndef LIBC_COPT_PRINTF_DISABLE_WRITE_INT
case ('n'):
#endif // LIBC_COPT_PRINTF_DISABLE_WRITE_INT
diff --git a/libc/src/stdio/printf_core/printf_config.h b/libc/src/stdio/printf_core/printf_config.h
index e1d9654..8a48abd 100644
--- a/libc/src/stdio/printf_core/printf_config.h
+++ b/libc/src/stdio/printf_core/printf_config.h
@@ -29,6 +29,13 @@
#define LIBC_COPT_PRINTF_INDEX_ARR_LEN 128
#endif
+// If fixed point is available and the user hasn't explicitly opted out, then
+// enable fixed point.
+#if defined(LIBC_COMPILER_HAS_FIXED_POINT) && \
+ !defined(LIBC_COPT_PRINTF_DISABLE_FIXED_POINT)
+#define LIBC_INTERNAL_PRINTF_HAS_FIXED_POINT
+#endif
+
// TODO(michaelrj): Provide a proper interface for these options.
// LIBC_COPT_FLOAT_TO_STR_USE_MEGA_LONG_DOUBLE_TABLE
// LIBC_COPT_FLOAT_TO_STR_USE_DYADIC_FLOAT