From e2ea94067385a9252ecef6d42cdf9a7408a51326 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sun, 20 Oct 2019 07:55:05 -0700 Subject: [PATCH] Handle assymetric boundaries --- include/fmt/format-inl.h | 88 ++++++++++++++++++++-------------------- test/grisu-test.cc | 3 ++ 2 files changed, 48 insertions(+), 43 deletions(-) diff --git a/include/fmt/format-inl.h b/include/fmt/format-inl.h index 08feab50..9e34e2ae 100644 --- a/include/fmt/format-inl.h +++ b/include/fmt/format-inl.h @@ -364,29 +364,6 @@ class fp { static FMT_CONSTEXPR_DECL const uint64_t implicit_bit = 1ull << double_significand_size; - // Assigns d to this and return true iff predecessor is closer than successor. - template bool assign(Double d) { - // Assume double is in the format [sign][exponent][significand]. - using limits = std::numeric_limits; - const int exponent_size = - bits::value - double_significand_size - 1; // -1 for sign - const uint64_t significand_mask = implicit_bit - 1; - const uint64_t exponent_mask = (~0ull >> 1) & ~significand_mask; - const int exponent_bias = (1 << exponent_size) - limits::max_exponent - 1; - auto u = bit_cast(d); - f = u & significand_mask; - auto biased_e = (u & exponent_mask) >> double_significand_size; - // Predecessor is closer if d is a normalized power of 2 (f == 0) other than - // the smallest normalized number (biased_e > 1). - bool is_predecessor_closer = f == 0 && biased_e > 1; - if (biased_e != 0) - f += implicit_bit; - else - biased_e = 1; // Subnormals use biased exponent 1 (min exponent). - e = static_cast(biased_e - exponent_bias - double_significand_size); - return is_predecessor_closer; - } - public: significand_type f; int e; @@ -417,6 +394,29 @@ class fp { return value; } + // Assigns d to this and return true iff predecessor is closer than successor. + template bool assign(Double d) { + // Assume double is in the format [sign][exponent][significand]. + using limits = std::numeric_limits; + const int exponent_size = + bits::value - double_significand_size - 1; // -1 for sign + const uint64_t significand_mask = implicit_bit - 1; + const uint64_t exponent_mask = (~0ull >> 1) & ~significand_mask; + const int exponent_bias = (1 << exponent_size) - limits::max_exponent - 1; + auto u = bit_cast(d); + f = u & significand_mask; + auto biased_e = (u & exponent_mask) >> double_significand_size; + // Predecessor is closer if d is a normalized power of 2 (f == 0) other than + // the smallest normalized number (biased_e > 1). + bool is_predecessor_closer = f == 0 && biased_e > 1; + if (biased_e != 0) + f += implicit_bit; + else + biased_e = 1; // Subnormals use biased exponent 1 (min exponent). + e = static_cast(biased_e - exponent_bias - double_significand_size); + return is_predecessor_closer; + } + // Assigns d to this together with computing lower and upper boundaries, // where a boundary is a value half way between the number and its predecessor // (lower) or successor (upper). The upper boundary is normalized and lower @@ -779,7 +779,7 @@ enum result { template digits::result grisu_gen_digits(fp value, uint64_t error, int& exp, Handler& handler) { - fp one(1ull << -value.e, value.e); + const fp one(1ull << -value.e, value.e); // The integral part of scaled value (p1 in Grisu) = value / one. It cannot be // zero because it contains a product of two 64-bit numbers with MSB set (due // to normalization) - 1, shifted right by at most 60 bits. @@ -800,7 +800,7 @@ digits::result grisu_gen_digits(fp value, uint64_t error, int& exp, digit = integral / divisor; integral %= divisor; }; - // This optimization by miloyip reduces the number of integer divisions by + // This optimization by Milo Yip reduces the number of integer divisions by // one per iteration. switch (exp) { case 10: @@ -961,27 +961,29 @@ template struct grisu_shortest_handler { // Formats value using a variation of the Fixed-Precision Positive // Floating-Point Printout ((FPP)^2) algorithm by Steele & White: // https://fmt.dev/p372-steele.pdf. -FMT_FUNC void fallback_format(fp value, buffer& buf, int& exp10) { - // Shift to account for unequal gaps when lower boundary is 2 times closer. - // TODO: handle denormals - int shift = 0; // fp_value.f == 1 ? 1 : 0; - bigint numerator; // 2 * R in (FPP)^2. - bigint denominator; // 2 * S in (FPP)^2. +template +void fallback_format(Double d, buffer& buf, int& exp10) { + bigint numerator; // 2 * R in (FPP)^2. + bigint denominator; // 2 * S in (FPP)^2. + // lower and upper are differences between value and corresponding boundaries. bigint lower; // (M^- in (FPP)^2). bigint upper_store; // upper's value if different from lower. 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 = value.f << (shift + 1); + fp value; + // Shift numerator and denominator by an extra bit or two (if lower boundary + // is closer) to make lower and upper integers. This eliminates multiplication + // by 2 during later computations. + // TODO: handle float + int shift = value.assign(d) ? 2 : 1; + uint64_t significand = value.f << shift; if (value.e >= 0) { numerator.assign(significand); numerator <<= value.e; lower.assign(1); lower <<= value.e; - if (shift != 0) { + if (shift != 1) { upper_store.assign(1); - upper_store <<= value.e + shift; + upper_store <<= value.e + 1; upper = &upper_store; } denominator.assign_pow10(exp10); @@ -989,21 +991,21 @@ FMT_FUNC void fallback_format(fp value, buffer& buf, int& exp10) { } else if (exp10 < 0) { numerator.assign_pow10(-exp10); lower.assign(numerator); - if (shift != 0) { + if (shift != 1) { upper_store.assign(numerator); upper_store <<= 1; upper = &upper_store; } numerator *= significand; denominator.assign(1); - denominator <<= 1 - value.e; + denominator <<= shift - value.e; } else { numerator.assign(significand); denominator.assign_pow10(exp10); - denominator <<= 1 - value.e; + denominator <<= shift - value.e; lower.assign(1); - if (shift != 0) { - upper_store.assign(1ull << shift); + if (shift != 1) { + upper_store.assign(1ull << 1); upper = &upper_store; } } @@ -1103,7 +1105,7 @@ bool grisu_format(Double value, buffer& buf, int precision, size = handler.size; if (result == digits::error) { exp = exp + size - cached_exp10 - 1; - fallback_format(fp_value, buf, exp); + fallback_format(value, buf, exp); return true; } } diff --git a/test/grisu-test.cc b/test/grisu-test.cc index 02b9e6fb..3fc670cd 100644 --- a/test/grisu-test.cc +++ b/test/grisu-test.cc @@ -69,4 +69,7 @@ TEST(GrisuTest, Fallback) { EXPECT_EQ("2.2506787569811123e-253", fmt::format("{}", 2.2506787569811123e-253)); EXPECT_EQ("1103618912042992.8", fmt::format("{}", 1103618912042992.8)); + // pow(2, -25) - assymetric boundaries: + EXPECT_EQ("2.9802322387695312e-08", + fmt::format("{}", 2.9802322387695312e-08)); }