mirror of
https://github.com/fmtlib/fmt.git
synced 2024-10-02 04:52:03 +00:00
Handle negative exponent and rename value/pow10 to numerator/denominator
This commit is contained in:
parent
f7a5748fd3
commit
1cbc5fa6cb
@ -502,21 +502,18 @@ class bigint {
|
|||||||
|
|
||||||
friend struct formatter<bigint>;
|
friend struct formatter<bigint>;
|
||||||
|
|
||||||
void assign(uint64_t n) {
|
|
||||||
int num_bigits = 0;
|
|
||||||
do {
|
|
||||||
bigits_[num_bigits++] = n & ~bigit(0);
|
|
||||||
n >>= bigit_bits;
|
|
||||||
} while (n != 0);
|
|
||||||
bigits_.resize(num_bigits);
|
|
||||||
}
|
|
||||||
|
|
||||||
void subtract_bigits(int index, bigit other, bigit& borrow) {
|
void subtract_bigits(int index, bigit other, bigit& borrow) {
|
||||||
auto result = static_cast<double_bigit>(bigits_[index]) - other - borrow;
|
auto result = static_cast<double_bigit>(bigits_[index]) - other - borrow;
|
||||||
bigits_[index] = static_cast<bigit>(result);
|
bigits_[index] = static_cast<bigit>(result);
|
||||||
borrow = static_cast<bigit>(result >> (bigit_bits * 2 - 1));
|
borrow = static_cast<bigit>(result >> (bigit_bits * 2 - 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void remove_leading_zeros() {
|
||||||
|
int num_bigits = static_cast<int>(bigits_.size()) - 1;
|
||||||
|
while (num_bigits > 0 && bigits_[num_bigits] == 0) --num_bigits;
|
||||||
|
bigits_.resize(num_bigits + 1);
|
||||||
|
}
|
||||||
|
|
||||||
// Computes *this -= other assuming aligned bigints and *this >= other.
|
// Computes *this -= other assuming aligned bigints and *this >= other.
|
||||||
void subtract_aligned(const bigint& other) {
|
void subtract_aligned(const bigint& other) {
|
||||||
FMT_ASSERT(other.exp_ >= exp_, "unaligned bigints");
|
FMT_ASSERT(other.exp_ >= exp_, "unaligned bigints");
|
||||||
@ -528,18 +525,61 @@ class bigint {
|
|||||||
subtract_bigits(i, other.bigits_[j], borrow);
|
subtract_bigits(i, other.bigits_[j], borrow);
|
||||||
}
|
}
|
||||||
while (borrow > 0) subtract_bigits(i, 0, borrow);
|
while (borrow > 0) subtract_bigits(i, 0, borrow);
|
||||||
|
remove_leading_zeros();
|
||||||
|
}
|
||||||
|
|
||||||
|
void multiply(uint32_t value) {
|
||||||
|
const double_bigit wide_value = value;
|
||||||
|
bigit carry = 0;
|
||||||
|
for (size_t i = 0, n = bigits_.size(); i < n; ++i) {
|
||||||
|
double_bigit result = bigits_[i] * wide_value + carry;
|
||||||
|
bigits_[i] = static_cast<bigit>(result);
|
||||||
|
carry = static_cast<bigit>(result >> bigit_bits);
|
||||||
|
}
|
||||||
|
if (carry != 0) bigits_.push_back(carry);
|
||||||
|
}
|
||||||
|
|
||||||
|
void multiply(uint64_t value) {
|
||||||
|
const bigit mask = ~bigit(0);
|
||||||
|
const double_bigit lower = value & mask;
|
||||||
|
const double_bigit upper = value >> bigit_bits;
|
||||||
|
double_bigit carry = 0;
|
||||||
|
for (size_t i = 0, n = bigits_.size(); i < n; ++i) {
|
||||||
|
double_bigit result = bigits_[i] * lower + (carry & mask);
|
||||||
|
carry =
|
||||||
|
bigits_[i] * upper + (result >> bigit_bits) + (carry >> bigit_bits);
|
||||||
|
bigits_[i] = static_cast<bigit>(result);
|
||||||
|
}
|
||||||
|
while (carry != 0) {
|
||||||
|
bigits_.push_back(carry & mask);
|
||||||
|
carry >>= bigit_bits;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
bigint() : exp_(0) {}
|
bigint() : exp_(0) {}
|
||||||
|
explicit bigint(uint64_t n) { assign(n); }
|
||||||
template <typename Int> explicit bigint(Int n) : exp_(0) {
|
|
||||||
assign(uint32_or_64_or_128_t<Int>(to_unsigned(n)));
|
|
||||||
}
|
|
||||||
|
|
||||||
bigint(const bigint&) = delete;
|
bigint(const bigint&) = delete;
|
||||||
void operator=(const bigint&) = delete;
|
void operator=(const bigint&) = delete;
|
||||||
|
|
||||||
|
void assign(const bigint& other) {
|
||||||
|
bigits_.resize(other.bigits_.size());
|
||||||
|
auto data = other.bigits_.data();
|
||||||
|
std::copy(data, data + other.bigits_.size(), bigits_.data());
|
||||||
|
exp_ = other.exp_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void assign(uint64_t n) {
|
||||||
|
int num_bigits = 0;
|
||||||
|
do {
|
||||||
|
bigits_[num_bigits++] = n & ~bigit(0);
|
||||||
|
n >>= bigit_bits;
|
||||||
|
} while (n != 0);
|
||||||
|
bigits_.resize(num_bigits);
|
||||||
|
exp_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
int num_bigits() const { return static_cast<int>(bigits_.size()) + exp_; }
|
int num_bigits() const { return static_cast<int>(bigits_.size()) + exp_; }
|
||||||
|
|
||||||
bigint& operator<<=(int shift) {
|
bigint& operator<<=(int shift) {
|
||||||
@ -557,20 +597,9 @@ class bigint {
|
|||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
bigint& operator*=(uint32_t value) {
|
template <typename Int> bigint& operator*=(Int value) {
|
||||||
assert(value > 0);
|
FMT_ASSERT(value > 0, "");
|
||||||
// Verify that the computation cannot overflow.
|
multiply(uint32_or_64_or_128_t<Int>(value));
|
||||||
constexpr double_bigit max_bigit = max_value<bigit>();
|
|
||||||
constexpr double_bigit max_double_bigit = max_value<double_bigit>();
|
|
||||||
static_assert(max_bigit * max_bigit <= max_double_bigit - max_bigit, "");
|
|
||||||
bigit carry = 0;
|
|
||||||
const double_bigit wide_value = value;
|
|
||||||
for (size_t i = 0, n = bigits_.size(); i < n; ++i) {
|
|
||||||
double_bigit result = bigits_[i] * wide_value + carry;
|
|
||||||
bigits_[i] = static_cast<bigit>(result);
|
|
||||||
carry = static_cast<bigit>(result >> bigit_bits);
|
|
||||||
}
|
|
||||||
if (carry != 0) bigits_.push_back(carry);
|
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -659,11 +688,8 @@ class bigint {
|
|||||||
bigits_[bigit_index] = static_cast<bigit>(sum);
|
bigits_[bigit_index] = static_cast<bigit>(sum);
|
||||||
sum >>= bits<bigit>::value;
|
sum >>= bits<bigit>::value;
|
||||||
}
|
}
|
||||||
// Remove leading zeros.
|
|
||||||
--num_result_bigits;
|
--num_result_bigits;
|
||||||
while (num_result_bigits > 0 && bigits_[num_result_bigits] == 0)
|
remove_leading_zeros();
|
||||||
--num_result_bigits;
|
|
||||||
bigits_.resize(num_result_bigits + 1);
|
|
||||||
exp_ *= 2;
|
exp_ *= 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -907,7 +933,7 @@ template <int GRISU_VERSION> struct grisu_shortest_handler {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Format value using a variation of the Fixed-Precision Positive Floating-Point
|
// Formats v using a variation of the Fixed-Precision Positive Floating-Point
|
||||||
// Printout ((FPP)^2) algorithm by Steele & White:
|
// Printout ((FPP)^2) algorithm by Steele & White:
|
||||||
// http://kurtstephens.com/files/p372-steele.pdf.
|
// http://kurtstephens.com/files/p372-steele.pdf.
|
||||||
template <typename Double>
|
template <typename Double>
|
||||||
@ -916,52 +942,74 @@ FMT_FUNC void fallback_format(Double v, buffer<char>& buf, int& exp10) {
|
|||||||
// Shift to account for unequal gaps when lower boundary is 2 times closer.
|
// Shift to account for unequal gaps when lower boundary is 2 times closer.
|
||||||
// TODO: handle denormals
|
// TODO: handle denormals
|
||||||
int shift = fp_value.f == 1 ? 1 : 0;
|
int shift = fp_value.f == 1 ? 1 : 0;
|
||||||
// Shift value and pow10 by an extra bit to make lower and upper which are
|
bigint numerator; // 2 * R in (FPP)^2.
|
||||||
// normally half ulp integers. This eliminates multiplication by 2 during
|
bigint denominator; // 2 * S in (FPP)^2.
|
||||||
// later computations.
|
bigint lower; // (M^- in (FPP)^2).
|
||||||
bigint value(fp_value.f << (shift + 1)); // 2 * R in (FPP)^2.
|
bigint upper_store;
|
||||||
bigint pow10; // 2 * S in (FPP)^2.
|
bigint *upper = nullptr; // (M^+ in (FPP)^2).
|
||||||
bigint lower(1); // (M^- in (FPP)^2).
|
// Shift numerator and denominator by an extra bit to make lower and upper
|
||||||
bigint upper(1 << shift); // (M^+ in (FPP)^2).
|
// which are normally half ulp integers. This eliminates multiplication by 2
|
||||||
|
// during later computations.
|
||||||
|
uint64_t significand = fp_value.f << (shift + 1);
|
||||||
if (fp_value.e >= 0) {
|
if (fp_value.e >= 0) {
|
||||||
value <<= fp_value.e;
|
numerator.assign(significand);
|
||||||
|
numerator <<= fp_value.e;
|
||||||
|
lower.assign(1);
|
||||||
lower <<= fp_value.e;
|
lower <<= fp_value.e;
|
||||||
upper <<= fp_value.e;
|
if (shift != 0) {
|
||||||
pow10.assign_pow10(exp10);
|
upper_store.assign(1);
|
||||||
pow10 <<= 1;
|
upper_store <<= fp_value.e + shift;
|
||||||
|
upper = &upper_store;
|
||||||
|
} else {
|
||||||
|
upper = &lower;
|
||||||
|
}
|
||||||
|
denominator.assign_pow10(exp10);
|
||||||
|
denominator <<= 1;
|
||||||
} else {
|
} else {
|
||||||
pow10 <<= -fp_value.e;
|
numerator.assign_pow10(-exp10);
|
||||||
// TODO: fixup
|
lower.assign(numerator);
|
||||||
|
if (shift != 0) {
|
||||||
|
upper_store.assign(numerator);
|
||||||
|
upper_store <<= 1;
|
||||||
|
upper = &upper_store;
|
||||||
|
} else {
|
||||||
|
upper = &lower;
|
||||||
|
}
|
||||||
|
numerator *= significand;
|
||||||
|
denominator.assign(1);
|
||||||
|
denominator <<= 1 - fp_value.e;
|
||||||
}
|
}
|
||||||
// Invariant: fp_value == (value / pow10) * pow(10, exp10).
|
// Invariant: fp_value == (numerator / denominator) * pow(10, exp10).
|
||||||
bool even = (fp_value.f & 1) == 0;
|
bool even = (fp_value.f & 1) == 0;
|
||||||
int num_digits = 0;
|
int num_digits = 0;
|
||||||
char* data = buf.data();
|
char* data = buf.data();
|
||||||
for (;;) {
|
for (;;) {
|
||||||
int digit = value.divmod_assign(pow10);
|
int digit = numerator.divmod_assign(denominator);
|
||||||
bool low = compare(value, lower) - even < 0; // value <[=] lower.
|
bool low = compare(numerator, lower) - even < 0; // numerator <[=] lower.
|
||||||
bool high = add_compare(value, upper, pow10) + even >
|
bool high = add_compare(numerator, *upper, denominator) + even >
|
||||||
0; // value + upper >[=] pow10.
|
0; // numerator + upper >[=] pow10.
|
||||||
if (low || high) {
|
if (low || high) {
|
||||||
if (!low) {
|
if (!low) {
|
||||||
++digit;
|
++digit;
|
||||||
} else if (high) {
|
} else if (high) {
|
||||||
// TODO: round up if 2 * value >= pow10
|
// TODO: round up if 2 * numerator >= denominator
|
||||||
}
|
}
|
||||||
data[num_digits++] = static_cast<char>('0' + digit);
|
data[num_digits++] = static_cast<char>('0' + digit);
|
||||||
buf.resize(num_digits);
|
buf.resize(num_digits);
|
||||||
|
exp10 -= num_digits -1;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
data[num_digits++] = static_cast<char>('0' + digit);
|
data[num_digits++] = static_cast<char>('0' + digit);
|
||||||
|
numerator *= 10;
|
||||||
lower *= 10;
|
lower *= 10;
|
||||||
upper *= 10;
|
if (upper != &lower) *upper *= 10;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Double,
|
template <typename Double,
|
||||||
enable_if_t<(sizeof(Double) == sizeof(uint64_t)), int>>
|
enable_if_t<(sizeof(Double) == sizeof(uint64_t)), int>>
|
||||||
FMT_API bool grisu_format(Double value, buffer<char>& buf, int precision,
|
bool grisu_format(Double value, buffer<char>& buf, int precision,
|
||||||
unsigned options, int& exp) {
|
unsigned options, int& exp) {
|
||||||
FMT_ASSERT(value >= 0, "value is negative");
|
FMT_ASSERT(value >= 0, "value is negative");
|
||||||
const bool fixed = (options & grisu_options::fixed) != 0;
|
const bool fixed = (options & grisu_options::fixed) != 0;
|
||||||
if (value <= 0) { // <= instead of == to silence a warning.
|
if (value <= 0) { // <= instead of == to silence a warning.
|
||||||
@ -1003,7 +1051,14 @@ FMT_API bool grisu_format(Double value, buffer<char>& buf, int precision,
|
|||||||
assert(min_exp <= upper.e && upper.e <= -32);
|
assert(min_exp <= upper.e && upper.e <= -32);
|
||||||
auto result = digits::result();
|
auto result = digits::result();
|
||||||
int size = 0;
|
int size = 0;
|
||||||
if ((options & grisu_options::grisu3) != 0) {
|
if ((options & grisu_options::grisu2) != 0) {
|
||||||
|
++lower.f; // \tilde{M}^- + 1 ulp -> M^-_{\uparrow}.
|
||||||
|
--upper.f; // \tilde{M}^+ - 1 ulp -> M^+_{\downarrow}.
|
||||||
|
grisu_shortest_handler<2> handler{buf.data(), 0, (upper - normalized).f};
|
||||||
|
result = grisu_gen_digits(upper, upper.f - lower.f, exp, handler);
|
||||||
|
size = handler.size;
|
||||||
|
assert(result != digits::error);
|
||||||
|
} else {
|
||||||
--lower.f; // \tilde{M}^- - 1 ulp -> M^-_{\downarrow}.
|
--lower.f; // \tilde{M}^- - 1 ulp -> M^-_{\downarrow}.
|
||||||
++upper.f; // \tilde{M}^+ + 1 ulp -> M^+_{\uparrow}.
|
++upper.f; // \tilde{M}^+ + 1 ulp -> M^+_{\uparrow}.
|
||||||
// Numbers outside of (lower, upper) definitely do not round to value.
|
// Numbers outside of (lower, upper) definitely do not round to value.
|
||||||
@ -1011,17 +1066,10 @@ FMT_API bool grisu_format(Double value, buffer<char>& buf, int precision,
|
|||||||
result = grisu_gen_digits(upper, upper.f - lower.f, exp, handler);
|
result = grisu_gen_digits(upper, upper.f - lower.f, exp, handler);
|
||||||
size = handler.size;
|
size = handler.size;
|
||||||
if (result == digits::error) {
|
if (result == digits::error) {
|
||||||
exp -= cached_exp10;
|
exp = exp + size - cached_exp10 - 1;
|
||||||
fallback_format(value, buf, exp);
|
fallback_format(value, buf, exp);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
++lower.f; // \tilde{M}^- + 1 ulp -> M^-_{\uparrow}.
|
|
||||||
--upper.f; // \tilde{M}^+ - 1 ulp -> M^+_{\downarrow}.
|
|
||||||
grisu_shortest_handler<2> handler{buf.data(), 0, (upper - normalized).f};
|
|
||||||
result = grisu_gen_digits(upper, upper.f - lower.f, exp, handler);
|
|
||||||
size = handler.size;
|
|
||||||
assert(result != digits::error);
|
|
||||||
}
|
}
|
||||||
buf.resize(to_unsigned(size));
|
buf.resize(to_unsigned(size));
|
||||||
}
|
}
|
||||||
|
@ -1111,7 +1111,7 @@ It grisu_prettify(const char* digits, int size, int exp, It it,
|
|||||||
}
|
}
|
||||||
|
|
||||||
namespace grisu_options {
|
namespace grisu_options {
|
||||||
enum { fixed = 1, grisu3 = 2 };
|
enum { fixed = 1, grisu2 = 2 };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Formats value using the Grisu algorithm:
|
// Formats value using the Grisu algorithm:
|
||||||
|
@ -87,16 +87,19 @@ TEST(BigIntTest, ShiftLeft) {
|
|||||||
|
|
||||||
TEST(BigIntTest, Multiply) {
|
TEST(BigIntTest, Multiply) {
|
||||||
bigint n(0x42);
|
bigint n(0x42);
|
||||||
|
EXPECT_THROW(n *= 0, assertion_failure);
|
||||||
n *= 1;
|
n *= 1;
|
||||||
EXPECT_EQ("42", fmt::format("{}", n));
|
EXPECT_EQ("42", fmt::format("{}", n));
|
||||||
n *= 2;
|
n *= 2;
|
||||||
EXPECT_EQ("84", fmt::format("{}", n));
|
EXPECT_EQ("84", fmt::format("{}", n));
|
||||||
n *= 0x12345678;
|
n *= 0x12345678;
|
||||||
EXPECT_EQ("962fc95e0", fmt::format("{}", n));
|
EXPECT_EQ("962fc95e0", fmt::format("{}", n));
|
||||||
auto max = max_value<uint32_t>();
|
bigint bigmax(max_value<uint32_t>());
|
||||||
bigint bigmax(max);
|
bigmax *= max_value<uint32_t>();
|
||||||
bigmax *= max;
|
|
||||||
EXPECT_EQ("fffffffe00000001", fmt::format("{}", bigmax));
|
EXPECT_EQ("fffffffe00000001", fmt::format("{}", bigmax));
|
||||||
|
bigmax.assign(max_value<uint64_t>());
|
||||||
|
bigmax *= max_value<uint64_t>();
|
||||||
|
EXPECT_EQ("fffffffffffffffe0000000000000001", fmt::format("{}", bigmax));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(BigIntTest, Accumulator) {
|
TEST(BigIntTest, Accumulator) {
|
||||||
|
@ -55,3 +55,12 @@ TEST(GrisuTest, Prettify) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST(GrisuTest, ZeroPrecision) { EXPECT_EQ("1", fmt::format("{:.0}", 1.0)); }
|
TEST(GrisuTest, ZeroPrecision) { EXPECT_EQ("1", fmt::format("{:.0}", 1.0)); }
|
||||||
|
|
||||||
|
TEST(GrisuTest, Fallback) {
|
||||||
|
EXPECT_EQ("1e+23", fmt::format("{}", 1e23));
|
||||||
|
EXPECT_EQ("9e-265", fmt::format("{}", 9e-265));
|
||||||
|
EXPECT_EQ("5.423717798060526e+125",
|
||||||
|
fmt::format("{}", 5.423717798060526e+125));
|
||||||
|
EXPECT_EQ("1.372371880954233e-288",
|
||||||
|
fmt::format("{}", 1.372371880954233e-288));
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user