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>;
|
||||
|
||||
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) {
|
||||
auto result = static_cast<double_bigit>(bigits_[index]) - other - borrow;
|
||||
bigits_[index] = static_cast<bigit>(result);
|
||||
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.
|
||||
void subtract_aligned(const bigint& other) {
|
||||
FMT_ASSERT(other.exp_ >= exp_, "unaligned bigints");
|
||||
@ -528,18 +525,61 @@ class bigint {
|
||||
subtract_bigits(i, other.bigits_[j], 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:
|
||||
bigint() : exp_(0) {}
|
||||
|
||||
template <typename Int> explicit bigint(Int n) : exp_(0) {
|
||||
assign(uint32_or_64_or_128_t<Int>(to_unsigned(n)));
|
||||
}
|
||||
explicit bigint(uint64_t n) { assign(n); }
|
||||
|
||||
bigint(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_; }
|
||||
|
||||
bigint& operator<<=(int shift) {
|
||||
@ -557,20 +597,9 @@ class bigint {
|
||||
return *this;
|
||||
}
|
||||
|
||||
bigint& operator*=(uint32_t value) {
|
||||
assert(value > 0);
|
||||
// Verify that the computation cannot overflow.
|
||||
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);
|
||||
template <typename Int> bigint& operator*=(Int value) {
|
||||
FMT_ASSERT(value > 0, "");
|
||||
multiply(uint32_or_64_or_128_t<Int>(value));
|
||||
return *this;
|
||||
}
|
||||
|
||||
@ -659,11 +688,8 @@ class bigint {
|
||||
bigits_[bigit_index] = static_cast<bigit>(sum);
|
||||
sum >>= bits<bigit>::value;
|
||||
}
|
||||
// Remove leading zeros.
|
||||
--num_result_bigits;
|
||||
while (num_result_bigits > 0 && bigits_[num_result_bigits] == 0)
|
||||
--num_result_bigits;
|
||||
bigits_.resize(num_result_bigits + 1);
|
||||
remove_leading_zeros();
|
||||
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:
|
||||
// http://kurtstephens.com/files/p372-steele.pdf.
|
||||
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.
|
||||
// TODO: handle denormals
|
||||
int shift = fp_value.f == 1 ? 1 : 0;
|
||||
// Shift value and pow10 by an extra bit to make lower and upper which are
|
||||
// normally half ulp integers. This eliminates multiplication by 2 during
|
||||
// later computations.
|
||||
bigint value(fp_value.f << (shift + 1)); // 2 * R in (FPP)^2.
|
||||
bigint pow10; // 2 * S in (FPP)^2.
|
||||
bigint lower(1); // (M^- in (FPP)^2).
|
||||
bigint upper(1 << shift); // (M^+ in (FPP)^2).
|
||||
bigint numerator; // 2 * R in (FPP)^2.
|
||||
bigint denominator; // 2 * S in (FPP)^2.
|
||||
bigint lower; // (M^- in (FPP)^2).
|
||||
bigint upper_store;
|
||||
bigint *upper = nullptr; // (M^+ in (FPP)^2).
|
||||
// Shift numerator and denominator by an extra bit to make lower and upper
|
||||
// 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) {
|
||||
value <<= fp_value.e;
|
||||
numerator.assign(significand);
|
||||
numerator <<= fp_value.e;
|
||||
lower.assign(1);
|
||||
lower <<= fp_value.e;
|
||||
upper <<= fp_value.e;
|
||||
pow10.assign_pow10(exp10);
|
||||
pow10 <<= 1;
|
||||
if (shift != 0) {
|
||||
upper_store.assign(1);
|
||||
upper_store <<= fp_value.e + shift;
|
||||
upper = &upper_store;
|
||||
} else {
|
||||
upper = &lower;
|
||||
}
|
||||
denominator.assign_pow10(exp10);
|
||||
denominator <<= 1;
|
||||
} else {
|
||||
pow10 <<= -fp_value.e;
|
||||
// TODO: fixup
|
||||
numerator.assign_pow10(-exp10);
|
||||
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;
|
||||
int num_digits = 0;
|
||||
char* data = buf.data();
|
||||
for (;;) {
|
||||
int digit = value.divmod_assign(pow10);
|
||||
bool low = compare(value, lower) - even < 0; // value <[=] lower.
|
||||
bool high = add_compare(value, upper, pow10) + even >
|
||||
0; // value + upper >[=] pow10.
|
||||
int digit = numerator.divmod_assign(denominator);
|
||||
bool low = compare(numerator, lower) - even < 0; // numerator <[=] lower.
|
||||
bool high = add_compare(numerator, *upper, denominator) + even >
|
||||
0; // numerator + upper >[=] pow10.
|
||||
if (low || high) {
|
||||
if (!low) {
|
||||
++digit;
|
||||
} 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);
|
||||
buf.resize(num_digits);
|
||||
exp10 -= num_digits -1;
|
||||
return;
|
||||
}
|
||||
data[num_digits++] = static_cast<char>('0' + digit);
|
||||
numerator *= 10;
|
||||
lower *= 10;
|
||||
upper *= 10;
|
||||
if (upper != &lower) *upper *= 10;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Double,
|
||||
enable_if_t<(sizeof(Double) == sizeof(uint64_t)), int>>
|
||||
FMT_API bool grisu_format(Double value, buffer<char>& buf, int precision,
|
||||
unsigned options, int& exp) {
|
||||
bool grisu_format(Double value, buffer<char>& buf, int precision,
|
||||
unsigned options, int& exp) {
|
||||
FMT_ASSERT(value >= 0, "value is negative");
|
||||
const bool fixed = (options & grisu_options::fixed) != 0;
|
||||
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);
|
||||
auto result = digits::result();
|
||||
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}.
|
||||
++upper.f; // \tilde{M}^+ + 1 ulp -> M^+_{\uparrow}.
|
||||
// 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);
|
||||
size = handler.size;
|
||||
if (result == digits::error) {
|
||||
exp -= cached_exp10;
|
||||
exp = exp + size - cached_exp10 - 1;
|
||||
fallback_format(value, buf, exp);
|
||||
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));
|
||||
}
|
||||
|
@ -1111,7 +1111,7 @@ It grisu_prettify(const char* digits, int size, int exp, It it,
|
||||
}
|
||||
|
||||
namespace grisu_options {
|
||||
enum { fixed = 1, grisu3 = 2 };
|
||||
enum { fixed = 1, grisu2 = 2 };
|
||||
}
|
||||
|
||||
// Formats value using the Grisu algorithm:
|
||||
|
@ -87,16 +87,19 @@ TEST(BigIntTest, ShiftLeft) {
|
||||
|
||||
TEST(BigIntTest, Multiply) {
|
||||
bigint n(0x42);
|
||||
EXPECT_THROW(n *= 0, assertion_failure);
|
||||
n *= 1;
|
||||
EXPECT_EQ("42", fmt::format("{}", n));
|
||||
n *= 2;
|
||||
EXPECT_EQ("84", fmt::format("{}", n));
|
||||
n *= 0x12345678;
|
||||
EXPECT_EQ("962fc95e0", fmt::format("{}", n));
|
||||
auto max = max_value<uint32_t>();
|
||||
bigint bigmax(max);
|
||||
bigmax *= max;
|
||||
bigint bigmax(max_value<uint32_t>());
|
||||
bigmax *= max_value<uint32_t>();
|
||||
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) {
|
||||
|
@ -55,3 +55,12 @@ TEST(GrisuTest, Prettify) {
|
||||
}
|
||||
|
||||
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