Files
rippled/src/tests/libxrpl/basics/Number.cpp

3054 lines
125 KiB
C++

#include <xrpl/basics/Number.h>
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/protocol/IOUAmount.h>
#include <xrpl/protocol/Issue.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/SystemParameters.h>
#include <xrpl/protocol/XRPAmount.h>
// NOLINTNEXTLINE(misc-include-cleaner)
#include <boost/multiprecision/cpp_dec_float.hpp>
#include <boost/multiprecision/number.hpp>
#include <gtest/gtest.h>
#include <algorithm>
#include <array>
#include <cctype>
#include <cstdint>
#include <functional>
#include <iomanip>
#include <limits>
#include <map>
#include <ranges>
#include <sstream>
#include <stdexcept>
#include <string>
#include <tuple>
#include <utility>
#include <vector>
namespace xrpl {
using BigInt = boost::multiprecision::cpp_int;
using Dec = boost::multiprecision::cpp_dec_float_50;
static std::string
fmt(BigInt const& value)
{
auto s = to_string(value);
std::string out;
int count = 0;
for (char const& ch : std::views::reverse(s))
{
if (count != 0 && count % 3 == 0 && (isdigit(ch) != 0))
out.insert(out.begin(), '_');
out.insert(out.begin(), ch);
++count;
}
return out;
}
BigInt
toBigInt(Number const& n)
{
BigInt v = n.mantissa();
auto e = n.exponent();
for (; e > 0; --e)
v *= 10;
for (; e < 0; ++e)
{
EXPECT_EQ(v % 10, 0);
v /= 10;
}
return v;
}
template <class T = Dec>
static T
pow10(int n)
{
if (n == 0)
return 1;
if (n == 1)
return 10;
if (n > 1)
{
auto r = pow10<T>(n / 2);
r *= r;
if (n % 2 != 0)
r *= 10;
return r;
}
T p = 1;
p /= pow10<T>(-n);
return p;
}
static std::string
fmt(Dec const& value)
{
std::ostringstream os;
os << std::setprecision(40) << value;
return os.str();
}
TEST(NumberTest, zero)
{
for (auto const mantissaScale : MantissaRange::getAllScales())
{
NumberMantissaScaleGuard const sg(mantissaScale);
for (Number const& z : {Number{0, 0}, Number{0}})
{
EXPECT_EQ(z.mantissa(), 0);
EXPECT_EQ(z.exponent(), Number{}.exponent());
EXPECT_EQ((z + z), z);
EXPECT_EQ((z - z), z);
EXPECT_EQ(z, -z);
}
}
}
TEST(NumberTest, limits)
{
for (auto const mantissaScale : MantissaRange::getAllScales())
{
NumberMantissaScaleGuard const sg(mantissaScale);
auto const scale = Number::getMantissaScale();
bool caught = false;
auto const minMantissa = Number::minMantissa();
try
{
[[maybe_unused]] Number const x =
Number{false, minMantissa * 10, 32768, Number::Normalized{}};
}
catch (std::overflow_error const&)
{
caught = true;
}
EXPECT_TRUE(caught);
auto test = [](auto const& x, auto const& y, int line) {
auto const result = x == y;
std::stringstream ss;
ss << x << " == " << y << " -> " << (result ? "true" : "false");
EXPECT_TRUE(result) << ss.str() << " (" << __FILE__ << ":" << line << ")";
};
test(
Number{false, minMantissa * 10, 32767, Number::Normalized{}},
Number{false, minMantissa, 32768, Number::Normalized{}},
__LINE__);
test(Number{false, minMantissa, -32769, Number::Normalized{}}, Number{}, __LINE__);
test(
Number{false, minMantissa, 32000, Number::Normalized{}} * 1'000 +
Number{false, 1'500, 32000, Number::Normalized{}},
Number{false, minMantissa + 2, 32003, Number::Normalized{}},
__LINE__);
// 9,223,372,036,854,775,808
test(
Number{std::numeric_limits<std::int64_t>::min()},
scale == MantissaRange::MantissaScale::Small
? Number{-9'223'372'036'854'776, 3}
: Number{true, 9'223'372'036'854'775'808ULL, 0, Number::Normalized{}},
__LINE__);
test(
Number{std::numeric_limits<std::int64_t>::min() + 1},
scale == MantissaRange::MantissaScale::Small ? Number{-9'223'372'036'854'776, 3}
: Number{-9'223'372'036'854'775'807},
__LINE__);
test(
Number{std::numeric_limits<std::int64_t>::max()},
Number{
scale == MantissaRange::MantissaScale::Small
? 9'223'372'036'854'776
: std::numeric_limits<std::int64_t>::max(),
18 - Number::mantissaLog()},
__LINE__);
caught = false;
try
{
[[maybe_unused]]
Number const q = Number{false, minMantissa, 32767, Number::Normalized{}} * 100;
}
catch (std::overflow_error const&)
{
caught = true;
}
EXPECT_TRUE(caught);
try
{
Number{1, 2000000, Number::Normalized{}};
ADD_FAILURE();
}
catch (std::overflow_error const& e)
{
std::string const expected = "Number::normalize 2";
EXPECT_EQ(e.what(), expected) << e.what();
}
if (scale == MantissaRange::MantissaScale::Large330)
{
// Normalization with the other scales, including the older large mantissa scales, will
// overflow.
Number const bigNum{Number::kMaxRepUp, Number::kMaxExponent, Number::Normalized{}};
// The display of large exponents won't go above kMaxExponent
EXPECT_EQ(to_string(bigNum), "9223372036854775810e32768") << bigNum;
// Perhaps surprisingly, this is ok, because the exponent range is related to when the
// number is _normalized_, and for mantissas > kMaxRep, the accessors return values that
// are not normalized.
EXPECT_EQ(bigNum.mantissa(), 922337203685477581ULL) << bigNum.mantissa();
EXPECT_EQ(bigNum.exponent(), 32769) << bigNum.exponent();
}
else
{
try
{
Number{Number::kMaxRepUp, Number::kMaxExponent, Number::Normalized{}};
ADD_FAILURE();
}
catch (std::overflow_error const& e)
{
std::string const expected =
(scale == MantissaRange::MantissaScale::Small ? "Number::normalize 1"
: "Number::normalize 1.5");
EXPECT_EQ(e.what(), expected) << e.what();
}
}
}
}
TEST(NumberTest, add)
{
for (auto const mantissaScale : MantissaRange::getAllScales())
{
NumberMantissaScaleGuard const sg(mantissaScale);
auto const scale = Number::getMantissaScale();
EXPECT_EQ(Number::getround(), Number::RoundingMode::ToNearest)
<< to_string(Number::getround());
using Case = std::tuple<Number, Number, Number, int>;
// TODO: Move these to the blocks where they're used
auto const cSmall = std::to_array<Case>({
{Number{1'000'000'000'000'000, -15},
Number{6'555'555'555'555'555, -29},
Number{1'000'000'000'000'066, -15},
__LINE__},
{Number{-1'000'000'000'000'000, -15},
Number{-6'555'555'555'555'555, -29},
Number{-1'000'000'000'000'066, -15},
__LINE__},
{Number{-1'000'000'000'000'000, -15},
Number{6'555'555'555'555'555, -29},
Number{-9'999'999'999'999'344, -16},
__LINE__},
{Number{-6'555'555'555'555'555, -29},
Number{1'000'000'000'000'000, -15},
Number{9'999'999'999'999'344, -16},
__LINE__},
{Number{}, Number{5}, Number{5}, __LINE__},
{Number{5}, Number{}, Number{5}, __LINE__},
{Number{5'555'555'555'555'555, -32768},
Number{-5'555'555'555'555'554, -32768},
Number{0},
__LINE__},
{Number{-9'999'999'999'999'999, -31},
Number{1'000'000'000'000'000, -15},
Number{9'999'999'999'999'990, -16},
__LINE__},
});
auto const cLarge = std::to_array<Case>(
// Note that items with extremely large mantissas need to be
// calculated, because otherwise they overflow uint64. Items from C
// with larger mantissa
{
{Number{1'000'000'000'000'000, -15},
Number{6'555'555'555'555'555, -29},
Number{1'000'000'000'000'065'556, -18},
__LINE__},
{Number{-1'000'000'000'000'000, -15},
Number{-6'555'555'555'555'555, -29},
Number{-1'000'000'000'000'065'556, -18},
__LINE__},
{Number{-1'000'000'000'000'000, -15},
Number{6'555'555'555'555'555, -29},
Number{true, 9'999'999'999'999'344'444ULL, -19, Number::Normalized{}},
__LINE__},
{Number{-6'555'555'555'555'555, -29},
Number{1'000'000'000'000'000, -15},
Number{false, 9'999'999'999'999'344'444ULL, -19, Number::Normalized{}},
__LINE__},
{Number{}, Number{5}, Number{5}, __LINE__},
{Number{5}, Number{}, Number{5}, __LINE__},
{Number{5'555'555'555'555'555'000, -32768},
Number{-5'555'555'555'555'554'000, -32768},
Number{0},
__LINE__},
{Number{-9'999'999'999'999'999, -31},
Number{1'000'000'000'000'000, -15},
Number{9'999'999'999'999'990, -16},
__LINE__},
// Items from cSmall expanded for the larger mantissa
{Number{1'000'000'000'000'000'000, -18},
Number{6'555'555'555'555'555'555, -35},
Number{1'000'000'000'000'000'066, -18},
__LINE__},
{Number{-1'000'000'000'000'000'000, -18},
Number{-6'555'555'555'555'555'555, -35},
Number{-1'000'000'000'000'000'066, -18},
__LINE__},
{Number{-1'000'000'000'000'000'000, -18},
Number{6'555'555'555'555'555'555, -35},
Number{true, 9'999'999'999'999'999'344ULL, -19, Number::Normalized{}},
__LINE__},
{Number{-6'555'555'555'555'555'555, -35},
Number{1'000'000'000'000'000'000, -18},
Number{false, 9'999'999'999'999'999'344ULL, -19, Number::Normalized{}},
__LINE__},
{Number{}, Number{5}, Number{5}, __LINE__},
{Number{5'555'555'555'555'555'555, -32768},
Number{-5'555'555'555'555'555'554, -32768},
Number{0},
__LINE__},
{Number{true, 9'999'999'999'999'999'999ULL, -37, Number::Normalized{}},
Number{1'000'000'000'000'000'000, -18},
Number{false, 9'999'999'999'999'999'990ULL, -19, Number::Normalized{}},
__LINE__},
{Number{Number::kMaxRep - 1}, Number{1, 0}, Number{Number::kMaxRep}, __LINE__},
// Test extremes
{
// Each Number operand rounds up, so the actual mantissa is
// minMantissa
Number{false, 9'999'999'999'999'999'999ULL, 0, Number::Normalized{}},
Number{false, 9'999'999'999'999'999'999ULL, 0, Number::Normalized{}},
Number{2, 19},
__LINE__,
},
{
// Does not round. Mantissas are going to be > kMaxRep, so if
// added together as uint64_t's, the result will overflow.
// With addition using uint128_t, there's no problem. After
// normalizing, the resulting mantissa ends up less than
// kMaxRep.
Number{false, 9'999'999'999'999'999'990ULL, 0, Number::Normalized{}},
Number{false, 9'999'999'999'999'999'990ULL, 0, Number::Normalized{}},
Number{false, 1'999'999'999'999'999'998ULL, 1, Number::Normalized{}},
__LINE__,
},
});
auto const cLargeLegacy = std::to_array<Case>({
{Number{Number::kMaxRep}, Number{6, -1}, Number{Number::kMaxRep / 10, 1}, __LINE__},
});
auto const cLarge320 = std::to_array<Case>({
{Number{Number::kMaxRep},
Number{6, -1},
Number{(Number::kMaxRep / 10) + 1, 1},
__LINE__},
});
auto const cLargeCorrected = std::to_array<Case>({
{Number{Number::kMaxRep}, Number{6, -1}, Number{Number::kMaxRep}, __LINE__},
});
auto test = [](auto const& c) {
for (auto const& [x, y, z, line] : c)
{
auto const result = x + y;
std::stringstream ss;
ss << x << " + " << y << " = " << result << ". Expected: " << z;
EXPECT_EQ(result, z) << ss.str() << " Line: " << line;
}
};
if (scale == MantissaRange::MantissaScale::Small)
{
test(cSmall);
}
else
{
test(cLarge);
if (scale == MantissaRange::MantissaScale::LargeLegacy)
{
test(cLargeLegacy);
}
else if (scale == MantissaRange::MantissaScale::Large320)
{
test(cLarge320);
}
else
{
test(cLargeCorrected);
// This has to be created in this block, because normalization with the other
// scales, including the older large mantissa scales, will overflow.
Number const bigResult{
Number::kMaxRepUp, Number::kMaxExponent, Number::Normalized{}};
auto const cBigNums = std::to_array<Case>({
{
// Add 3 to the mantissa to avoid rounding
Number::max(),
Number{3, Number::kMaxExponent},
bigResult,
__LINE__,
},
});
test(cBigNums);
}
}
{
bool caught = false;
try
{
Number{false, Number::maxMantissa(), 32768, Number::Normalized{}} +
Number{false, Number::minMantissa(), 32767, Number::Normalized{}} * 5;
}
catch (std::overflow_error const&)
{
caught = true;
}
EXPECT_TRUE(caught);
}
}
}
TEST(NumberTest, add_sub_extreme_exponents)
{
for (auto const mantissaScale : MantissaRange::getAllScales())
{
NumberMantissaScaleGuard const sg(mantissaScale);
auto const scale = Number::getMantissaScale();
EXPECT_EQ(Number::getround(), Number::RoundingMode::ToNearest)
<< to_string(Number::getround());
// Special cases: Exponents at each end of the allowable range
for (auto const round :
{Number::RoundingMode::ToNearest,
Number::RoundingMode::TowardsZero,
Number::RoundingMode::Downward,
Number::RoundingMode::Upward})
{
NumberRoundModeGuard const rg{round};
auto const bigMantissa = std::invoke([scale, round] {
auto m = Number::maxMantissa();
if (scale != MantissaRange::MantissaScale::Small)
{
// At the large scales, the maxMantissa is not representable, so we need to
// shrink it down to a representable value.
m /= 10;
}
if (round == Number::RoundingMode::Upward)
{
// Rounding upward will overflow if the mantissa is at maxMantissa. Subtract an
// arbitrary small value to keep the mantissa near the limit, but with a
// little room to grow. 67 has no meaning, except that it's, you know,
// six seven.
m -= 67;
}
return m;
});
auto const params = {
std::make_pair(Number::minMantissa(), 0),
// At the large scales, the maxMantissa is not representable, so we need to shrink
// it down to a representable value. Rounding upward will overflow if the mantissa
// is right at the all nines value. To keep things a little simpler, do those
// modifications unconditionally.
std::make_pair(bigMantissa, 1),
};
for (auto const& [mantissa, exponentOffset] : params)
{
auto const x = Number{mantissa, Number::kMaxExponent, Number::Normalized{}};
auto const y =
Number{mantissa, Number::kMinExponent + exponentOffset, Number::Normalized{}};
std::ostringstream detail;
detail << "Scale: " << to_string(scale) << ", round: " << to_string(round)
<< ", x: " << x << ", y: " << y;
EXPECT_EQ(x.mantissa(), mantissa);
EXPECT_EQ(x.exponent(), Number::kMaxExponent);
EXPECT_NE(x, beast::kZero);
EXPECT_EQ(y.mantissa(), mantissa);
EXPECT_EQ(y.exponent(), Number::kMinExponent + exponentOffset);
EXPECT_NE(y, beast::kZero);
{
// x + y
auto const result = x + y;
if (round == Number::RoundingMode::Upward)
{
// Rounding upward will take that little x-bit and round result up to the
// next representable value.
EXPECT_NE(result, x);
EXPECT_EQ(result, (Number{x.mantissa() + 1, x.exponent()}));
}
else
{
EXPECT_EQ(result, x);
}
}
{
// x - y
auto const result = x - y;
switch (round)
{
case Number::RoundingMode::TowardsZero:
if (scale < MantissaRange::MantissaScale::Large330)
{
// Rounding TowardsZero was broken before Large330.
EXPECT_EQ(result, x) << detail.str();
break;
}
[[fallthrough]];
case Number::RoundingMode::Downward:
// Rounding downward (or toward zero in Large330) will take that little
// x-bit and round result down to the next representable value.
EXPECT_NE(result, x) << detail.str();
EXPECT_EQ(result, (Number{x.mantissa() - 1, x.exponent()}))
<< detail.str();
break;
default:
// Rounding up and toNearest rounds back to the original value
EXPECT_EQ(result, x) << detail.str();
}
}
{
// y + x
auto const result = y + x;
if (round == Number::RoundingMode::Upward)
{
// Rounding upward will take that little x-bit and round result up to the
// next representable value.
EXPECT_NE(result, x);
EXPECT_EQ(result, (Number{x.mantissa() + 1, x.exponent()}));
}
else
{
EXPECT_EQ(result, x);
}
}
{
// y - x
auto const result = y - x;
switch (round)
{
case Number::RoundingMode::TowardsZero:
if (scale < MantissaRange::MantissaScale::Large330)
{
// Rounding TowardsZero was broken before Large330.
EXPECT_EQ(result, -x) << detail.str();
break;
}
[[fallthrough]];
case Number::RoundingMode::Upward:
// Rounding upward (or toward zero in Large330) will take that little
// x-bit and round result up to the next representable negative value.
EXPECT_NE(result, -x) << detail.str();
EXPECT_EQ(result, (Number{-x.mantissa() + 1, x.exponent()}))
<< detail.str();
break;
default:
// Rounding up and toNearest rounds back to the original value
EXPECT_EQ(result, -x) << detail.str();
}
}
}
}
}
}
TEST(NumberTest, sub)
{
for (auto const mantissaScale : MantissaRange::getAllScales())
{
NumberMantissaScaleGuard const sg(mantissaScale);
auto const scale = Number::getMantissaScale();
using Case = std::tuple<Number, Number, Number, int>;
auto const cSmall = std::to_array<Case>(
{{Number{1'000'000'000'000'000, -15},
Number{6'555'555'555'555'555, -29},
Number{9'999'999'999'999'344, -16},
__LINE__},
{Number{6'555'555'555'555'555, -29},
Number{1'000'000'000'000'000, -15},
Number{-9'999'999'999'999'344, -16},
__LINE__},
{Number{1'000'000'000'000'000, -15},
Number{1'000'000'000'000'000, -15},
Number{0},
__LINE__},
{Number{1'000'000'000'000'000, -15},
Number{1'000'000'000'000'001, -15},
Number{-1'000'000'000'000'000, -30},
__LINE__},
{Number{1'000'000'000'000'001, -15},
Number{1'000'000'000'000'000, -15},
Number{1'000'000'000'000'000, -30},
__LINE__}});
auto const cLargeAll = std::to_array<Case>(
// Note that items with extremely large mantissas need to be
// calculated, because otherwise they overflow uint64. Items from C
// with larger mantissa
{
{Number{1'000'000'000'000'000, -15},
Number{6'555'555'555'555'555, -29},
Number{false, 9'999'999'999'999'344'444ULL, -19, Number::Normalized{}},
__LINE__},
{Number{6'555'555'555'555'555, -29},
Number{1'000'000'000'000'000, -15},
Number{true, 9'999'999'999'999'344'444ULL, -19, Number::Normalized{}},
__LINE__},
{Number{1'000'000'000'000'000, -15},
Number{1'000'000'000'000'000, -15},
Number{0},
__LINE__},
{Number{1'000'000'000'000'000, -15},
Number{1'000'000'000'000'001, -15},
Number{-1'000'000'000'000'000, -30},
__LINE__},
{Number{1'000'000'000'000'001, -15},
Number{1'000'000'000'000'000, -15},
Number{1'000'000'000'000'000, -30},
__LINE__},
// Items from cSmall expanded for the larger mantissa
{Number{1'000'000'000'000'000'000, -18},
Number{6'555'555'555'555'555'555, -32},
Number{false, 9'999'999'999'999'344'444ULL, -19, Number::Normalized{}},
__LINE__},
{Number{6'555'555'555'555'555'555, -32},
Number{1'000'000'000'000'000'000, -18},
Number{true, 9'999'999'999'999'344'444ULL, -19, Number::Normalized{}},
__LINE__},
{Number{1'000'000'000'000'000'000, -18},
Number{1'000'000'000'000'000'000, -18},
Number{0},
__LINE__},
{Number{1'000'000'000'000'000'000, -18},
Number{1'000'000'000'000'000'001, -18},
Number{-1'000'000'000'000'000'000, -36},
__LINE__},
{Number{1'000'000'000'000'000'001, -18},
Number{1'000'000'000'000'000'000, -18},
Number{1'000'000'000'000'000'000, -36},
__LINE__},
{Number{Number::kMaxRep}, Number{6, -1}, Number{Number::kMaxRep - 1}, __LINE__},
});
// Note that items with extremely large mantissas need to be
// calculated, because otherwise they overflow uint64. Items from C
// with larger mantissa
auto const cLarge = std::to_array<Case>({
// Anything larger than kMaxRep rounds up
{Number{false, Number::kMaxRep + 1, 0, Number::Normalized{}},
Number{1, 0},
Number{(Number::kMaxRep / 10) + 1, 1},
__LINE__},
{Number{false, Number::kMaxRep + 1, 0, Number::Normalized{}},
Number{3, 0},
Number{Number::kMaxRep},
__LINE__},
{Number{false, Number::kMaxRep + 2, 0, Number::Normalized{}},
Number{1, 0},
Number{(Number::kMaxRep / 10) + 1, 1},
__LINE__},
{Number{false, Number::kMaxRep + 2, 0, Number::Normalized{}},
Number{3, 0},
Number{Number::kMaxRep},
__LINE__},
{power(2, 63), Number{3, 0}, Number{Number::kMaxRep}, __LINE__},
});
auto const cLarge330 = std::to_array<Case>({
// kMaxRep + 1 is below the half-way point, so it rounds down to kMaxRep when the Number
// is created.
{Number{false, Number::kMaxRep + 1, 0, Number::Normalized{}},
Number{1, 0},
Number{Number::kMaxRep - 1},
__LINE__},
{Number{false, Number::kMaxRep + 1, 0, Number::Normalized{}},
Number{3, 0},
Number{Number::kMaxRep - 3},
__LINE__},
// kMaxRepUp -1 is above the half-way point, so it rounds up to kMaxRepUp when the
// Number is created. Subtracting 1 from that rounds up again. A little non-intuitive.
{Number{false, Number::kMaxRepUp - 1, 0, Number::Normalized{}},
Number{1, 0},
Number{(Number::kMaxRep / 10) + 1, 1},
__LINE__},
// Subtracting 3 gets back down to kMaxRep
{Number{false, Number::kMaxRepUp - 1, 0, Number::Normalized{}},
Number{3, 0},
Number{Number::kMaxRep},
__LINE__},
// 2^63 is the same as kMaxRep+1
{power(2, 63), Number{3, 0}, Number{Number::kMaxRep - 3}, __LINE__},
});
auto test = [](auto const& c) {
for (auto const& [x, y, z, line] : c)
{
auto const result = x - y;
std::stringstream ss;
ss << x << " - " << y << " = " << result << ". Expected: " << z;
EXPECT_EQ(result, z) << ss.str() << " Line: " << line;
}
};
switch (scale)
{
case MantissaRange::MantissaScale::Small:
test(cSmall);
break;
case MantissaRange::MantissaScale::LargeLegacy:
case MantissaRange::MantissaScale::Large320:
test(cLargeAll);
test(cLarge);
break;
case MantissaRange::MantissaScale::Large330:
test(cLargeAll);
test(cLarge330);
break;
default:
ADD_FAILURE();
break;
}
}
}
TEST(NumberTest, mul)
{
for (auto const mantissaScale : MantissaRange::getAllScales())
{
NumberMantissaScaleGuard const sg(mantissaScale);
auto const scale = Number::getMantissaScale();
using Case = std::tuple<Number, Number, Number>;
auto test = [](auto const& c) {
for (auto const& [x, y, z] : c)
{
auto const result = x * y;
std::stringstream ss;
ss << x << " * " << y << " = " << result << ". Expected: " << z;
EXPECT_EQ(result, z) << ss.str();
}
};
auto tests = [&](auto const& cSmall, auto const& cLarge) {
if (scale == MantissaRange::MantissaScale::Small)
{
test(cSmall);
}
else
{
test(cLarge);
}
};
auto const maxMantissa = Number::maxMantissa();
SaveNumberRoundMode const save{Number::setround(Number::RoundingMode::ToNearest)};
{
auto const cSmall = std::to_array<Case>({
{Number{7}, Number{8}, Number{56}},
{Number{1414213562373095, -15},
Number{1414213562373095, -15},
Number{2000000000000000, -15}},
{Number{-1414213562373095, -15},
Number{1414213562373095, -15},
Number{-2000000000000000, -15}},
{Number{-1414213562373095, -15},
Number{-1414213562373095, -15},
Number{2000000000000000, -15}},
{Number{3214285714285706, -15},
Number{3111111111111119, -15},
Number{1000000000000000, -14}},
{Number{1000000000000000, -32768}, Number{1000000000000000, -32768}, Number{0}},
// Maximum mantissa range
{Number{9'999'999'999'999'999, 0},
Number{9'999'999'999'999'999, 0},
Number{9'999'999'999'999'998, 16}},
});
auto const cLarge = std::to_array<Case>({
// Note that items with extremely large mantissas need to be
// calculated, because otherwise they overflow uint64. Items
// from C with larger mantissa
{Number{7}, Number{8}, Number{56}},
{Number{1414213562373095, -15},
Number{1414213562373095, -15},
Number{1999999999999999862, -18}},
{Number{-1414213562373095, -15},
Number{1414213562373095, -15},
Number{-1999999999999999862, -18}},
{Number{-1414213562373095, -15},
Number{-1414213562373095, -15},
Number{1999999999999999862, -18}},
{Number{3214285714285706, -15},
Number{3111111111111119, -15},
Number{false, 9'999'999'999'999'999'579ULL, -18, Number::Normalized{}}},
{Number{1000000000000000000, -32768},
Number{1000000000000000000, -32768},
Number{0}},
// Items from cSmall expanded for the larger mantissa,
// except duplicates. Sadly, it looks like sqrt(2)^2 != 2
// with higher precision
{Number{1414213562373095049, -18},
Number{1414213562373095049, -18},
Number{2000000000000000001, -18}},
{Number{-1414213562373095048, -18},
Number{1414213562373095048, -18},
Number{-1999999999999999998, -18}},
{Number{-1414213562373095048, -18},
Number{-1414213562373095049, -18},
Number{1999999999999999999, -18}},
{Number{3214285714285714278, -18}, Number{3111111111111111119, -18}, Number{10, 0}},
// Maximum mantissa range - rounds up to 1e19
{Number{false, maxMantissa, 0, Number::Normalized{}},
Number{false, maxMantissa, 0, Number::Normalized{}},
Number{1, 38}},
// Maximum int64 range
{Number{Number::kMaxRep, 0},
Number{Number::kMaxRep, 0},
Number{85'070'591'730'234'615'85, 19}},
});
tests(cSmall, cLarge);
}
Number::setround(Number::RoundingMode::TowardsZero);
{
auto const cSmall = std::to_array<Case>(
{{Number{7}, Number{8}, Number{56}},
{Number{1414213562373095, -15},
Number{1414213562373095, -15},
Number{1999999999999999, -15}},
{Number{-1414213562373095, -15},
Number{1414213562373095, -15},
Number{-1999999999999999, -15}},
{Number{-1414213562373095, -15},
Number{-1414213562373095, -15},
Number{1999999999999999, -15}},
{Number{3214285714285706, -15},
Number{3111111111111119, -15},
Number{9999999999999999, -15}},
{Number{1000000000000000, -32768}, Number{1000000000000000, -32768}, Number{0}}});
auto const cLarge = std::to_array<Case>(
// Note that items with extremely large mantissas need to be
// calculated, because otherwise they overflow uint64. Items
// from C with larger mantissa
{
{Number{7}, Number{8}, Number{56}},
{Number{1414213562373095, -15},
Number{1414213562373095, -15},
Number{1999999999999999861, -18}},
{Number{-1414213562373095, -15},
Number{1414213562373095, -15},
Number{-1999999999999999861, -18}},
{Number{-1414213562373095, -15},
Number{-1414213562373095, -15},
Number{1999999999999999861, -18}},
{Number{3214285714285706, -15},
Number{3111111111111119, -15},
Number{false, 9999999999999999579ULL, -18, Number::Normalized{}}},
{Number{1000000000000000000, -32768},
Number{1000000000000000000, -32768},
Number{0}},
// Items from cSmall expanded for the larger mantissa,
// except duplicates. Sadly, it looks like sqrt(2)^2 != 2
// with higher precision
{Number{1414213562373095049, -18},
Number{1414213562373095049, -18},
Number{2, 0}},
{Number{-1414213562373095048, -18},
Number{1414213562373095048, -18},
Number{-1999999999999999997, -18}},
{Number{-1414213562373095048, -18},
Number{-1414213562373095049, -18},
Number{1999999999999999999, -18}},
{Number{3214285714285714278, -18},
Number{3111111111111111119, -18},
Number{10, 0}},
// Maximum mantissa range - rounds down to maxMantissa/10e1
// 99'999'999'999'999'999'800'000'000'000'000'000'100
{Number{false, maxMantissa, 0, Number::Normalized{}},
Number{false, maxMantissa, 0, Number::Normalized{}},
Number{false, (maxMantissa / 10) - 1, 20, Number::Normalized{}}},
// Maximum int64 range
// 85'070'591'730'234'615'847'396'907'784'232'501'249
{Number{Number::kMaxRep, 0},
Number{Number::kMaxRep, 0},
Number{85'070'591'730'234'615'84, 19}},
});
tests(cSmall, cLarge);
}
Number::setround(Number::RoundingMode::Downward);
{
auto const cSmall = std::to_array<Case>(
{{Number{7}, Number{8}, Number{56}},
{Number{1414213562373095, -15},
Number{1414213562373095, -15},
Number{1999999999999999, -15}},
{Number{-1414213562373095, -15},
Number{1414213562373095, -15},
Number{-2000000000000000, -15}},
{Number{-1414213562373095, -15},
Number{-1414213562373095, -15},
Number{1999999999999999, -15}},
{Number{3214285714285706, -15},
Number{3111111111111119, -15},
Number{9999999999999999, -15}},
{Number{1000000000000000, -32768}, Number{1000000000000000, -32768}, Number{0}}});
auto const cLarge = std::to_array<Case>(
// Note that items with extremely large mantissas need to be
// calculated, because otherwise they overflow uint64. Items
// from C with larger mantissa
{
{Number{7}, Number{8}, Number{56}},
{Number{1414213562373095, -15},
Number{1414213562373095, -15},
Number{1999999999999999861, -18}},
{Number{-1414213562373095, -15},
Number{1414213562373095, -15},
Number{-1999999999999999862, -18}},
{Number{-1414213562373095, -15},
Number{-1414213562373095, -15},
Number{1999999999999999861, -18}},
{Number{3214285714285706, -15},
Number{3111111111111119, -15},
Number{false, 9'999'999'999'999'999'579ULL, -18, Number::Normalized{}}},
{Number{1000000000000000000, -32768},
Number{1000000000000000000, -32768},
Number{0}},
// Items from cSmall expanded for the larger mantissa,
// except duplicates. Sadly, it looks like sqrt(2)^2 != 2
// with higher precision
{Number{1414213562373095049, -18},
Number{1414213562373095049, -18},
Number{2, 0}},
{Number{-1414213562373095048, -18},
Number{1414213562373095048, -18},
Number{-1999999999999999998, -18}},
{Number{-1414213562373095048, -18},
Number{-1414213562373095049, -18},
Number{1999999999999999999, -18}},
{Number{3214285714285714278, -18},
Number{3111111111111111119, -18},
Number{10, 0}},
// Maximum mantissa range - rounds down to maxMantissa/10e1
// 99'999'999'999'999'999'800'000'000'000'000'000'100
{Number{false, maxMantissa, 0, Number::Normalized{}},
Number{false, maxMantissa, 0, Number::Normalized{}},
Number{false, (maxMantissa / 10) - 1, 20, Number::Normalized{}}},
// Maximum int64 range
// 85'070'591'730'234'615'847'396'907'784'232'501'249
{Number{Number::kMaxRep, 0},
Number{Number::kMaxRep, 0},
Number{85'070'591'730'234'615'84, 19}},
});
tests(cSmall, cLarge);
}
Number::setround(Number::RoundingMode::Upward);
{
auto const cSmall = std::to_array<Case>(
{{Number{7}, Number{8}, Number{56}},
{Number{1414213562373095, -15},
Number{1414213562373095, -15},
Number{2000000000000000, -15}},
{Number{-1414213562373095, -15},
Number{1414213562373095, -15},
Number{-1999999999999999, -15}},
{Number{-1414213562373095, -15},
Number{-1414213562373095, -15},
Number{2000000000000000, -15}},
{Number{3214285714285706, -15},
Number{3111111111111119, -15},
Number{1000000000000000, -14}},
{Number{1000000000000000, -32768}, Number{1000000000000000, -32768}, Number{0}}});
auto const cLarge = std::to_array<Case>(
// Note that items with extremely large mantissas need to be
// calculated, because otherwise they overflow uint64. Items
// from C with larger mantissa
{
{Number{7}, Number{8}, Number{56}},
{Number{1414213562373095, -15},
Number{1414213562373095, -15},
Number{1999999999999999862, -18}},
{Number{-1414213562373095, -15},
Number{1414213562373095, -15},
Number{-1999999999999999861, -18}},
{Number{-1414213562373095, -15},
Number{-1414213562373095, -15},
Number{1999999999999999862, -18}},
{Number{3214285714285706, -15},
Number{3111111111111119, -15},
Number{999999999999999958, -17}},
{Number{1000000000000000000, -32768},
Number{1000000000000000000, -32768},
Number{0}},
// Items from cSmall expanded for the larger mantissa,
// except duplicates. Sadly, it looks like sqrt(2)^2 != 2
// with higher precision
{Number{1414213562373095049, -18},
Number{1414213562373095049, -18},
Number{2000000000000000001, -18}},
{Number{-1414213562373095048, -18},
Number{1414213562373095048, -18},
Number{-1999999999999999997, -18}},
{Number{-1414213562373095048, -18},
Number{-1414213562373095049, -18},
Number{2, 0}},
{Number{3214285714285714278, -18},
Number{3111111111111111119, -18},
Number{1000000000000000001, -17}},
// Maximum mantissa range - rounds up to minMantissa*10
// 1e19*1e19=1e38
{Number{false, maxMantissa, 0, Number::Normalized{}},
Number{false, maxMantissa, 0, Number::Normalized{}},
Number{1, 38}},
// Maximum int64 range
// 85'070'591'730'234'615'847'396'907'784'232'501'249
{Number{Number::kMaxRep, 0},
Number{Number::kMaxRep, 0},
Number{85'070'591'730'234'615'85, 19}},
});
tests(cSmall, cLarge);
}
{
bool caught = false;
try
{
Number{false, maxMantissa, 32768, Number::Normalized{}} *
Number{false, Number::minMantissa() * 5, 32767, Number::Normalized{}};
}
catch (std::overflow_error const&)
{
caught = true;
}
EXPECT_TRUE(caught);
}
}
}
TEST(NumberTest, div)
{
for (auto const mantissaScale : MantissaRange::getAllScales())
{
NumberMantissaScaleGuard const sg(mantissaScale);
auto const scale = Number::getMantissaScale();
using Case = std::tuple<Number, Number, Number>;
auto test = [](auto const& c) {
for (auto const& [x, y, z] : c)
{
auto const result = x / y;
std::stringstream ss;
ss << x << " / " << y << " = " << result << ". Expected: " << z;
EXPECT_EQ(result, z) << ss.str();
}
};
auto const maxMantissa = Number::maxMantissa();
auto tests = [&](auto const& cSmall, auto const& cLarge) {
if (scale == MantissaRange::MantissaScale::Small)
{
test(cSmall);
}
else
{
test(cLarge);
}
};
SaveNumberRoundMode const save{Number::setround(Number::RoundingMode::ToNearest)};
{
auto const cSmall = std::to_array<Case>(
{{Number{1}, Number{2}, Number{5, -1}},
{Number{1}, Number{10}, Number{1, -1}},
{Number{1}, Number{-10}, Number{-1, -1}},
{Number{0}, Number{100}, Number{0}},
{Number{1414213562373095, -10}, Number{1414213562373095, -10}, Number{1}},
{Number{9'999'999'999'999'999},
Number{1'000'000'000'000'000},
Number{9'999'999'999'999'999, -15}},
{Number{2}, Number{3}, Number{6'666'666'666'666'667, -16}},
{Number{-2}, Number{3}, Number{-6'666'666'666'666'667, -16}},
{Number{1}, Number{7}, Number{1'428'571'428'571'428, -16}}});
auto const cLarge = std::to_array<Case>(
// Note that items with extremely large mantissas need to be
// calculated, because otherwise they overflow uint64. Items
// from C with larger mantissa
{{Number{1}, Number{2}, Number{5, -1}},
{Number{1}, Number{10}, Number{1, -1}},
{Number{1}, Number{-10}, Number{-1, -1}},
{Number{0}, Number{100}, Number{0}},
{Number{1414213562373095, -10}, Number{1414213562373095, -10}, Number{1}},
{Number{9'999'999'999'999'999},
Number{1'000'000'000'000'000},
Number{9'999'999'999'999'999, -15}},
{Number{2}, Number{3}, Number{6'666'666'666'666'666'667, -19}},
{Number{-2}, Number{3}, Number{-6'666'666'666'666'666'667, -19}},
{Number{1}, Number{7}, Number{1'428'571'428'571'428'571, -19}},
// Items from cSmall expanded for the larger mantissa, except
// duplicates.
{Number{1414213562373095049, -13}, Number{1414213562373095049, -13}, Number{1}},
{Number{false, maxMantissa, 0, Number::Normalized{}},
Number{1'000'000'000'000'000'000},
Number{false, maxMantissa, -18, Number::Normalized{}}}});
tests(cSmall, cLarge);
}
Number::setround(Number::RoundingMode::TowardsZero);
{
auto const cSmall = std::to_array<Case>(
{{Number{1}, Number{2}, Number{5, -1}},
{Number{1}, Number{10}, Number{1, -1}},
{Number{1}, Number{-10}, Number{-1, -1}},
{Number{0}, Number{100}, Number{0}},
{Number{1414213562373095, -10}, Number{1414213562373095, -10}, Number{1}},
{Number{9'999'999'999'999'999},
Number{1'000'000'000'000'000},
Number{9'999'999'999'999'999, -15}},
{Number{2}, Number{3}, Number{6'666'666'666'666'666, -16}},
{Number{-2}, Number{3}, Number{-6'666'666'666'666'666, -16}},
{Number{1}, Number{7}, Number{1'428'571'428'571'428, -16}}});
auto const cLarge = std::to_array<Case>(
// Note that items with extremely large mantissas need to be
// calculated, because otherwise they overflow uint64. Items
// from C with larger mantissa
{{Number{1}, Number{2}, Number{5, -1}},
{Number{1}, Number{10}, Number{1, -1}},
{Number{1}, Number{-10}, Number{-1, -1}},
{Number{0}, Number{100}, Number{0}},
{Number{1414213562373095, -10}, Number{1414213562373095, -10}, Number{1}},
{Number{9'999'999'999'999'999},
Number{1'000'000'000'000'000},
Number{9'999'999'999'999'999, -15}},
{Number{2}, Number{3}, Number{6'666'666'666'666'666'666, -19}},
{Number{-2}, Number{3}, Number{-6'666'666'666'666'666'666, -19}},
{Number{1}, Number{7}, Number{1'428'571'428'571'428'571, -19}},
// Items from cSmall expanded for the larger mantissa, except
// duplicates.
{Number{1414213562373095049, -13}, Number{1414213562373095049, -13}, Number{1}},
{Number{false, maxMantissa, 0, Number::Normalized{}},
Number{1'000'000'000'000'000'000},
Number{false, maxMantissa, -18, Number::Normalized{}}}});
tests(cSmall, cLarge);
}
Number::setround(Number::RoundingMode::Downward);
{
auto const cSmall = std::to_array<Case>(
{{Number{1}, Number{2}, Number{5, -1}},
{Number{1}, Number{10}, Number{1, -1}},
{Number{1}, Number{-10}, Number{-1, -1}},
{Number{0}, Number{100}, Number{0}},
{Number{1414213562373095, -10}, Number{1414213562373095, -10}, Number{1}},
{Number{9'999'999'999'999'999},
Number{1'000'000'000'000'000},
Number{9'999'999'999'999'999, -15}},
{Number{2}, Number{3}, Number{6'666'666'666'666'666, -16}},
{Number{-2}, Number{3}, Number{-6'666'666'666'666'667, -16}},
{Number{1}, Number{7}, Number{1'428'571'428'571'428, -16}}});
auto const cLarge = std::to_array<Case>(
// Note that items with extremely large mantissas need to be
// calculated, because otherwise they overflow uint64. Items
// from C with larger mantissa
{{Number{1}, Number{2}, Number{5, -1}},
{Number{1}, Number{10}, Number{1, -1}},
{Number{1}, Number{-10}, Number{-1, -1}},
{Number{0}, Number{100}, Number{0}},
{Number{1414213562373095, -10}, Number{1414213562373095, -10}, Number{1}},
{Number{9'999'999'999'999'999},
Number{1'000'000'000'000'000},
Number{9'999'999'999'999'999, -15}},
{Number{2}, Number{3}, Number{6'666'666'666'666'666'666, -19}},
{Number{-2}, Number{3}, Number{-6'666'666'666'666'666'667, -19}},
{Number{1}, Number{7}, Number{1'428'571'428'571'428'571, -19}},
// Items from cSmall expanded for the larger mantissa, except
// duplicates.
{Number{1414213562373095049, -13}, Number{1414213562373095049, -13}, Number{1}},
{Number{false, maxMantissa, 0, Number::Normalized{}},
Number{1'000'000'000'000'000'000},
Number{false, maxMantissa, -18, Number::Normalized{}}}});
tests(cSmall, cLarge);
}
Number::setround(Number::RoundingMode::Upward);
{
auto const cSmall = std::to_array<Case>(
{{Number{1}, Number{2}, Number{5, -1}},
{Number{1}, Number{10}, Number{1, -1}},
{Number{1}, Number{-10}, Number{-1, -1}},
{Number{0}, Number{100}, Number{0}},
{Number{1414213562373095, -10}, Number{1414213562373095, -10}, Number{1}},
{Number{9'999'999'999'999'999},
Number{1'000'000'000'000'000},
Number{9'999'999'999'999'999, -15}},
{Number{2}, Number{3}, Number{6'666'666'666'666'667, -16}},
{Number{-2}, Number{3}, Number{-6'666'666'666'666'666, -16}},
{Number{1}, Number{7}, Number{1'428'571'428'571'429, -16}}});
auto const cLarge = std::to_array<Case>(
// Note that items with extremely large mantissas need to be
// calculated, because otherwise they overflow uint64. Items
// from C with larger mantissa
{{Number{1}, Number{2}, Number{5, -1}},
{Number{1}, Number{10}, Number{1, -1}},
{Number{1}, Number{-10}, Number{-1, -1}},
{Number{0}, Number{100}, Number{0}},
{Number{1414213562373095, -10}, Number{1414213562373095, -10}, Number{1}},
{Number{9'999'999'999'999'999},
Number{1'000'000'000'000'000},
Number{9'999'999'999'999'999, -15}},
{Number{2}, Number{3}, Number{6'666'666'666'666'666'667, -19}},
{Number{-2}, Number{3}, Number{-6'666'666'666'666'666'666, -19}},
{Number{1}, Number{7}, Number{1'428'571'428'571'428'572, -19}},
// Items from cSmall expanded for the larger mantissa, except
// duplicates.
{Number{1414213562373095049, -13}, Number{1414213562373095049, -13}, Number{1}},
{Number{false, maxMantissa, 0, Number::Normalized{}},
Number{1'000'000'000'000'000'000},
Number{false, maxMantissa, -18, Number::Normalized{}}}});
tests(cSmall, cLarge);
}
bool caught = false;
try
{
Number{1000000000000000, -15} / Number{0};
}
catch (std::overflow_error const&)
{
caught = true;
}
EXPECT_TRUE(caught);
}
}
TEST(NumberTest, root)
{
for (auto const mantissaScale : MantissaRange::getAllScales())
{
NumberMantissaScaleGuard const sg(mantissaScale);
using Case = std::tuple<Number, unsigned, Number>;
auto test = [](auto const& c) {
for (auto const& [x, y, z] : c)
{
auto const result = root(x, y);
std::stringstream ss;
ss << "root(" << x << ", " << y << ") = " << result << ". Expected: " << z;
EXPECT_EQ(result, z) << ss.str();
}
};
auto const cSmall = std::to_array<Case>(
{{Number{2}, 2, Number{1414213562373095049, -18}},
{Number{2'000'000}, 2, Number{1414213562373095049, -15}},
{Number{2, -30}, 2, Number{1414213562373095049, -33}},
{Number{-27}, 3, Number{-3}},
{Number{1}, 5, Number{1}},
{Number{-1}, 0, Number{1}},
{Number{5, -1}, 0, Number{0}},
{Number{0}, 5, Number{0}},
{Number{5625, -4}, 2, Number{75, -2}}});
auto const cLarge = std::to_array<Case>({
{Number{false, Number::maxMantissa() - 9, -1, Number::Normalized{}},
2,
Number{false, 999'999'999'999'999'999, -9, Number::Normalized{}}},
{Number{false, Number::maxMantissa() - 9, 0, Number::Normalized{}},
2,
Number{false, 3'162'277'660'168'379'330, -9, Number::Normalized{}}},
{Number{Number::kMaxRep},
2,
Number{false, 3'037'000'499'976049692, -9, Number::Normalized{}}},
{Number{Number::kMaxRep},
4,
Number{false, 55'108'98747006743627, -14, Number::Normalized{}}},
});
test(cSmall);
if (Number::getMantissaScale() != MantissaRange::MantissaScale::Small)
{
NumberRoundModeGuard const mg(Number::RoundingMode::TowardsZero);
test(cLarge);
}
bool caught = false;
try
{
(void)root(Number{-2}, 0);
}
catch (std::overflow_error const&)
{
caught = true;
}
EXPECT_TRUE(caught);
caught = false;
try
{
(void)root(Number{-2}, 4);
}
catch (std::overflow_error const&)
{
caught = true;
}
EXPECT_TRUE(caught);
}
}
TEST(NumberTest, root2)
{
for (auto const mantissaScale : MantissaRange::getAllScales())
{
NumberMantissaScaleGuard const sg(mantissaScale);
auto test = [](auto const& c) {
for (auto const& x : c)
{
auto const expected = root(x, 2);
auto const result = root2(x);
std::stringstream ss;
ss << "root2(" << x << ") = " << result << ". Expected: " << expected;
EXPECT_EQ(result, expected) << ss.str();
}
};
auto const cSmall = std::to_array<Number>({
Number{2},
Number{2'000'000},
Number{2, -30},
Number{27},
Number{1},
Number{5, -1},
Number{0},
Number{5625, -4},
Number{Number::kMaxRep},
});
test(cSmall);
bool caught = false;
try
{
(void)root2(Number{-2});
}
catch (std::overflow_error const&)
{
caught = true;
}
EXPECT_TRUE(caught);
}
}
TEST(NumberTest, power1)
{
for (auto const mantissaScale : MantissaRange::getAllScales())
{
NumberMantissaScaleGuard const sg(mantissaScale);
using Case = std::tuple<Number, unsigned, Number>;
Case const c[]{
{Number{64}, 0, Number{1}},
{Number{64}, 1, Number{64}},
{Number{64}, 2, Number{4096}},
{Number{-64}, 2, Number{4096}},
{Number{64}, 3, Number{262144}},
{Number{-64}, 3, Number{-262144}},
{Number{64}, 11, Number{false, 7378697629483820646ULL, 1, Number::Normalized{}}},
{Number{-64}, 11, Number{true, 7378697629483820646ULL, 1, Number::Normalized{}}}};
for (auto const& [x, y, z] : c)
EXPECT_EQ(power(x, y), z);
}
}
TEST(NumberTest, power2)
{
for (auto const mantissaScale : MantissaRange::getAllScales())
{
NumberMantissaScaleGuard const sg(mantissaScale);
using Case = std::tuple<Number, unsigned, unsigned, Number>;
Case const c[]{
{Number{1}, 3, 7, Number{1}},
{Number{-1}, 1, 0, Number{1}},
{Number{-1, -1}, 1, 0, Number{0}},
{Number{16}, 0, 5, Number{1}},
{Number{34}, 3, 3, Number{34}},
{Number{4}, 3, 2, Number{8}}};
for (auto const& [x, n, d, z] : c)
EXPECT_EQ(power(x, n, d), z);
bool caught = false;
try
{
(void)power(Number{7}, 0, 0);
}
catch (std::overflow_error const&)
{
caught = true;
}
EXPECT_TRUE(caught);
caught = false;
try
{
(void)power(Number{7}, 1, 0);
}
catch (std::overflow_error const&)
{
caught = true;
}
EXPECT_TRUE(caught);
caught = false;
try
{
(void)power(Number{-1, -1}, 3, 2);
}
catch (std::overflow_error const&)
{
caught = true;
}
EXPECT_TRUE(caught);
}
}
TEST(NumberTest, conversions)
{
for (auto const mantissaScale : MantissaRange::getAllScales())
{
NumberMantissaScaleGuard const sg(mantissaScale);
IOUAmount const x{5, 6};
Number const y = x;
EXPECT_EQ(y, (Number{5, 6}));
IOUAmount const z{y};
EXPECT_EQ(x, z);
XRPAmount const xrp{500};
STAmount const st = xrp;
Number const n = st;
EXPECT_EQ(XRPAmount{n}, xrp);
IOUAmount const x0{0, 0};
Number const y0 = x0;
EXPECT_EQ(y0, Number{0});
IOUAmount const z0{y0};
EXPECT_EQ(x0, z0);
XRPAmount const xrp0{0};
Number const n0 = xrp0;
EXPECT_EQ(n0, Number{0});
XRPAmount const xrp1{n0}; // NOLINT misc-confusable-identifiers
EXPECT_EQ(xrp1, xrp0);
}
}
TEST(NumberTest, to_integer)
{
for (auto const mantissaScale : MantissaRange::getAllScales())
{
NumberMantissaScaleGuard const sg(mantissaScale);
using Case = std::tuple<Number, std::int64_t>;
SaveNumberRoundMode const save{Number::setround(Number::RoundingMode::ToNearest)};
{
Case const c[]{
{Number{0}, 0},
{Number{1}, 1},
{Number{2}, 2},
{Number{3}, 3},
{Number{-1}, -1},
{Number{-2}, -2},
{Number{-3}, -3},
{Number{10}, 10},
{Number{99}, 99},
{Number{1155}, 1155},
{Number{9'999'999'999'999'999, 0}, 9'999'999'999'999'999},
{Number{9'999'999'999'999'999, 1}, 99'999'999'999'999'990},
{Number{9'999'999'999'999'999, 2}, 999'999'999'999'999'900},
{Number{-9'999'999'999'999'999, 2}, -999'999'999'999'999'900},
{Number{15, -1}, 2},
{Number{14, -1}, 1},
{Number{16, -1}, 2},
{Number{25, -1}, 2},
{Number{6, -1}, 1},
{Number{5, -1}, 0},
{Number{4, -1}, 0},
{Number{-15, -1}, -2},
{Number{-14, -1}, -1},
{Number{-16, -1}, -2},
{Number{-25, -1}, -2},
{Number{-6, -1}, -1},
{Number{-5, -1}, 0},
{Number{-4, -1}, 0}};
for (auto const& [x, y] : c)
{
auto j = static_cast<std::int64_t>(x);
EXPECT_EQ(j, y);
}
}
auto prevMode = Number::setround(Number::RoundingMode::TowardsZero);
EXPECT_EQ(prevMode, Number::RoundingMode::ToNearest);
{
Case const c[]{
{Number{0}, 0},
{Number{1}, 1},
{Number{2}, 2},
{Number{3}, 3},
{Number{-1}, -1},
{Number{-2}, -2},
{Number{-3}, -3},
{Number{10}, 10},
{Number{99}, 99},
{Number{1155}, 1155},
{Number{9'999'999'999'999'999, 0}, 9'999'999'999'999'999},
{Number{9'999'999'999'999'999, 1}, 99'999'999'999'999'990},
{Number{9'999'999'999'999'999, 2}, 999'999'999'999'999'900},
{Number{-9'999'999'999'999'999, 2}, -999'999'999'999'999'900},
{Number{15, -1}, 1},
{Number{14, -1}, 1},
{Number{16, -1}, 1},
{Number{25, -1}, 2},
{Number{6, -1}, 0},
{Number{5, -1}, 0},
{Number{4, -1}, 0},
{Number{-15, -1}, -1},
{Number{-14, -1}, -1},
{Number{-16, -1}, -1},
{Number{-25, -1}, -2},
{Number{-6, -1}, 0},
{Number{-5, -1}, 0},
{Number{-4, -1}, 0}};
for (auto const& [x, y] : c)
{
auto j = static_cast<std::int64_t>(x);
EXPECT_EQ(j, y);
}
}
prevMode = Number::setround(Number::RoundingMode::Downward);
EXPECT_EQ(prevMode, Number::RoundingMode::TowardsZero);
{
Case const c[]{
{Number{0}, 0},
{Number{1}, 1},
{Number{2}, 2},
{Number{3}, 3},
{Number{-1}, -1},
{Number{-2}, -2},
{Number{-3}, -3},
{Number{10}, 10},
{Number{99}, 99},
{Number{1155}, 1155},
{Number{9'999'999'999'999'999, 0}, 9'999'999'999'999'999},
{Number{9'999'999'999'999'999, 1}, 99'999'999'999'999'990},
{Number{9'999'999'999'999'999, 2}, 999'999'999'999'999'900},
{Number{-9'999'999'999'999'999, 2}, -999'999'999'999'999'900},
{Number{15, -1}, 1},
{Number{14, -1}, 1},
{Number{16, -1}, 1},
{Number{25, -1}, 2},
{Number{6, -1}, 0},
{Number{5, -1}, 0},
{Number{4, -1}, 0},
{Number{-15, -1}, -2},
{Number{-14, -1}, -2},
{Number{-16, -1}, -2},
{Number{-25, -1}, -3},
{Number{-6, -1}, -1},
{Number{-5, -1}, -1},
{Number{-4, -1}, -1}};
for (auto const& [x, y] : c)
{
auto j = static_cast<std::int64_t>(x);
EXPECT_EQ(j, y);
}
}
prevMode = Number::setround(Number::RoundingMode::Upward);
EXPECT_EQ(prevMode, Number::RoundingMode::Downward);
{
Case const c[]{
{Number{0}, 0},
{Number{1}, 1},
{Number{2}, 2},
{Number{3}, 3},
{Number{-1}, -1},
{Number{-2}, -2},
{Number{-3}, -3},
{Number{10}, 10},
{Number{99}, 99},
{Number{1155}, 1155},
{Number{9'999'999'999'999'999, 0}, 9'999'999'999'999'999},
{Number{9'999'999'999'999'999, 1}, 99'999'999'999'999'990},
{Number{9'999'999'999'999'999, 2}, 999'999'999'999'999'900},
{Number{-9'999'999'999'999'999, 2}, -999'999'999'999'999'900},
{Number{15, -1}, 2},
{Number{14, -1}, 2},
{Number{16, -1}, 2},
{Number{25, -1}, 3},
{Number{6, -1}, 1},
{Number{5, -1}, 1},
{Number{4, -1}, 1},
{Number{-15, -1}, -1},
{Number{-14, -1}, -1},
{Number{-16, -1}, -1},
{Number{-25, -1}, -2},
{Number{-6, -1}, 0},
{Number{-5, -1}, 0},
{Number{-4, -1}, 0}};
for (auto const& [x, y] : c)
{
auto j = static_cast<std::int64_t>(x);
EXPECT_EQ(j, y);
}
}
bool caught = false;
try
{
(void)static_cast<std::int64_t>(Number{9223372036854776, 3});
}
catch (std::overflow_error const&)
{
caught = true;
}
EXPECT_TRUE(caught);
}
}
TEST(NumberTest, squelch)
{
for (auto const mantissaScale : MantissaRange::getAllScales())
{
NumberMantissaScaleGuard const sg(mantissaScale);
Number const limit{1, -6};
EXPECT_EQ(squelch(Number{2, -6}, limit), (Number{2, -6}));
EXPECT_EQ(squelch(Number{1, -6}, limit), (Number{1, -6}));
EXPECT_EQ(squelch(Number{9, -7}, limit), Number{0});
EXPECT_EQ(squelch(Number{-2, -6}, limit), (Number{-2, -6}));
EXPECT_EQ(squelch(Number{-1, -6}, limit), (Number{-1, -6}));
EXPECT_EQ(squelch(Number{-9, -7}, limit), Number{0});
}
}
TEST(NumberTest, to_string)
{
for (auto const mantissaScale : MantissaRange::getAllScales())
{
NumberMantissaScaleGuard const sg(mantissaScale);
auto const scale = Number::getMantissaScale();
auto test = [](Number const& n, std::string const& expected, int line) {
auto const result = to_string(n);
std::stringstream ss;
ss << "to_string(" << result << "). Expected: " << expected;
EXPECT_EQ(result, expected) << ss.str() << " Line: " << line;
};
test(Number(-2, 0), "-2", __LINE__);
test(Number(0, 0), "0", __LINE__);
test(Number(2, 0), "2", __LINE__);
test(Number(25, -3), "0.025", __LINE__);
test(Number(-25, -3), "-0.025", __LINE__);
test(Number(25, 1), "250", __LINE__);
test(Number(-25, 1), "-250", __LINE__);
test(Number(2, 20), "2e20", __LINE__);
test(Number(-2, -20), "-2e-20", __LINE__);
// Test the edges
// ((exponent < -(25)) || (exponent > -(5)))))
// or ((exponent < -(28)) || (exponent > -(8)))))
test(Number(2, -10), "0.0000000002", __LINE__);
test(Number(2, -11), "2e-11", __LINE__);
test(Number(-2, 10), "-20000000000", __LINE__);
test(Number(-2, 11), "-2e11", __LINE__);
test(Number(-2, 11) - 1, "-200000000001", __LINE__);
switch (scale)
{
case MantissaRange::MantissaScale::Small:
test(Number::min(), "1e-32753", __LINE__);
test(Number::max(), "9999999999999999e32768", __LINE__);
test(Number::lowest(), "-9999999999999999e32768", __LINE__);
{
NumberRoundModeGuard const mg(Number::RoundingMode::TowardsZero);
auto const maxMantissa = Number::maxMantissa();
EXPECT_EQ(maxMantissa, 9'999'999'999'999'999);
test(
Number{false, (maxMantissa * 1000) + 999, -3, Number::Normalized()},
"9999999999999999",
__LINE__);
test(
Number{true, (maxMantissa * 1000) + 999, -3, Number::Normalized()},
"-9999999999999999",
__LINE__);
test(
Number{std::numeric_limits<std::int64_t>::max(), -3},
"9223372036854775",
__LINE__);
test(
-(Number{std::numeric_limits<std::int64_t>::max(), -3}),
"-9223372036854775",
__LINE__);
test(
Number{std::numeric_limits<std::int64_t>::min(), 0},
"-9223372036854775e3",
__LINE__);
test(
-(Number{std::numeric_limits<std::int64_t>::min(), 0}),
"9223372036854775e3",
__LINE__);
}
break;
default:
// Test the edges
// ((exponent < -(28)) || (exponent > -(8)))))
test(Number::min(), "1e-32750", __LINE__);
test(Number::max(), "9223372036854775807e32768", __LINE__);
test(Number::lowest(), "-9223372036854775807e32768", __LINE__);
{
NumberRoundModeGuard const mg(Number::RoundingMode::TowardsZero);
auto const maxMantissa = Number::maxMantissa();
EXPECT_EQ(maxMantissa, 9'999'999'999'999'999'999ULL);
test(
Number{false, maxMantissa, 0, Number::Normalized{}},
"9999999999999999990",
__LINE__);
test(
Number{true, maxMantissa, 0, Number::Normalized{}},
"-9999999999999999990",
__LINE__);
test(
Number{std::numeric_limits<std::int64_t>::max(), 0},
"9223372036854775807",
__LINE__);
test(
-(Number{std::numeric_limits<std::int64_t>::max(), 0}),
"-9223372036854775807",
__LINE__);
switch (scale)
{
case MantissaRange::MantissaScale::Large330:
// Because the absolute value of min() is larger than max(), it
// will be rounded down toward max()
test(
Number{std::numeric_limits<std::int64_t>::min(), 0},
"-9223372036854775807",
__LINE__);
test(
-(Number{std::numeric_limits<std::int64_t>::min(), 0}),
"9223372036854775807",
__LINE__);
break;
default:
// Because the absolute value of min() is larger than max(), it
// will be scaled down to fit under max(). Since we're
// rounding towards zero, the 8 at the end is dropped.
test(
Number{std::numeric_limits<std::int64_t>::min(), 0},
"-9223372036854775800",
__LINE__);
test(
-(Number{std::numeric_limits<std::int64_t>::min(), 0}),
"9223372036854775800",
__LINE__);
break;
}
}
switch (scale)
{
case MantissaRange::MantissaScale::Large330:
// Rounding to nearest, since the mantissa is below the halfway point from
// kMaxRep to kMaxRepUp, it will be rounded down to kMaxRep
test(
Number{std::numeric_limits<std::int64_t>::max(), 0} + 1,
"9223372036854775807",
__LINE__);
test(
-(Number{std::numeric_limits<std::int64_t>::max(), 0} + 1),
"-9223372036854775807",
__LINE__);
break;
default:
// Rounding to nearest, since the mantissa is bigger than kMaxRep, the 8
// will be dropped, and since that is bigger than 5, the result will be
// rounded up from 0 to 1.
test(
Number{std::numeric_limits<std::int64_t>::max(), 0} + 1,
"9223372036854775810",
__LINE__);
test(
-(Number{std::numeric_limits<std::int64_t>::max(), 0} + 1),
"-9223372036854775810",
__LINE__);
break;
}
// Rounding to nearest, will be rounded up to kMaxRepUp, but for different reasons
// depending on the scale. If older than "Large", it rounds up for the same reason
// "+1" rounds up. For "Large", since the mantissa is above the halfway point from
// kMaxRep to kMaxRepUp, it will be rounded up to kMaxRepUp.
test(
Number{std::numeric_limits<std::int64_t>::max(), 0} + 2,
"9223372036854775810",
__LINE__);
test(
-(Number{std::numeric_limits<std::int64_t>::max(), 0} + 2),
"-9223372036854775810",
__LINE__);
break;
}
}
}
TEST(NumberTest, relationals)
{
for (auto const mantissaScale : MantissaRange::getAllScales())
{
NumberMantissaScaleGuard const sg(mantissaScale);
{
auto test = [](auto const& nums) {
EXPECT_TRUE(std::ranges::is_sorted(nums));
for (auto iter1 = nums.begin(); iter1 != nums.end(); ++iter1)
{
auto iter2 = iter1;
for (++iter2; iter2 != nums.end(); ++iter2)
{
Number const& smaller = *iter1;
Number const& larger = *iter2;
std::stringstream ss;
ss << smaller << " < " << larger;
auto const str = ss.str();
// The ==/!= operators use a completely different code path than <, etc.
// This helps detect a breakage in one but not the other. It also helps
// verify that the values are being ordered correctly.
EXPECT_TRUE(smaller != larger) << str << " (!=)";
EXPECT_FALSE(smaller == larger) << str << " (==)";
// true results using operator< and derived operators
EXPECT_TRUE(smaller < larger) << str << " (<)";
EXPECT_TRUE(larger > smaller) << str << " (>)";
EXPECT_TRUE(larger >= smaller) << str << " (>=)";
EXPECT_TRUE(smaller <= larger) << str << " (<=)";
// false results using operator< and derived operators
EXPECT_FALSE(larger < smaller) << str << " (! <)";
EXPECT_FALSE(smaller > larger) << str << " (! >)";
EXPECT_FALSE(smaller >= larger) << str << " (! >=)";
EXPECT_FALSE(larger <= smaller) << str << " (! <=)";
}
}
};
auto const intNums = []() {
// Inequality test cases are built from a list of sorted integers
auto const values =
std::to_array<int>({-100, -50, -20, -10, -1, 0, 1, 10, 20, 50, 100});
// Check this list is sorted before converting it to Numbers.
// That way if any of the other tests fail, we know it's because of code and not the
// source data.
EXPECT_TRUE(std::ranges::is_sorted(values));
std::vector<Number> result;
result.reserve(values.size());
for (auto const v : values)
result.emplace_back(v);
return result;
}();
auto const otherNums = std::to_array<Number>({
Number{-5, 100},
Number{-1, 100},
Number{-7, -10},
Number{-2, -10},
Number{0},
Number{2, -10},
Number{7, -10},
Number{1, 100},
Number{5, 100},
});
test(intNums);
test(otherNums);
}
{
// Equality test cases are <Number, __LINE__>. Number will be compared against itself
using Case = std::pair<Number, int>;
auto const c = std::to_array<Case>({
{700, __LINE__},
{50, __LINE__},
{1, __LINE__},
{0, __LINE__},
{-1, __LINE__},
{-30, __LINE__},
{-600, __LINE__},
});
for (auto const& [n, line] : c)
{
auto const str = to_string(n);
auto const location =
std::string{" ("} + __FILE__ + ":" + std::to_string(line) + ")";
// NOLINTBEGIN(misc-redundant-expression) Explicitly testing operators with
// equivalent values
EXPECT_TRUE(n == n) << str << " ==" << location;
EXPECT_FALSE(n != n) << str << " !=" << location;
EXPECT_FALSE(n < n) << str << " <" << location;
EXPECT_FALSE(n > n) << str << " >" << location;
EXPECT_TRUE(n >= n) << str << " >=" << location;
EXPECT_TRUE(n <= n) << str << " <=" << location;
// NOLINTEND(misc-redundant-expression)
}
}
}
}
TEST(NumberTest, stream)
{
for (auto const mantissaScale : MantissaRange::getAllScales())
{
NumberMantissaScaleGuard const sg(mantissaScale);
Number const x{100};
std::ostringstream os;
os << x;
EXPECT_EQ((os.str()), (to_string(x)));
}
}
TEST(NumberTest, inc_dec)
{
for (auto const mantissaScale : MantissaRange::getAllScales())
{
NumberMantissaScaleGuard const sg(mantissaScale);
Number x{100};
Number const y = +x;
EXPECT_EQ((x), (y));
EXPECT_EQ((x++), (y));
EXPECT_EQ((x), (Number{101}));
EXPECT_EQ((x--), (Number{101}));
EXPECT_EQ((x), (y));
}
}
TEST(NumberTest, to_st_amount)
{
for (auto const mantissaScale : MantissaRange::getAllScales())
{
NumberMantissaScaleGuard const sg(mantissaScale);
Issue const issue;
Number const n{7'518'783'80596, -5};
SaveNumberRoundMode const save{Number::setround(Number::RoundingMode::ToNearest)};
auto res2 = STAmount{issue, n};
EXPECT_EQ((res2), (STAmount{7518784}));
Number::setround(Number::RoundingMode::TowardsZero);
res2 = STAmount{issue, n};
EXPECT_EQ((res2), (STAmount{7518783}));
Number::setround(Number::RoundingMode::Downward);
res2 = STAmount{issue, n};
EXPECT_EQ((res2), (STAmount{7518783}));
Number::setround(Number::RoundingMode::Upward);
res2 = STAmount{issue, n};
EXPECT_EQ((res2), (STAmount{7518784}));
}
}
TEST(NumberTest, truncate)
{
for (auto const mantissaScale : MantissaRange::getAllScales())
{
NumberMantissaScaleGuard const sg(mantissaScale);
EXPECT_EQ((Number(25, +1).truncate()), (Number(250, 0)));
EXPECT_EQ((Number(25, 0).truncate()), (Number(25, 0)));
EXPECT_EQ((Number(25, -1).truncate()), (Number(2, 0)));
EXPECT_EQ((Number(25, -2).truncate()), (Number(0, 0)));
EXPECT_EQ((Number(99, -2).truncate()), (Number(0, 0)));
EXPECT_EQ((Number(-25, +1).truncate()), (Number(-250, 0)));
EXPECT_EQ((Number(-25, 0).truncate()), (Number(-25, 0)));
EXPECT_EQ((Number(-25, -1).truncate()), (Number(-2, 0)));
EXPECT_EQ((Number(-25, -2).truncate()), (Number(0, 0)));
EXPECT_EQ((Number(-99, -2).truncate()), (Number(0, 0)));
EXPECT_EQ((Number(0, 0).truncate()), (Number(0, 0)));
EXPECT_EQ((Number(0, 30000).truncate()), (Number(0, 0)));
EXPECT_EQ((Number(0, -30000).truncate()), (Number(0, 0)));
EXPECT_EQ((Number(100, -30000).truncate()), (Number(0, 0)));
EXPECT_EQ((Number(100, -30000).truncate()), (Number(0, 0)));
EXPECT_EQ((Number(-100, -30000).truncate()), (Number(0, 0)));
EXPECT_EQ((Number(-100, -30000).truncate()), (Number(0, 0)));
}
}
TEST(NumberTest, rounding)
{
for (auto const mantissaScale : MantissaRange::getAllScales())
{
NumberMantissaScaleGuard const sg(mantissaScale);
// Test that rounding works as expected.
using NumberRoundings = std::map<Number::RoundingMode, std::int64_t>;
std::map<Number, NumberRoundings> const expected{
// Positive numbers
{Number{13, -1},
{{Number::RoundingMode::ToNearest, 1},
{Number::RoundingMode::TowardsZero, 1},
{Number::RoundingMode::Downward, 1},
{Number::RoundingMode::Upward, 2}}},
{Number{23, -1},
{{Number::RoundingMode::ToNearest, 2},
{Number::RoundingMode::TowardsZero, 2},
{Number::RoundingMode::Downward, 2},
{Number::RoundingMode::Upward, 3}}},
{Number{15, -1},
{{Number::RoundingMode::ToNearest, 2},
{Number::RoundingMode::TowardsZero, 1},
{Number::RoundingMode::Downward, 1},
{Number::RoundingMode::Upward, 2}}},
{Number{25, -1},
{{Number::RoundingMode::ToNearest, 2},
{Number::RoundingMode::TowardsZero, 2},
{Number::RoundingMode::Downward, 2},
{Number::RoundingMode::Upward, 3}}},
{Number{152, -2},
{{Number::RoundingMode::ToNearest, 2},
{Number::RoundingMode::TowardsZero, 1},
{Number::RoundingMode::Downward, 1},
{Number::RoundingMode::Upward, 2}}},
{Number{252, -2},
{{Number::RoundingMode::ToNearest, 3},
{Number::RoundingMode::TowardsZero, 2},
{Number::RoundingMode::Downward, 2},
{Number::RoundingMode::Upward, 3}}},
{Number{17, -1},
{{Number::RoundingMode::ToNearest, 2},
{Number::RoundingMode::TowardsZero, 1},
{Number::RoundingMode::Downward, 1},
{Number::RoundingMode::Upward, 2}}},
{Number{27, -1},
{{Number::RoundingMode::ToNearest, 3},
{Number::RoundingMode::TowardsZero, 2},
{Number::RoundingMode::Downward, 2},
{Number::RoundingMode::Upward, 3}}},
// Negative numbers
{Number{-13, -1},
{{Number::RoundingMode::ToNearest, -1},
{Number::RoundingMode::TowardsZero, -1},
{Number::RoundingMode::Downward, -2},
{Number::RoundingMode::Upward, -1}}},
{Number{-23, -1},
{{Number::RoundingMode::ToNearest, -2},
{Number::RoundingMode::TowardsZero, -2},
{Number::RoundingMode::Downward, -3},
{Number::RoundingMode::Upward, -2}}},
{Number{-15, -1},
{{Number::RoundingMode::ToNearest, -2},
{Number::RoundingMode::TowardsZero, -1},
{Number::RoundingMode::Downward, -2},
{Number::RoundingMode::Upward, -1}}},
{Number{-25, -1},
{{Number::RoundingMode::ToNearest, -2},
{Number::RoundingMode::TowardsZero, -2},
{Number::RoundingMode::Downward, -3},
{Number::RoundingMode::Upward, -2}}},
{Number{-152, -2},
{{Number::RoundingMode::ToNearest, -2},
{Number::RoundingMode::TowardsZero, -1},
{Number::RoundingMode::Downward, -2},
{Number::RoundingMode::Upward, -1}}},
{Number{-252, -2},
{{Number::RoundingMode::ToNearest, -3},
{Number::RoundingMode::TowardsZero, -2},
{Number::RoundingMode::Downward, -3},
{Number::RoundingMode::Upward, -2}}},
{Number{-17, -1},
{{Number::RoundingMode::ToNearest, -2},
{Number::RoundingMode::TowardsZero, -1},
{Number::RoundingMode::Downward, -2},
{Number::RoundingMode::Upward, -1}}},
{Number{-27, -1},
{{Number::RoundingMode::ToNearest, -3},
{Number::RoundingMode::TowardsZero, -2},
{Number::RoundingMode::Downward, -3},
{Number::RoundingMode::Upward, -2}}},
};
for (auto const& [num, roundings] : expected)
{
for (auto const& [mode, val] : roundings)
{
NumberRoundModeGuard const g{mode};
auto const res = static_cast<std::int64_t>(num);
EXPECT_EQ((res), (val)) << to_string(num) + " with mode " +
std::to_string(static_cast<int>(mode)) + " expected " +
std::to_string(val) + " got " + std::to_string(res);
}
}
}
}
TEST(NumberTest, int64)
{
for (auto const mantissaScale : MantissaRange::getAllScales())
{
NumberMantissaScaleGuard const sg(mantissaScale);
auto const scale = Number::getMantissaScale();
// Control case
EXPECT_GT((Number::maxMantissa()), (10));
Number const ten{10};
EXPECT_LE((ten.exponent()), (0));
if (scale == MantissaRange::MantissaScale::Small)
{
EXPECT_GT((std::numeric_limits<std::int64_t>::max()), (kInitialXrp.drops()));
EXPECT_LT((Number::maxMantissa()), (kInitialXrp.drops()));
Number const initalXrp{kInitialXrp};
EXPECT_GT((initalXrp.exponent()), (0));
Number const maxInt64{Number::kMaxRep};
EXPECT_GT((maxInt64.exponent()), (0));
// 85'070'591'730'234'615'865'843'651'857'942'052'864 - 38 digits
EXPECT_EQ((power(maxInt64, 2)), (Number{85'070'591'730'234'62, 22}));
Number const max = Number{false, Number::maxMantissa(), 0, Number::Normalized{}};
EXPECT_LE(max.exponent(), 0);
// 99'999'999'999'999'980'000'000'000'000'001 - 32 digits
EXPECT_EQ(power(max, 2), (Number{99'999'999'999'999'98, 16}));
}
else
{
EXPECT_GT((std::numeric_limits<std::int64_t>::max()), (kInitialXrp.drops()));
EXPECT_GT((Number::maxMantissa()), (kInitialXrp.drops()));
Number const initalXrp{kInitialXrp};
EXPECT_LE((initalXrp.exponent()), (0));
Number const maxInt64{Number::kMaxRep};
EXPECT_LE((maxInt64.exponent()), (0));
// 85'070'591'730'234'615'847'396'907'784'232'501'249 - 38 digits
EXPECT_EQ((power(maxInt64, 2)), (Number{85'070'591'730'234'615'85, 19}));
NumberRoundModeGuard const mg(Number::RoundingMode::TowardsZero);
auto const maxMantissa = Number::maxMantissa();
Number const max = Number{false, maxMantissa, 0, Number::Normalized{}};
EXPECT_EQ((max.mantissa()), (maxMantissa / 10));
EXPECT_EQ((max.exponent()), (1));
// 99'999'999'999'999'999'800'000'000'000'000'000'100 - also 38
// digits
EXPECT_EQ(
(power(max, 2)), (Number{false, (maxMantissa / 10) - 1, 20, Number::Normalized{}}));
}
}
}
TEST(NumberTest, upward_rounding_produces_value_not_below_exact_at_k_max_rep_cusp)
{
for (auto const mantissaScale : MantissaRange::getAllScales())
{
NumberMantissaScaleGuard const mg{mantissaScale};
NumberRoundModeGuard const rg{Number::RoundingMode::Upward};
auto const scale = Number::getMantissaScale();
constexpr std::int64_t kAValue = 1'000'000'000'000'049'863LL;
constexpr std::int64_t kBValue = 9'223'372'036'854'315'903LL;
Number const a = kAValue;
Number const b = kBValue;
Number const product = a * b;
// Exact reference in BigInt.
BigInt const exactProduct = BigInt(kAValue) * BigInt(kBValue);
// What Number actually stored.
BigInt const storedValue = toBigInt(product);
BigInt const signedDifference = storedValue - exactProduct;
auto const message = [&] {
std::ostringstream os;
os << " a = " << fmt(BigInt(kAValue)) << "\n"
<< " b = " << fmt(BigInt(kBValue)) << "\n"
<< " exact a*b = " << fmt(exactProduct) << "\n"
<< " stored = " << fmt(storedValue) << "\n"
<< " stored - exact = " << fmt(signedDifference) << "\n"
<< " upward = " << (signedDifference >= 0 ? "held" : "VIOLATED") << "\n"
<< " stored.mantissa = " << product.mantissa() << "\n"
<< " stored.exponent = " << product.exponent() << "\n\n";
return os.str();
};
switch (scale)
{
case MantissaRange::MantissaScale::Large320:
case MantissaRange::MantissaScale::Large330:
EXPECT_TRUE(signedDifference >= 0) << message();
EXPECT_TRUE(signedDifference < pow10<BigInt>(product.exponent())) << message();
EXPECT_EQ(product.mantissa(), (std::numeric_limits<std::int64_t>::max() / 10) + 1);
EXPECT_EQ(product.exponent(), 19);
break;
case MantissaRange::MantissaScale::LargeLegacy:
EXPECT_TRUE(signedDifference < 0) << message();
EXPECT_EQ(
product.mantissa(), (std::numeric_limits<std::int64_t>::max() / 100) * 100);
EXPECT_EQ(product.exponent(), 18);
break;
case MantissaRange::MantissaScale::Small:
// The seemingly weird rounding here is because a & b are both
// normalized, and both round up when being converted to Number,
// so you're really getting
// 1_000_000_000_000_050 * 9_223_372_036_854_316.
EXPECT_TRUE(signedDifference >= 0) << message();
EXPECT_EQ(
product.mantissa(), (std::numeric_limits<std::int64_t>::max() / 1000) + 3);
EXPECT_EQ(product.exponent(), 21);
break;
}
}
}
/*
* Companion regression for the kMaxRep cusp behavior, but for `operator/=` on
* the cusp-fix-ENABLED `Large` scale.
*
* Before the dropped-remainder fix, `operator/=` with Upward rounding could
* return a value STRICTLY LESS than the exact quotient, violating Upward's
* directional invariant.
*
* Mechanism (fix-enabled path):
* 1. `operator/=` computes `numerator = nm * 10^17` and
* `zm = numerator / dm` (integer division, truncates remainder).
* 2. If `remainder != 0`, the correction block runs:
* zm *= 100000
* correction = (remainder * 100000) / dm // also truncates
* zm += correction
* ze -= 5
* The truncation in `correction` discards a sub-1/100000 residual.
* 3. `normalize`'s shift loop reduces zm to fit, but the discarded residual
* is BELOW the Guard's visibility, so the Guard sees fraction = 0.
* 4. Under Upward + positive, `round()` returns -1 (no round-up), and the
* algorithm returns the truncated zm.
*/
TEST(NumberTest, upward_division_returns_value_not_below_exact_on_large_scale)
{
for (auto const mantissaScale : MantissaRange::getAllScales())
{
NumberMantissaScaleGuard const mg{mantissaScale};
NumberRoundModeGuard const rg{Number::RoundingMode::Upward};
auto const scale = Number::getMantissaScale();
constexpr std::int64_t kAValue = 2LL;
constexpr std::int64_t kBValue = 1'000'000'000'000'000'007LL;
// kBValue = 10^18 + 7 (prime, in [minMantissa, kMaxRep]).
Number const a{kAValue, 0};
Number const b{kBValue, 0};
Number const quotient = a / b;
Dec const exact = Dec(kAValue) / Dec(kBValue);
Dec const stored = Dec(quotient.mantissa()) * pow10(quotient.exponent());
Dec const diff = stored - exact;
auto const message = [&] {
std::ostringstream os;
os << " a = " << kAValue << "\n"
<< " b = " << kBValue << "\n"
<< " exact a/b = " << fmt(exact) << "\n"
<< " stored a/b = " << fmt(stored) << "\n"
<< " stored - exact = " << fmt(diff)
<< " (negative => Upward gave value BELOW truth)\n"
<< " quotient.mantissa = " << quotient.mantissa() << "\n"
<< " quotient.exponent = " << quotient.exponent() << "\n\n";
return os.str();
};
// Upward invariant: stored >= exact. Bug: stored < exact.
switch (scale)
{
case MantissaRange::MantissaScale::Large320:
case MantissaRange::MantissaScale::Large330:
EXPECT_TRUE(stored >= exact) << message();
EXPECT_TRUE(diff < pow10(quotient.exponent())) << message();
break;
case MantissaRange::MantissaScale::LargeLegacy:
EXPECT_TRUE(stored < exact) << message();
EXPECT_TRUE(diff >= -pow10(quotient.exponent())) << message();
break;
case MantissaRange::MantissaScale::Small:
// Small mantissa doesn't have the correction for dropped remainders.
EXPECT_TRUE(stored < exact) << message();
break;
}
}
}
// Companion test case for Upward positive operator/=: Downward negative.
TEST(NumberTest, downward_division_returns_value_not_above_exact_on_large_scale)
{
for (auto const mantissaScale : MantissaRange::getAllScales())
{
NumberMantissaScaleGuard const mg{mantissaScale};
NumberRoundModeGuard const rg{Number::RoundingMode::Downward};
auto const scale = Number::getMantissaScale();
constexpr std::int64_t kAValue = -2LL;
constexpr std::int64_t kBValue = 1'000'000'000'000'000'007LL;
// kBValue = 10^18 + 7 (prime, in [minMantissa, kMaxRep]).
Number const a{kAValue, 0};
Number const b{kBValue, 0};
Number const quotient = a / b;
Dec const exact = Dec(kAValue) / Dec(kBValue);
Dec const stored = Dec(quotient.mantissa()) * pow10(quotient.exponent());
Dec const diff = stored - exact;
auto const message = [&] {
std::ostringstream os;
os << " a = " << kAValue << "\n"
<< " b = " << kBValue << "\n"
<< " exact a/b = " << fmt(exact) << "\n"
<< " stored a/b = " << fmt(stored) << "\n"
<< " stored - exact = " << fmt(diff)
<< " (positive => Downward gave value ABOVE truth)\n"
<< " quotient.mantissa = " << quotient.mantissa() << "\n"
<< " quotient.exponent = " << quotient.exponent() << "\n\n";
return os.str();
};
// invariant: stored <= exact. Bug: stored > exact.
switch (scale)
{
case MantissaRange::MantissaScale::Large320:
case MantissaRange::MantissaScale::Large330:
EXPECT_TRUE(stored <= exact) << message();
EXPECT_TRUE(diff > -pow10(quotient.exponent())) << message();
break;
case MantissaRange::MantissaScale::LargeLegacy:
EXPECT_TRUE(stored > exact) << message();
EXPECT_TRUE(diff <= pow10(quotient.exponent())) << message();
break;
case MantissaRange::MantissaScale::Small:
// Small mantissa doesn't have the correction for dropped remainders.
EXPECT_TRUE(stored < exact) << message();
break;
}
}
}
/*
* Companion test case for Upward positive operator/=: ToNearest.
*
* With ToNearest, if the dropped digits are exactly "5", then the mantissa will
* be rounded to even. The numbers below result in a value where the unrounded
* mantissa ends in an even digit, and "infinite precision" would drop
* "500000000000000000145...", but doNormalize only sees "5". Without the
* rounding fix, doNormalize rounds down to the even value. With the rounding
* fix, doNormalize knows there are more digits beyond "5", and so rounds _up_
* to the odd value.
*/
TEST(NumberTest, to_nearest_division_uses_dropped_digits_on_large_scale)
{
for (auto const mantissaScale : MantissaRange::getAllScales())
{
NumberMantissaScaleGuard const mg{mantissaScale};
NumberRoundModeGuard const rg{Number::RoundingMode::ToNearest};
auto const scale = Number::getMantissaScale();
constexpr std::int64_t kAValue = 1'269'917'268'816'087'809LL;
constexpr std::int64_t kBValue = 3'458'525'013'821'685'511LL;
// kBValue is prime and in [minMantissa, kMaxRep].
Number const a{kAValue, 0};
Number const b{kBValue, 0};
Number const quotient = a / b;
Dec const exact = Dec(kAValue) / Dec(kBValue);
Dec const stored = Dec(quotient.mantissa()) * pow10(quotient.exponent());
Dec const diff = stored - exact;
auto const message = [&] {
std::ostringstream os;
os << " a = " << kAValue << "\n"
<< " b = " << kBValue << "\n"
<< " exact a/b = " << fmt(exact) << "\n"
<< " stored a/b = " << fmt(stored) << "\n"
<< " stored - exact = " << fmt(diff)
<< " (negative => ToNearest gave value BELOW truth)\n"
<< " quotient.mantissa = " << quotient.mantissa() << "\n"
<< " quotient.exponent = " << quotient.exponent() << "\n\n";
return os.str();
};
// invariant: stored >= exact. Bug: stored < exact.
switch (scale)
{
case MantissaRange::MantissaScale::Large320:
case MantissaRange::MantissaScale::Large330:
EXPECT_TRUE(stored >= exact) << message();
EXPECT_TRUE(diff < pow10(quotient.exponent())) << message();
break;
case MantissaRange::MantissaScale::LargeLegacy:
EXPECT_TRUE(stored < exact) << message();
EXPECT_TRUE(diff >= -pow10(quotient.exponent())) << message();
break;
case MantissaRange::MantissaScale::Small:
// Small mantissa doesn't have the correction for dropped remainders.
EXPECT_TRUE(stored < exact) << message();
break;
}
}
}
TEST(NumberTest, subtraction_rounding)
{
for (auto const mantissaScale : MantissaRange::getAllScales())
{
NumberMantissaScaleGuard const mg{mantissaScale};
NumberRoundModeGuard const rg{Number::RoundingMode::ToNearest};
auto const scale = Number::getMantissaScale();
auto const exp = Number::mantissaLog();
// SubCase is <offset, extraB, aString, bString>
// * offset: offset from exp
// * extraB: whether to include 1e"exp" in "b"
// * aString: expected string value for "a"
// * bString: expected string value for "b"
// There aren't too many valid combinations for test cases here. If extraB is true,
// offset can really only be 2, because any larger and the mantissa can't be represented
// without loss. Offset can't be less than 2, or there's no error.
using SubCase = std::tuple<int, bool, std::string, std::string>;
auto const c = std::to_array<SubCase>({
{2,
true,
scale == MantissaRange::MantissaScale::Small ? "100000000000000000"
: "100000000000000000000",
scale == MantissaRange::MantissaScale::Small ? "-1000000000000001"
: "-1000000000000000001"},
{2,
false,
scale == MantissaRange::MantissaScale::Small ? "100000000000000000"
: "100000000000000000000",
"-1"},
{30,
false,
scale == MantissaRange::MantissaScale::Small
? "1000000000000000000000000000000000000000000000"
: "1000000000000000000000000000000000000000000000000",
"-1"},
});
for (auto const& [offset, extraB, aString, bString] : c)
{
Number const a{1LL, exp + offset};
Number const b{-((extraB ? Number{1, exp} : kNumZero) + 1)};
auto const bigA = toBigInt(a);
auto const bigB = toBigInt(b);
EXPECT_EQ(bigA, BigInt{aString});
EXPECT_EQ(bigB, BigInt{bString});
auto construct = [&a, &b](Number::RoundingMode r) {
NumberRoundModeGuard const roundGuard{r};
auto const sum = a + b;
BigInt const stored = toBigInt(sum);
return std::make_pair(r, std::make_pair(stored, sum));
};
BigInt const exact = bigA + bigB;
auto const sums = [&]() {
std::map<Number::RoundingMode, std::pair<BigInt, Number>> r;
r.emplace(construct(Number::RoundingMode::TowardsZero));
r.emplace(construct(Number::RoundingMode::Upward));
r.emplace(construct(Number::RoundingMode::Downward));
r.emplace(construct(Number::RoundingMode::ToNearest));
return r;
}();
auto const message = [&](auto const& r, auto const& sum) {
std::ostringstream os;
os << " a = " << a << " (" << fmt(bigA) << ")\n b = " << b
<< " (" << fmt(bigB) << ")\n exact a + b = " << fmt(exact) << "\n";
auto const diff = sum.first - exact;
auto const rLabel = to_string(r);
os << std::string(15 - rLabel.length(), ' ') << rLabel << " = " << fmt(sum.first)
<< "\n difference = " << fmt(diff) << "\n\n";
return os.str();
};
auto const expectedExponent =
offset - (scale == MantissaRange::MantissaScale::Small && extraB ? 1 : 0);
auto const epsilon = pow10<BigInt>(expectedExponent);
for (auto const& [r, sum] : sums)
{
auto diff = sum.first - exact;
switch (scale)
{
case MantissaRange::MantissaScale::Small:
case MantissaRange::MantissaScale::LargeLegacy:
case MantissaRange::MantissaScale::Large320: {
// Without the fix, all the results but one round up
if (r == Number::RoundingMode::Downward)
{
// Downward works because the Guard sign is negative, and Downward
// returns Up instead of Down if negative and there's a remainder,
// whereas TowardsZero always returns Down.
EXPECT_LT(sum.first, exact) << message(r, sum);
EXPECT_EQ(diff, -(epsilon - 1)) << message(r, sum);
}
else
{
EXPECT_GT(sum.first, exact) << message(r, sum);
EXPECT_EQ(diff, 1) << message(r, sum);
}
break;
}
default: {
EXPECT_LE(sum.second.exponent(), expectedExponent) << message(r, sum);
switch (r)
{
case Number::RoundingMode::Upward:
case Number::RoundingMode::ToNearest:
EXPECT_GT(sum.first, exact) << message(r, sum);
EXPECT_EQ(diff, 1) << message(r, sum);
break;
default:
EXPECT_LT(sum.first, exact) << message(r, sum);
EXPECT_EQ(diff, -(epsilon - 1)) << message(r, sum);
}
}
}
}
}
}
}
TEST(NumberTest, normalization_cusp_tonearest_and_downward)
{
for (auto const mantissaScale : MantissaRange::getAllScales())
{
NumberMantissaScaleGuard const mg{mantissaScale};
NumberRoundModeGuard const rg{Number::RoundingMode::ToNearest};
auto const scale = Number::getMantissaScale();
constexpr auto kMaxRep = Number::kMaxRep;
// Both ToNearest and Downward should round to `below`
auto constexpr actual = static_cast<std::uint64_t>(kMaxRep) + 1;
Number const below{static_cast<std::int64_t>(kMaxRep), 0};
Number const above{false, static_cast<std::uint64_t>(kMaxRep) + 3, 0, Number::Normalized{}};
auto construct = [](Number::RoundingMode mode) {
NumberRoundModeGuard const roundGuard{mode};
return Number(false, actual, 0, Number::Normalized{});
};
Number const upward = construct(Number::RoundingMode::Upward);
Number const toNearest = construct(Number::RoundingMode::ToNearest);
Number const downward = construct(Number::RoundingMode::Downward);
auto message = [&] {
std::ostringstream log;
log << " actual = " << actual << " (kMaxRep + 1)\n"
<< " below = " << below << " (kMaxRep, distance 1)\n"
<< " above = " << above << " (kMaxRep + 3, distance 2)\n"
<< " Upward = " << upward << "\n"
<< " ToNearest = " << toNearest << "\n"
<< " Downward = " << downward << "\n\n";
return log.str();
};
switch (scale)
{
case MantissaRange::MantissaScale::Small:
// With the small mantissa, everything but Downward rounds UP, including the
// reference values, "above" and "below"
EXPECT_EQ(below, above) << message();
EXPECT_EQ(upward, above) << message();
EXPECT_EQ(toNearest, above) << message();
EXPECT_LT(downward, below) << message();
break;
case MantissaRange::MantissaScale::LargeLegacy:
case MantissaRange::MantissaScale::Large320:
// Upward round UP
EXPECT_EQ(upward, above) << message();
// ToNearest rounds UP when the DOWN neighbor is strictly closer
EXPECT_EQ(toNearest, above) << message();
EXPECT_GT(toNearest, below) << message();
// Downward undershoots: it returns a value below `below`
EXPECT_LT(downward, below) << message();
// Both should have given the same answer, but they differ
EXPECT_GT(toNearest, downward) << message();
break;
default:
// Covers "Large" and any newly added scales
// Upward round UP
EXPECT_EQ(upward, above) << message();
// ToNearest rounds to the strictly closer DOWN neighbor
EXPECT_NE(toNearest, above) << message();
EXPECT_EQ(toNearest, below) << message();
// Downward also rounds to `below`
EXPECT_EQ(downward, below) << message();
// ToNearest rounds to downward
EXPECT_EQ(toNearest, downward) << message();
break;
}
}
}
TEST(NumberTest, number_add_directed_sign_wrong)
{
for (auto const mantissaScale : MantissaRange::getAllScales())
{
NumberMantissaScaleGuard const mg{mantissaScale};
NumberRoundModeGuard const rg{Number::RoundingMode::ToNearest};
auto const scale = Number::getMantissaScale();
{
// Two negative numbers with the same exponent
Number const a{-6, Number::mantissaLog()};
Number const b{a - 3};
EXPECT_TRUE(a.exponent() == b.exponent() && abs(b) > abs(a));
BigInt const exact = toBigInt(a) + toBigInt(b);
if (scale == MantissaRange::MantissaScale::Small)
{
EXPECT_EQ(exact, BigInt{"-12000000000000003"});
}
else
{
EXPECT_EQ(exact, BigInt{"-12000000000000000003"});
}
Number down, up;
{
NumberRoundModeGuard const g{Number::RoundingMode::Downward};
down = a + b;
}
{
NumberRoundModeGuard const g{Number::RoundingMode::Upward};
up = a + b;
}
auto const valueDown = toBigInt(down);
auto const valueUp = toBigInt(up);
auto message = [&] {
std::ostringstream log;
log << " exact = " << fmt(exact) << "\n downward = " << fmt(valueDown)
<< " (correct rounding: <= exact)"
<< "\n upward = " << fmt(valueUp) << " (correct rounding: >= exact)\n\n";
return log.str();
};
if (scale == MantissaRange::MantissaScale::Large330)
{
EXPECT_LE(valueDown, exact) << message(); // Downward should round away from zero
EXPECT_GE(valueUp, exact) << message(); // Upward should round toward 0
}
else
{
EXPECT_GT(valueDown, exact)
<< message(); // Downward rounded toward zero (too high)
EXPECT_LT(valueUp, exact) << message(); // Upward rounded toward -inf (too low)
}
}
{
// Positive control: the same magnitudes with a positive result round
Number const pa{6, Number::mantissaLog()};
Number const pb{pa + 3};
EXPECT_TRUE(pa.exponent() == pb.exponent() && abs(pb) > abs(pa));
BigInt const pexact = toBigInt(pa) + toBigInt(pb); // 12'000'000'000'000'000'003
Number pdown, pup;
{
NumberRoundModeGuard const g{Number::RoundingMode::Downward};
pdown = pa + pb;
}
{
NumberRoundModeGuard const g{Number::RoundingMode::Upward};
pup = pa + pb;
}
auto const valuePDown = toBigInt(pdown);
auto const valuePUp = toBigInt(pup);
auto message = [&] {
std::ostringstream log;
log << " exact = " << fmt(pexact) << "\n downward = " << fmt(valuePDown)
<< " (correct rounding: <= exact)"
<< "\n upward = " << fmt(valuePUp)
<< " (correct rounding: >= exact)\n\n";
return log.str();
};
EXPECT_LE(valuePDown, pexact) << message(); // correct for positive results
EXPECT_GE(valuePUp, pexact) << message();
}
{
// Mixed sign numbers with the same exponent: negative second value
Number const a{1, Number::mantissaLog()};
Number const b{Number{-9, Number::mantissaLog()} - 3};
EXPECT_TRUE(a.exponent() == b.exponent() && abs(b) > abs(a));
BigInt const exact = toBigInt(a) + toBigInt(b);
if (scale == MantissaRange::MantissaScale::Small)
{
EXPECT_EQ(exact, BigInt{"-8000000000000003"});
}
else
{
EXPECT_EQ(exact, BigInt{"-8000000000000000003"});
}
Number down, up;
{
NumberRoundModeGuard const g{Number::RoundingMode::Downward};
down = a + b;
}
{
NumberRoundModeGuard const g{Number::RoundingMode::Upward};
up = a + b;
}
auto const valueDown = toBigInt(down);
auto const valueUp = toBigInt(up);
auto message = [&] {
std::ostringstream log;
log << " exact = " << fmt(exact) << "\n downward = " << fmt(valueDown)
<< " (correct rounding: <= exact)"
<< "\n upward = " << fmt(valueUp) << " (correct rounding: >= exact)\n\n";
return log.str();
};
EXPECT_LE(valueDown, exact) << message(); // Downward should round away from zero
EXPECT_GE(valueUp, exact) << message(); // Upward should round toward 0
}
{
// Mixed sign numbers with the same exponent: negative first value
Number const a{-1, Number::mantissaLog()};
Number const b{Number{9, Number::mantissaLog()} + 3};
EXPECT_TRUE(a.exponent() == b.exponent() && abs(b) > abs(a));
BigInt const exact = toBigInt(a) + toBigInt(b);
if (scale == MantissaRange::MantissaScale::Small)
{
EXPECT_EQ(exact, BigInt{"8000000000000003"});
}
else
{
EXPECT_EQ(exact, BigInt{"8000000000000000003"});
}
Number down, up;
{
NumberRoundModeGuard const g{Number::RoundingMode::Downward};
down = a + b;
}
{
NumberRoundModeGuard const g{Number::RoundingMode::Upward};
up = a + b;
}
auto const valueDown = toBigInt(down);
auto const valueUp = toBigInt(up);
auto message = [&] {
std::ostringstream log;
log << " exact = " << fmt(exact) << "\n downward = " << fmt(valueDown)
<< " (correct rounding: <= exact)"
<< "\n upward = " << fmt(valueUp) << " (correct rounding: >= exact)\n\n";
return log.str();
};
EXPECT_LE(valueDown, exact) << message(); // Downward should round away from zero
EXPECT_GE(valueUp, exact) << message(); // Upward should round toward 0
}
}
}
TEST(NumberTest, number_add_to_nearest_picks_farther)
{
for (auto const mantissaScale : MantissaRange::getAllScales())
{
NumberMantissaScaleGuard const mg{mantissaScale};
NumberRoundModeGuard const rg{Number::RoundingMode::ToNearest};
auto const scale = Number::getMantissaScale();
// Case is <y, expected q>
using Case = std::pair<Number, std::int64_t>;
auto const c = std::to_array<Case>({
{Number{5'175'909'259'972'499'745LL, 22}, -1'074'951'375'311'646'003},
{Number{1}, -1'074'956'551'220'905'975},
{Number{1, 10}, -1'074'956'551'220'905'975},
{Number{1, 20}, -1'074'956'551'220'905'975},
{Number{1, 27}, -1'074'956'551'220'905'975},
{Number{1, 28}, -1'074'956'551'220'905'974},
{Number{1, 31}, -1'074'956'551'220'904'975},
});
for (auto const& [y, expectedQ] : c)
{
Number const x{-1'074'956'551'220'905'975LL, 28};
Number const res = x + y;
BigInt const exact = toBigInt(x) + toBigInt(y);
BigInt const vres = toBigInt(res);
BigInt ulp = 1;
for (int i = 0; i < res.exponent(); ++i)
ulp *= 10;
BigInt const q = (exact - ulp / 2) / ulp;
Number const normalizedExact{static_cast<std::int64_t>(q), res.exponent()};
BigInt const norm = toBigInt(normalizedExact);
auto message = [&](auto const& comp) {
std::ostringstream log;
log << fmt(q) + " != " + fmt(comp) << "\n"
<< " x = " << x << "\n y = " << y
<< "\n exact = " << fmt(exact)
<< "\n result (x + y) = " << fmt(vres)
<< "\n normalize(exact) = " << fmt(norm) << "\n\n";
return log.str();
};
if (scale == MantissaRange::MantissaScale::Small)
{
auto const comp = toBigInt(Number{expectedQ, -3});
EXPECT_EQ(q, comp) << message(comp);
}
else
{
EXPECT_EQ(q, expectedQ) << message(BigInt(expectedQ));
}
EXPECT_EQ(normalizedExact, res);
}
}
}
TEST(NumberTest, number_cusp_rounding_with_fractional_parts)
{
for (auto const mantissaScale : MantissaRange::getAllScales())
{
NumberMantissaScaleGuard const mg{mantissaScale};
auto const scale = Number::getMantissaScale();
Number const below{static_cast<std::int64_t>(Number::kMaxRep), 0};
Number const above{false, Number::kMaxRepUp, 0, Number::Normalized{}};
auto header = [&] {
std::ostringstream log;
log << "Scale: " << to_string(mantissaScale) << ", Below: " << below
<< ", Above: " << above << "\n";
return log.str();
};
auto const zeroPointFour = Number(4, -1);
auto const zeroPointFive = Number(5, -1);
auto const zeroPointSix = Number(6, -1);
auto const onePointFour = Number(14, -1);
auto const onePointFive = Number(15, -1);
auto const onePointSix = Number(16, -1);
auto const twoPointFour = Number(24, -1);
auto const twoPointFive = Number(25, -1);
auto const twoPointSix = Number(26, -1);
auto const operands = std::to_array<Number>({
zeroPointFour,
zeroPointFive,
zeroPointSix,
onePointFour,
onePointFive,
onePointSix,
twoPointFour,
twoPointFive,
twoPointSix,
});
auto const modes = std::to_array<Number::RoundingMode>({
Number::RoundingMode::ToNearest,
Number::RoundingMode::TowardsZero,
Number::RoundingMode::Downward,
Number::RoundingMode::Upward,
});
// Addition cases test kMaxRep + Operand
for (auto const& mode : modes)
{
for (auto const& operand : operands)
{
NumberRoundModeGuard const rg{mode};
auto const expectedValue = [&]() {
// Returns "above" by default. The checks here are for exceptions.
if (scale >= MantissaRange::MantissaScale::Large330)
{
if (mode == Number::RoundingMode::ToNearest && operand < onePointFive)
return below;
if (mode == Number::RoundingMode::TowardsZero ||
mode == Number::RoundingMode::Downward)
return below;
}
if (scale == MantissaRange::MantissaScale::Large320)
{
if (mode == Number::RoundingMode::ToNearest)
{
if (operand < zeroPointFive)
return below;
}
if (mode == Number::RoundingMode::TowardsZero ||
mode == Number::RoundingMode::Downward)
{
if (operand >= onePointFour)
return below - 7;
return below;
}
}
if (scale == MantissaRange::MantissaScale::LargeLegacy)
{
if (mode == Number::RoundingMode::ToNearest)
{
if (operand < zeroPointFive)
return below;
if (operand <= zeroPointSix)
return below - 7;
}
if (mode == Number::RoundingMode::TowardsZero ||
mode == Number::RoundingMode::Downward)
{
if (operand >= onePointFour)
return below - 7;
return below;
}
if (mode == Number::RoundingMode::Upward && operand <= zeroPointSix)
return below - 7;
}
if (scale == MantissaRange::MantissaScale::Small &&
mode == Number::RoundingMode::Upward)
return above + 1000;
return above;
}();
Number const actual = below + operand;
auto message = [&] {
std::stringstream ss;
ss << header() << "kMaxRep + " << operand << " rounded " << to_string(mode)
<< " to " << actual << ". Expected: " << expectedValue;
return ss.str();
};
EXPECT_EQ(actual, expectedValue) << message();
}
}
// Subtraction cases test kMaxRepUp - Operand
for (auto const& mode : modes)
{
for (auto const& operand : operands)
{
NumberRoundModeGuard const rg{mode};
auto const expectedValue = [&]() {
if (scale >= MantissaRange::MantissaScale::Large330)
{
if (mode == Number::RoundingMode::ToNearest && operand > onePointFive)
return below;
if (mode == Number::RoundingMode::TowardsZero ||
mode == Number::RoundingMode::Downward)
return below;
}
if (scale == MantissaRange::MantissaScale::LargeLegacy ||
scale == MantissaRange::MantissaScale::Large320)
{
if (mode == Number::RoundingMode::ToNearest)
{
if (operand >= twoPointSix)
return below;
}
if (mode == Number::RoundingMode::TowardsZero)
{
if (operand >= onePointFour)
return below - 7;
}
if (mode == Number::RoundingMode::Downward)
{
if (operand <= onePointSix)
return below - 7;
return below;
}
}
if (scale == MantissaRange::MantissaScale::Small)
{
if (mode == Number::RoundingMode::Downward)
return below - 1000;
if (mode == Number::RoundingMode::Upward)
return below;
}
return above;
}();
Number const actual = above - operand;
auto message = [&] {
std::stringstream ss;
ss << header() << "kMaxRepUp - " << operand << " rounded " << to_string(mode)
<< " to " << actual << ". Expected: " << expectedValue;
return ss.str();
};
EXPECT_EQ(actual, expectedValue) << message();
}
}
}
}
} // namespace xrpl