From 569ac91e0bd1be1b2ea1f4e4b546b3a137299a6c Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sat, 25 Aug 2018 11:39:38 -0700 Subject: [PATCH] Implement Grisu boundary computation --- include/fmt/format.h | 14 +++++++++ test/core-test.cc | 51 ------------------------------- test/format-test.cc | 71 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 85 insertions(+), 51 deletions(-) diff --git a/include/fmt/format.h b/include/fmt/format.h index 9bdf9a1c..bd559a52 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -297,6 +297,7 @@ class fp { static FMT_CONSTEXPR_DECL const int significand_size = sizeof(significand_type) * char_size; + fp(): f(0), e(0) {} fp(uint64_t f, int e): f(f), e(e) {} // Constructs fp from an IEEE754 double. It is a template to prevent compile @@ -335,6 +336,19 @@ class fp { f <<= offset; e -= offset; } + + // Compute lower and upper boundaries (m^- and m^+ in the Grisu paper), 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 + // has the same exponent but may be not normalized. + void compute_boundaries(fp &lower, fp &upper) const { + lower = f == implicit_bit ? + fp((f << 2) - 1, e - 2) : fp((f << 1) - 1, e - 1); + upper = fp((f << 1) + 1, e - 1); + upper.normalize<1>(); // 1 is to account for the exponent shift above. + lower.f <<= lower.e - upper.e; + lower.e = upper.e; + } }; // Returns an fp number representing x - y. Result may not be normalized. diff --git a/test/core-test.cc b/test/core-test.cc index 06e63689..8648c736 100644 --- a/test/core-test.cc +++ b/test/core-test.cc @@ -36,7 +36,6 @@ using fmt::basic_format_arg; using fmt::internal::basic_buffer; using fmt::basic_memory_buffer; using fmt::string_view; -using fmt::internal::fp; using fmt::internal::value; using testing::_; @@ -879,56 +878,6 @@ TEST(UtilTest, ParseNonnegativeInt) { fmt::format_error, "number is too big"); } -template -void test_construct_from_double() { - fmt::print("warning: double is not IEC559, skipping FP tests\n"); -} - -template <> -void test_construct_from_double() { - auto v = fp(1.23); - EXPECT_EQ(v.f, 0x13ae147ae147aeu); - EXPECT_EQ(v.e, -52); -} - -TEST(FPTest, ConstructFromDouble) { - test_construct_from_double::is_iec559>(); -} - -TEST(FPTest, Normalize) { - auto v = fp(0xbeef, 42); - v.normalize(); - EXPECT_EQ(0xbeef000000000000, v.f); - EXPECT_EQ(-6, v.e); -} - -TEST(FPTest, Subtract) { - auto v = fp(123, 1) - fp(102, 1); - EXPECT_EQ(v.f, 21u); - EXPECT_EQ(v.e, 1); -} - -TEST(FPTest, Multiply) { - auto v = fp(123ULL << 32, 4) * fp(56ULL << 32, 7); - EXPECT_EQ(v.f, 123u * 56u); - EXPECT_EQ(v.e, 4 + 7 + 64); - v = fp(123ULL << 32, 4) * fp(567ULL << 31, 8); - EXPECT_EQ(v.f, (123 * 567 + 1u) / 2); - EXPECT_EQ(v.e, 4 + 8 + 64); -} - -TEST(FPTest, GetCachedPower) { - typedef std::numeric_limits limits; - for (auto exp = limits::min_exponent; exp <= limits::max_exponent; ++exp) { - int dec_exp = 0; - auto fp = fmt::internal::get_cached_power(exp, dec_exp); - EXPECT_LE(exp, fp.e); - int dec_exp_step = 8; - EXPECT_LE(fp.e, exp + dec_exp_step * log2(10)); - EXPECT_DOUBLE_EQ(pow(10, dec_exp), ldexp(fp.f, fp.e)); - } -} - TEST(IteratorTest, CountingIterator) { fmt::internal::counting_iterator it; auto prev = it++; diff --git a/test/format-test.cc b/test/format-test.cc index 73692164..4ddcc7a7 100644 --- a/test/format-test.cc +++ b/test/format-test.cc @@ -31,6 +31,77 @@ using fmt::format_error; using fmt::string_view; using fmt::memory_buffer; using fmt::wmemory_buffer; +using fmt::internal::fp; + +template +void test_construct_from_double() { + fmt::print("warning: double is not IEC559, skipping FP tests\n"); +} + +template <> +void test_construct_from_double() { + auto v = fp(1.23); + EXPECT_EQ(v.f, 0x13ae147ae147aeu); + EXPECT_EQ(v.e, -52); +} + +TEST(FPTest, ConstructFromDouble) { + test_construct_from_double::is_iec559>(); +} + +TEST(FPTest, Normalize) { + auto v = fp(0xbeef, 42); + v.normalize(); + EXPECT_EQ(0xbeef000000000000, v.f); + EXPECT_EQ(-6, v.e); +} + +TEST(FPTest, ComputeBoundariesSubnormal) { + auto v = fp(0xbeef, 42); + fp lower, upper; + v.compute_boundaries(lower, upper); + EXPECT_EQ(0xbeee800000000000, lower.f); + EXPECT_EQ(-6, lower.e); + EXPECT_EQ(0xbeef800000000000, upper.f); + EXPECT_EQ(-6, upper.e); +} + +TEST(FPTest, ComputeBoundaries) { + auto v = fp(0x10000000000000, 42); + fp lower, upper; + v.compute_boundaries(lower, upper); + EXPECT_EQ(0x7ffffffffffffe00, lower.f); + EXPECT_EQ(31, lower.e); + EXPECT_EQ(0x8000000000000400, upper.f); + EXPECT_EQ(31, upper.e); +} + +TEST(FPTest, Subtract) { + auto v = fp(123, 1) - fp(102, 1); + EXPECT_EQ(v.f, 21u); + EXPECT_EQ(v.e, 1); +} + +TEST(FPTest, Multiply) { + auto v = fp(123ULL << 32, 4) * fp(56ULL << 32, 7); + EXPECT_EQ(v.f, 123u * 56u); + EXPECT_EQ(v.e, 4 + 7 + 64); + v = fp(123ULL << 32, 4) * fp(567ULL << 31, 8); + EXPECT_EQ(v.f, (123 * 567 + 1u) / 2); + EXPECT_EQ(v.e, 4 + 8 + 64); +} + +TEST(FPTest, GetCachedPower) { + typedef std::numeric_limits limits; + for (auto exp = limits::min_exponent; exp <= limits::max_exponent; ++exp) { + int dec_exp = 0; + auto fp = fmt::internal::get_cached_power(exp, dec_exp); + EXPECT_LE(exp, fp.e); + int dec_exp_step = 8; + EXPECT_LE(fp.e, exp + dec_exp_step * log2(10)); + EXPECT_DOUBLE_EQ(pow(10, dec_exp), ldexp(fp.f, fp.e)); + } +} namespace {