[libc] Add fixed point support to printf (#82707)
This patch adds the r, R, k, and K conversion specifiers to printf, with accompanying tests. They are guarded behind the LIBC_COPT_PRINTF_DISABLE_FIXED_POINT flag as well as automatic fixed point support detection.
This commit is contained in:
@@ -15,6 +15,10 @@
|
||||
"LIBC_CONF_PRINTF_FLOAT_TO_STR_USE_MEGA_LONG_DOUBLE_TABLE": {
|
||||
"value": false,
|
||||
"doc": "Use large table for better printf long double performance."
|
||||
},
|
||||
"LIBC_CONF_PRINTF_DISABLE_FIXED_POINT": {
|
||||
"value": false,
|
||||
"doc": "Disable printing fixed point values in printf and friends."
|
||||
}
|
||||
},
|
||||
"string": {
|
||||
|
||||
@@ -26,6 +26,7 @@ overrides in ``config/<platform>/config.json`` and ``config/<platform>/<arch>/co
|
||||
to learn about the defaults for your platform and target.
|
||||
|
||||
* **"printf" options**
|
||||
- ``LIBC_CONF_PRINTF_DISABLE_FIXED_POINT``: Disable printing fixed point values in printf and friends.
|
||||
- ``LIBC_CONF_PRINTF_DISABLE_FLOAT``: Disable printing floating point values in printf and friends.
|
||||
- ``LIBC_CONF_PRINTF_DISABLE_INDEX_MODE``: Disable index mode in the printf format string.
|
||||
- ``LIBC_CONF_PRINTF_DISABLE_WRITE_INT``: Disable handling of %n in printf format string.
|
||||
|
||||
@@ -62,6 +62,13 @@ When set, this flag disables support for floating point numbers and all their
|
||||
conversions (%a, %f, %e, %g); any floating point number conversion will be
|
||||
treated as invalid. This reduces code size.
|
||||
|
||||
LIBC_COPT_PRINTF_DISABLE_FIXED_POINT
|
||||
------------------------------------
|
||||
When set, this flag disables support for fixed point numbers and all their
|
||||
conversions (%r, %k); any fixed point number conversion will be treated as
|
||||
invalid. This reduces code size. This has no effect if the current compiler does
|
||||
not support fixed point numbers.
|
||||
|
||||
LIBC_COPT_PRINTF_NO_NULLPTR_CHECKS
|
||||
----------------------------------
|
||||
When set, this flag disables the nullptr checks in %n and %s.
|
||||
@@ -191,3 +198,8 @@ original conversion.
|
||||
The %p conversion will display a null pointer as if it was the string
|
||||
"(nullptr)" passed to a "%s" conversion, with all other options remaining the
|
||||
same as the original conversion.
|
||||
|
||||
The %r, %R, %k, and %K fixed point number format specifiers are accepted as
|
||||
defined in ISO/IEC TR 18037 (the fixed point number extension). These are
|
||||
available when the compiler is detected as having support for fixed point
|
||||
numbers and the LIBC_COPT_PRINTF_DISABLE_FIXED_POINT flag is not set.
|
||||
|
||||
@@ -15,3 +15,17 @@ add_libc_fuzzer(
|
||||
libc.src.stdio.snprintf
|
||||
libc.src.__support.FPUtil.fp_bits
|
||||
)
|
||||
|
||||
if(LIBC_COMPILER_HAS_FIXED_POINT)
|
||||
add_libc_fuzzer(
|
||||
printf_fixed_conv_fuzz
|
||||
NEED_MPFR
|
||||
SRCS
|
||||
printf_fixed_conv_fuzz.cpp
|
||||
DEPENDS
|
||||
libc.src.stdio.snprintf
|
||||
libc.src.__support.fixed_point.fx_bits
|
||||
COMPILE_OPTIONS
|
||||
-ffixed-point # TODO: add -ffixed-point to fuzz tests automatically
|
||||
)
|
||||
endif()
|
||||
|
||||
133
libc/fuzzing/stdio/printf_fixed_conv_fuzz.cpp
Normal file
133
libc/fuzzing/stdio/printf_fixed_conv_fuzz.cpp
Normal file
@@ -0,0 +1,133 @@
|
||||
//===-- printf_fixed_conv_fuzz.cpp ----------------------------------------===//
|
||||
//
|
||||
// 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
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
///
|
||||
/// Fuzzing test for llvm-libc printf %f/e/g/a implementations.
|
||||
///
|
||||
//===----------------------------------------------------------------------===//
|
||||
#include "src/stdio/snprintf.h"
|
||||
|
||||
#include "include/llvm-libc-macros/stdfix-macros.h"
|
||||
#include "src/__support/fixed_point/fx_bits.h"
|
||||
#include "src/__support/fixed_point/fx_rep.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "utils/MPFRWrapper/mpfr_inc.h"
|
||||
|
||||
constexpr int MAX_SIZE = 10000;
|
||||
|
||||
inline bool simple_streq(char *first, char *second, int length) {
|
||||
for (int i = 0; i < length; ++i)
|
||||
if (first[i] != second[i])
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
inline int clamp(int num, int max) {
|
||||
if (num > max)
|
||||
return max;
|
||||
if (num < -max)
|
||||
return -max;
|
||||
return num;
|
||||
}
|
||||
|
||||
enum class TestResult {
|
||||
Success,
|
||||
BufferSizeFailed,
|
||||
LengthsDiffer,
|
||||
StringsNotEqual,
|
||||
};
|
||||
|
||||
template <typename F>
|
||||
inline TestResult test_vals(const char *fmt, uint64_t num, int prec,
|
||||
int width) {
|
||||
typename LIBC_NAMESPACE::fixed_point::FXRep<F>::StorageType raw_num = num;
|
||||
|
||||
auto raw_num_bits = LIBC_NAMESPACE::fixed_point::FXBits<F>(raw_num);
|
||||
|
||||
// This needs to be a float with enough bits of precision to hold the fixed
|
||||
// point number.
|
||||
static_assert(sizeof(long double) > sizeof(long accum));
|
||||
|
||||
// build a long double that is equivalent to the fixed point number.
|
||||
long double ld_num =
|
||||
static_cast<long double>(raw_num_bits.get_integral()) +
|
||||
(static_cast<long double>(raw_num_bits.get_fraction()) /
|
||||
static_cast<long double>(1ll << raw_num_bits.get_exponent()));
|
||||
|
||||
if (raw_num_bits.get_sign())
|
||||
ld_num = -ld_num;
|
||||
|
||||
// Call snprintf on a nullptr to get the buffer size.
|
||||
int buffer_size = LIBC_NAMESPACE::snprintf(nullptr, 0, fmt, width, prec, num);
|
||||
|
||||
if (buffer_size < 0)
|
||||
return TestResult::BufferSizeFailed;
|
||||
|
||||
char *test_buff = new char[buffer_size + 1];
|
||||
char *reference_buff = new char[buffer_size + 1];
|
||||
|
||||
int test_result = 0;
|
||||
int reference_result = 0;
|
||||
|
||||
test_result = LIBC_NAMESPACE::snprintf(test_buff, buffer_size + 1, fmt, width,
|
||||
prec, num);
|
||||
|
||||
// The fixed point format is defined to be %f equivalent.
|
||||
reference_result = mpfr_snprintf(reference_buff, buffer_size + 1, "%*.*Lf",
|
||||
width, prec, ld_num);
|
||||
|
||||
// All of these calls should return that they wrote the same amount.
|
||||
if (test_result != reference_result || test_result != buffer_size)
|
||||
return TestResult::LengthsDiffer;
|
||||
|
||||
if (!simple_streq(test_buff, reference_buff, buffer_size))
|
||||
return TestResult::StringsNotEqual;
|
||||
|
||||
delete[] test_buff;
|
||||
delete[] reference_buff;
|
||||
return TestResult::Success;
|
||||
}
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
|
||||
// const uint8_t raw_data[] = {0x8d,0x43,0x40,0x0,0x0,0x0,};
|
||||
// data = raw_data;
|
||||
// size = sizeof(raw_data);
|
||||
int prec = 0;
|
||||
int width = 0;
|
||||
|
||||
LIBC_NAMESPACE::fixed_point::FXRep<long accum>::StorageType raw_num = 0;
|
||||
|
||||
// Copy as many bytes of data as will fit into num, prec, and with. Any extras
|
||||
// are ignored.
|
||||
for (size_t cur = 0; cur < size; ++cur) {
|
||||
if (cur < sizeof(raw_num)) {
|
||||
raw_num = (raw_num << 8) + data[cur];
|
||||
} else if (cur < sizeof(raw_num) + sizeof(prec)) {
|
||||
prec = (prec << 8) + data[cur];
|
||||
} else if (cur < sizeof(raw_num) + sizeof(prec) + sizeof(width)) {
|
||||
width = (width << 8) + data[cur];
|
||||
}
|
||||
}
|
||||
|
||||
width = clamp(width, MAX_SIZE);
|
||||
prec = clamp(prec, MAX_SIZE);
|
||||
|
||||
TestResult result;
|
||||
result = test_vals<long accum>("%*.*lk", raw_num, prec, width);
|
||||
if (result != TestResult::Success)
|
||||
__builtin_trap();
|
||||
|
||||
result = test_vals<unsigned long accum>("%*.*lK", raw_num, prec, width);
|
||||
if (result != TestResult::Success)
|
||||
__builtin_trap();
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
309
libc/src/stdio/printf_core/fixed_converter.h
Normal file
309
libc/src/stdio/printf_core/fixed_converter.h
Normal file
@@ -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
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -121,6 +121,9 @@ endif()
|
||||
if(LIBC_CONF_PRINTF_DISABLE_WRITE_INT)
|
||||
list(APPEND sprintf_test_copts "-DLIBC_COPT_PRINTF_DISABLE_WRITE_INT")
|
||||
endif()
|
||||
if(LIBC_CONF_PRINTF_DISABLE_FIXED_POINT)
|
||||
list(APPEND sprintf_test_copts "-DLIBC_COPT_PRINTF_DISABLE_FIXED_POINT")
|
||||
endif()
|
||||
|
||||
add_fp_unittest(
|
||||
sprintf_test
|
||||
|
||||
@@ -3201,6 +3201,217 @@ TEST_F(LlvmLibcSPrintfTest, FloatAutoLongDoubleConv) {
|
||||
|
||||
#endif // LIBC_COPT_PRINTF_DISABLE_FLOAT
|
||||
|
||||
#if defined(LIBC_COMPILER_HAS_FIXED_POINT) && \
|
||||
!defined(LIBC_COPT_PRINTF_DISABLE_FIXED_POINT)
|
||||
TEST_F(LlvmLibcSPrintfTest, FixedConv) {
|
||||
|
||||
// These numeric tests are potentially a little weak, but the fuzz test is
|
||||
// more thorough than my handwritten tests tend to be.
|
||||
|
||||
// TODO: Replace hex literals with their appropriate fixed point literals.
|
||||
|
||||
written = LIBC_NAMESPACE::sprintf(buff, "%k", 0x0); // 0.0
|
||||
ASSERT_STREQ_LEN(written, buff, "0.000000");
|
||||
|
||||
written = LIBC_NAMESPACE::sprintf(buff, "%k", 0x80000000); // -0.0
|
||||
ASSERT_STREQ_LEN(written, buff, "-0.000000");
|
||||
|
||||
written = LIBC_NAMESPACE::sprintf(buff, "%r", 0xffff); // -fract max
|
||||
ASSERT_STREQ_LEN(written, buff, "-0.999969");
|
||||
|
||||
written = LIBC_NAMESPACE::sprintf(buff, "%R", 0xffff); // unsigned fract max
|
||||
ASSERT_STREQ_LEN(written, buff, "0.999985");
|
||||
|
||||
written = LIBC_NAMESPACE::sprintf(buff, "%k", 0xffffffff); // -accum max
|
||||
ASSERT_STREQ_LEN(written, buff, "-65535.999969");
|
||||
|
||||
written =
|
||||
LIBC_NAMESPACE::sprintf(buff, "%K", 0xffffffff); // unsigned accum max
|
||||
ASSERT_STREQ_LEN(written, buff, "65535.999985");
|
||||
|
||||
written = LIBC_NAMESPACE::sprintf(buff, "%r", 0x7fff); // fract max
|
||||
ASSERT_STREQ_LEN(written, buff, "0.999969");
|
||||
|
||||
written = LIBC_NAMESPACE::sprintf(buff, "%k", 0x7fffffff); // accum max
|
||||
ASSERT_STREQ_LEN(written, buff, "65535.999969");
|
||||
|
||||
// Length Modifier Tests.
|
||||
|
||||
written = LIBC_NAMESPACE::sprintf(buff, "%hk", 0x0); // 0.0
|
||||
ASSERT_STREQ_LEN(written, buff, "0.000000");
|
||||
|
||||
written = LIBC_NAMESPACE::sprintf(buff, "%hk", 0xffff); // -short accum max
|
||||
ASSERT_STREQ_LEN(written, buff, "-255.992188");
|
||||
|
||||
written = LIBC_NAMESPACE::sprintf(buff, "%hr", 0x0); // 0.0
|
||||
ASSERT_STREQ_LEN(written, buff, "0.000000");
|
||||
|
||||
written = LIBC_NAMESPACE::sprintf(buff, "%hr", 0xff); // -short fract max
|
||||
ASSERT_STREQ_LEN(written, buff, "-0.992188");
|
||||
|
||||
written = LIBC_NAMESPACE::sprintf(buff, "%hK", 0x0); // 0.0
|
||||
ASSERT_STREQ_LEN(written, buff, "0.000000");
|
||||
|
||||
written =
|
||||
LIBC_NAMESPACE::sprintf(buff, "%hK", 0xffff); // unsigned short accum max
|
||||
ASSERT_STREQ_LEN(written, buff, "255.996094");
|
||||
|
||||
written = LIBC_NAMESPACE::sprintf(buff, "%hR", 0x0); // 0.0
|
||||
ASSERT_STREQ_LEN(written, buff, "0.000000");
|
||||
|
||||
written =
|
||||
LIBC_NAMESPACE::sprintf(buff, "%hR", 0xff); // unsigned short fract max
|
||||
ASSERT_STREQ_LEN(written, buff, "0.996094");
|
||||
|
||||
written = LIBC_NAMESPACE::sprintf(buff, "%lk", 0x0); // 0.0
|
||||
ASSERT_STREQ_LEN(written, buff, "0.000000");
|
||||
|
||||
written = LIBC_NAMESPACE::sprintf(buff, "%lk",
|
||||
0xffffffffffffffff); //-long accum max
|
||||
ASSERT_STREQ_LEN(written, buff, "-4294967296.000000");
|
||||
|
||||
written = LIBC_NAMESPACE::sprintf(buff, "%lr", 0x0); // 0.0
|
||||
ASSERT_STREQ_LEN(written, buff, "0.000000");
|
||||
|
||||
written = LIBC_NAMESPACE::sprintf(buff, "%lr",
|
||||
0xffffffff); //-long fract max
|
||||
ASSERT_STREQ_LEN(written, buff, "-1.000000");
|
||||
|
||||
written = LIBC_NAMESPACE::sprintf(buff, "%lK", 0x0); // 0.0
|
||||
ASSERT_STREQ_LEN(written, buff, "0.000000");
|
||||
|
||||
written =
|
||||
LIBC_NAMESPACE::sprintf(buff, "%lK",
|
||||
0xffffffffffffffff); // unsigned long accum max
|
||||
ASSERT_STREQ_LEN(written, buff, "4294967296.000000");
|
||||
|
||||
written = LIBC_NAMESPACE::sprintf(buff, "%lR", 0x0); // 0.0
|
||||
ASSERT_STREQ_LEN(written, buff, "0.000000");
|
||||
|
||||
written = LIBC_NAMESPACE::sprintf(buff, "%lR",
|
||||
0xffffffff); // unsigned long fract max
|
||||
ASSERT_STREQ_LEN(written, buff, "1.000000");
|
||||
|
||||
// Min Width Tests.
|
||||
|
||||
written = LIBC_NAMESPACE::sprintf(buff, "%10k", 0x0000a000); // 1.25
|
||||
ASSERT_STREQ_LEN(written, buff, " 1.250000");
|
||||
|
||||
written = LIBC_NAMESPACE::sprintf(buff, "%10k", 0x8000a000); //-1.25
|
||||
ASSERT_STREQ_LEN(written, buff, " -1.250000");
|
||||
|
||||
written = LIBC_NAMESPACE::sprintf(buff, "%8k", 0x0000a000); // 1.25
|
||||
ASSERT_STREQ_LEN(written, buff, "1.250000");
|
||||
|
||||
written = LIBC_NAMESPACE::sprintf(buff, "%9k", 0x8000a000); //-1.25
|
||||
ASSERT_STREQ_LEN(written, buff, "-1.250000");
|
||||
|
||||
written = LIBC_NAMESPACE::sprintf(buff, "%4k", 0x0000a000); // 1.25
|
||||
ASSERT_STREQ_LEN(written, buff, "1.250000");
|
||||
|
||||
written = LIBC_NAMESPACE::sprintf(buff, "%4k", 0x8000a000); //-1.25
|
||||
ASSERT_STREQ_LEN(written, buff, "-1.250000");
|
||||
|
||||
// Precision Tests.
|
||||
|
||||
written =
|
||||
LIBC_NAMESPACE::sprintf(buff, "%.16K", 0xFFFFFFFF); // unsigned accum max
|
||||
ASSERT_STREQ_LEN(written, buff, "65535.9999847412109375");
|
||||
|
||||
written = LIBC_NAMESPACE::sprintf(
|
||||
buff, "%.32lK", 0xFFFFFFFFFFFFFFFF); // unsigned long accum max
|
||||
ASSERT_STREQ_LEN(written, buff,
|
||||
"4294967295.99999999976716935634613037109375");
|
||||
|
||||
written =
|
||||
LIBC_NAMESPACE::sprintf(buff, "%.0K", 0xFFFFFFFF); // unsigned accum max
|
||||
ASSERT_STREQ_LEN(written, buff, "65536");
|
||||
|
||||
written = LIBC_NAMESPACE::sprintf(buff, "%.0R", 0xFFFF); // unsigned fract max
|
||||
ASSERT_STREQ_LEN(written, buff, "1");
|
||||
|
||||
// Flag Tests.
|
||||
|
||||
written = LIBC_NAMESPACE::sprintf(buff, "%+k", 0x0000a000); // 1.25
|
||||
ASSERT_STREQ_LEN(written, buff, "+1.250000");
|
||||
|
||||
written = LIBC_NAMESPACE::sprintf(buff, "%+k", 0x8000a000); //-1.25
|
||||
ASSERT_STREQ_LEN(written, buff, "-1.250000");
|
||||
|
||||
written = LIBC_NAMESPACE::sprintf(buff, "% k", 0x0000a000); // 1.25
|
||||
ASSERT_STREQ_LEN(written, buff, " 1.250000");
|
||||
|
||||
written = LIBC_NAMESPACE::sprintf(buff, "% k", 0x8000a000); //-1.25
|
||||
ASSERT_STREQ_LEN(written, buff, "-1.250000");
|
||||
|
||||
// unsigned variants ignore sign flags.
|
||||
written = LIBC_NAMESPACE::sprintf(buff, "%+K", 0x00014000); // 1.25
|
||||
ASSERT_STREQ_LEN(written, buff, "1.250000");
|
||||
|
||||
written = LIBC_NAMESPACE::sprintf(buff, "% K", 0x00014000); // 1.25
|
||||
ASSERT_STREQ_LEN(written, buff, "1.250000");
|
||||
|
||||
written = LIBC_NAMESPACE::sprintf(buff, "%-10k", 0x0000c000); // 1.5
|
||||
ASSERT_STREQ_LEN(written, buff, "1.500000 ");
|
||||
|
||||
written = LIBC_NAMESPACE::sprintf(buff, "%#.k", 0x00008000); // 1.0
|
||||
ASSERT_STREQ_LEN(written, buff, "1.");
|
||||
|
||||
written = LIBC_NAMESPACE::sprintf(buff, "%#.0k", 0x0000c000); // 1.5
|
||||
ASSERT_STREQ_LEN(written, buff, "2.");
|
||||
|
||||
written = LIBC_NAMESPACE::sprintf(buff, "%010k", 0x0000c000); // 1.5
|
||||
ASSERT_STREQ_LEN(written, buff, "001.500000");
|
||||
|
||||
written = LIBC_NAMESPACE::sprintf(buff, "%010k", 0x8000c000); //-1.5
|
||||
ASSERT_STREQ_LEN(written, buff, "-01.500000");
|
||||
|
||||
written = LIBC_NAMESPACE::sprintf(buff, "%+- #0k", 0); // 0.0
|
||||
ASSERT_STREQ_LEN(written, buff, "+0.000000");
|
||||
|
||||
// Combined Tests.
|
||||
|
||||
written = LIBC_NAMESPACE::sprintf(buff, "%10.2k", 0x0004feb8); // 9.99
|
||||
ASSERT_STREQ_LEN(written, buff, " 9.99");
|
||||
|
||||
written = LIBC_NAMESPACE::sprintf(buff, "%5.1k", 0x0004feb8); // 9.99
|
||||
ASSERT_STREQ_LEN(written, buff, " 10.0");
|
||||
|
||||
written = LIBC_NAMESPACE::sprintf(buff, "%-10.2k", 0x0004feb8); // 9.99
|
||||
ASSERT_STREQ_LEN(written, buff, "9.99 ");
|
||||
|
||||
written = LIBC_NAMESPACE::sprintf(buff, "%-5.1k", 0x0004feb8); // 9.99
|
||||
ASSERT_STREQ_LEN(written, buff, "10.0 ");
|
||||
|
||||
written = LIBC_NAMESPACE::sprintf(buff, "%-5.1k", 0x00000001); // accum min
|
||||
ASSERT_STREQ_LEN(written, buff, "0.0 ");
|
||||
|
||||
written = LIBC_NAMESPACE::sprintf(buff, "%30k", 0x7fffffff); // accum max
|
||||
ASSERT_STREQ_LEN(written, buff, " 65535.999969");
|
||||
|
||||
written = LIBC_NAMESPACE::sprintf(buff, "%-30k", 0x7fffffff); // accum max
|
||||
ASSERT_STREQ_LEN(written, buff, "65535.999969 ");
|
||||
|
||||
written = LIBC_NAMESPACE::sprintf(buff, "%20.2lK",
|
||||
0x3b9ac9ffFD70A3D7); // 999999999.99
|
||||
ASSERT_STREQ_LEN(written, buff, " 999999999.99");
|
||||
|
||||
written = LIBC_NAMESPACE::sprintf(buff, "%20.1lK",
|
||||
0x3b9ac9ffFD70A3D7); // 999999999.99
|
||||
ASSERT_STREQ_LEN(written, buff, " 1000000000.0");
|
||||
|
||||
written = LIBC_NAMESPACE::sprintf(buff, "%12.3R %-12.3k", 0x1999,
|
||||
0x00800000); // 0.1, 256.0
|
||||
ASSERT_STREQ_LEN(written, buff, " 0.100 256.000 ");
|
||||
|
||||
written =
|
||||
LIBC_NAMESPACE::sprintf(buff, "%+-#12.3lk % 012.3k", 0x000000001013a92a,
|
||||
0x02740000); // 0.126, 1256.0
|
||||
ASSERT_STREQ_LEN(written, buff, "+0.126 0001256.000");
|
||||
}
|
||||
#endif // defined(LIBC_COMPILER_HAS_FIXED_POINT) &&
|
||||
// !defined(LIBC_COPT_PRINTF_DISABLE_FIXED_POINT)
|
||||
|
||||
#ifndef LIBC_COPT_PRINTF_DISABLE_WRITE_INT
|
||||
TEST(LlvmLibcSPrintfTest, WriteIntConv) {
|
||||
char buff[64];
|
||||
|
||||
@@ -2981,6 +2981,14 @@ libc_function(
|
||||
|
||||
################################ stdio targets #################################
|
||||
|
||||
libc_support_library(
|
||||
name = "printf_config",
|
||||
hdrs = ["src/stdio/printf_core/printf_config.h"],
|
||||
defines = PRINTF_COPTS,
|
||||
deps = [
|
||||
],
|
||||
)
|
||||
|
||||
libc_support_library(
|
||||
name = "printf_core_structs",
|
||||
hdrs = ["src/stdio/printf_core/core_structs.h"],
|
||||
@@ -2988,14 +2996,7 @@ libc_support_library(
|
||||
deps = [
|
||||
":__support_cpp_string_view",
|
||||
":__support_fputil_fp_bits",
|
||||
],
|
||||
)
|
||||
|
||||
libc_support_library(
|
||||
name = "printf_config",
|
||||
hdrs = ["src/stdio/printf_core/printf_config.h"],
|
||||
defines = PRINTF_COPTS,
|
||||
deps = [
|
||||
":printf_config",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -3081,6 +3082,7 @@ libc_support_library(
|
||||
":__support_libc_assert",
|
||||
":__support_uint",
|
||||
":__support_uint128",
|
||||
":printf_config",
|
||||
":printf_core_structs",
|
||||
":printf_writer",
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user