Files
rippled/src/test/basics/Number_test.cpp
2026-06-03 18:37:53 -04:00

2087 lines
86 KiB
C++

#include <xrpl/basics/Number.h>
#include <xrpl/beast/unit_test/suite.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 <array>
#include <cctype>
#include <cstdint>
#include <iomanip>
#include <limits>
#include <map>
#include <sstream>
#include <stdexcept>
#include <string>
#include <tuple>
namespace xrpl {
class Number_test : public beast::unit_test::Suite
{
using BigInt = boost::multiprecision::cpp_int;
static std::string
fmt(BigInt const& value)
{
auto s = to_string(value);
std::string out;
int count = 0;
for (auto it = s.rbegin(); it != s.rend(); ++it)
{
if (count != 0 && count % 3 == 0 && (isdigit(*it) != 0))
out.insert(out.begin(), '_');
out.insert(out.begin(), *it);
++count;
}
return out;
}
static BigInt
toBigInt(Number const& n)
{
BigInt v = n.mantissa();
for (int i = 0; i < n.exponent(); ++i)
v *= 10;
return v;
}
using dec = boost::multiprecision::cpp_dec_float_50;
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;
}
// n < 0
T p = 1;
p /= pow10<T>(-n);
return p;
}
static std::string
fmt(dec const& v)
{
std::ostringstream os;
os << std::setprecision(40) << v;
return os.str();
}
public:
void
testZero()
{
testcase << "zero " << to_string(Number::getMantissaScale());
for (Number const& z : {Number{0, 0}, Number{0}})
{
BEAST_EXPECT(z.mantissa() == 0);
BEAST_EXPECT(z.exponent() == Number{}.exponent());
BEAST_EXPECT((z + z) == z);
BEAST_EXPECT((z - z) == z);
BEAST_EXPECT(z == -z);
}
}
void
testLimits()
{
auto const scale = Number::getMantissaScale();
testcase << "test_limits " << to_string(scale);
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;
}
BEAST_EXPECT(caught);
auto test = [this](auto const& x, auto const& y, int line) {
auto const result = x == y;
std::stringstream ss;
ss << x << " == " << y << " -> " << (result ? "true" : "false");
expect(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;
}
BEAST_EXPECT(caught);
}
void
testAdd()
{
auto const scale = Number::getMantissaScale();
testcase << "test_add " << to_string(scale);
BEAST_EXPECT(Number::getround() == Number::RoundingMode::ToNearest);
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{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 cLargeCorrected = std::to_array<Case>({
{Number{Number::kMaxRep},
Number{6, -1},
Number{(Number::kMaxRep / 10) + 1, 1},
__LINE__},
});
auto test = [this](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(result == z, ss.str(), __FILE__, line);
}
};
if (scale == MantissaRange::MantissaScale::Small)
{
test(cSmall);
}
else
{
test(cLarge);
if (scale == MantissaRange::MantissaScale::LargeLegacy)
{
test(cLargeLegacy);
}
else
{
test(cLargeCorrected);
}
}
{
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;
}
BEAST_EXPECT(caught);
}
}
void
testSub()
{
auto const scale = Number::getMantissaScale();
testcase << "test_sub " << to_string(scale);
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 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{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__},
{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__},
{power(2, 63), Number{3, 0}, Number{Number::kMaxRep}, __LINE__},
});
auto test = [this](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(result == z, ss.str(), __FILE__, line);
}
};
if (scale == MantissaRange::MantissaScale::Small)
{
test(cSmall);
}
else
{
test(cLarge);
}
}
void
testMul()
{
auto const scale = Number::getMantissaScale();
testcase << "test_mul " << to_string(scale);
using Case = std::tuple<Number, Number, Number>;
auto test = [this](auto const& c) {
for (auto const& [x, y, z] : c)
{
auto const result = x * y;
std::stringstream ss;
ss << x << " * " << y << " = " << result << ". Expected: " << z;
BEAST_EXPECTS(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);
testcase << "test_mul " << to_string(Number::getMantissaScale()) << " towards_zero";
{
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);
testcase << "test_mul " << to_string(Number::getMantissaScale()) << " 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);
testcase << "test_mul " << to_string(Number::getMantissaScale()) << " 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);
}
testcase << "test_mul " << to_string(Number::getMantissaScale()) << " overflow";
{
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;
}
BEAST_EXPECT(caught);
}
}
void
testDiv()
{
auto const scale = Number::getMantissaScale();
testcase << "test_div " << to_string(scale);
using Case = std::tuple<Number, Number, Number>;
auto test = [this](auto const& c) {
for (auto const& [x, y, z] : c)
{
auto const result = x / y;
std::stringstream ss;
ss << x << " / " << y << " = " << result << ". Expected: " << z;
BEAST_EXPECTS(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);
}
testcase << "test_div " << to_string(Number::getMantissaScale()) << " towards_zero";
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);
}
testcase << "test_div " << to_string(Number::getMantissaScale()) << " downward";
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);
}
testcase << "test_div " << to_string(Number::getMantissaScale()) << " upward";
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);
}
testcase << "test_div " << to_string(Number::getMantissaScale()) << " overflow";
bool caught = false;
try
{
Number{1000000000000000, -15} / Number{0};
}
catch (std::overflow_error const&)
{
caught = true;
}
BEAST_EXPECT(caught);
}
void
testRoot()
{
auto const scale = Number::getMantissaScale();
testcase << "test_root " << to_string(scale);
using Case = std::tuple<Number, unsigned, Number>;
auto test = [this](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;
BEAST_EXPECTS(result == z, ss.str());
}
};
/*
auto tests = [&](auto const& cSmall, auto const& cLarge) {
test(cSmall);
if (scale != MantissaRange::MantissaScale::Small)
test(cLarge);
};
*/
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;
}
BEAST_EXPECT(caught);
caught = false;
try
{
(void)root(Number{-2}, 4);
}
catch (std::overflow_error const&)
{
caught = true;
}
BEAST_EXPECT(caught);
}
void
testRoot2()
{
auto const scale = Number::getMantissaScale();
testcase << "test_root2 " << to_string(scale);
auto test = [this](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;
BEAST_EXPECTS(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;
}
BEAST_EXPECT(caught);
}
void
testPower1()
{
testcase << "test_power1 " << to_string(Number::getMantissaScale());
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)
BEAST_EXPECT((power(x, y) == z));
}
void
testPower2()
{
testcase << "test_power2 " << to_string(Number::getMantissaScale());
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)
BEAST_EXPECT((power(x, n, d) == z));
bool caught = false;
try
{
(void)power(Number{7}, 0, 0);
}
catch (std::overflow_error const&)
{
caught = true;
}
BEAST_EXPECT(caught);
caught = false;
try
{
(void)power(Number{7}, 1, 0);
}
catch (std::overflow_error const&)
{
caught = true;
}
BEAST_EXPECT(caught);
caught = false;
try
{
(void)power(Number{-1, -1}, 3, 2);
}
catch (std::overflow_error const&)
{
caught = true;
}
BEAST_EXPECT(caught);
}
void
testConversions()
{
testcase << "testConversions " << to_string(Number::getMantissaScale());
IOUAmount const x{5, 6};
Number const y = x;
BEAST_EXPECT((y == Number{5, 6}));
IOUAmount const z{y};
BEAST_EXPECT(x == z);
XRPAmount const xrp{500};
STAmount const st = xrp;
Number const n = st;
BEAST_EXPECT(XRPAmount{n} == xrp);
IOUAmount const x0{0, 0};
Number const y0 = x0;
BEAST_EXPECT((y0 == Number{0}));
IOUAmount const z0{y0};
BEAST_EXPECT(x0 == z0);
XRPAmount const xrp0{0};
Number const n0 = xrp0;
BEAST_EXPECT(n0 == Number{0});
XRPAmount const xrp1{n0};
BEAST_EXPECT(xrp1 == xrp0);
}
void
testToInteger()
{
testcase << "test_to_integer " << to_string(Number::getMantissaScale());
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);
BEAST_EXPECT(j == y);
}
}
auto prevMode = Number::setround(Number::RoundingMode::TowardsZero);
BEAST_EXPECT(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);
BEAST_EXPECT(j == y);
}
}
prevMode = Number::setround(Number::RoundingMode::Downward);
BEAST_EXPECT(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);
BEAST_EXPECT(j == y);
}
}
prevMode = Number::setround(Number::RoundingMode::Upward);
BEAST_EXPECT(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);
BEAST_EXPECT(j == y);
}
}
bool caught = false;
try
{
(void)static_cast<std::int64_t>(Number{9223372036854776, 3});
}
catch (std::overflow_error const&)
{
caught = true;
}
BEAST_EXPECT(caught);
}
void
testSquelch()
{
testcase << "test_squelch " << to_string(Number::getMantissaScale());
Number const limit{1, -6};
BEAST_EXPECT((squelch(Number{2, -6}, limit) == Number{2, -6}));
BEAST_EXPECT((squelch(Number{1, -6}, limit) == Number{1, -6}));
BEAST_EXPECT((squelch(Number{9, -7}, limit) == Number{0}));
BEAST_EXPECT((squelch(Number{-2, -6}, limit) == Number{-2, -6}));
BEAST_EXPECT((squelch(Number{-1, -6}, limit) == Number{-1, -6}));
BEAST_EXPECT((squelch(Number{-9, -7}, limit) == Number{0}));
}
void
testToString()
{
auto const scale = Number::getMantissaScale();
testcase << "testToString " << to_string(scale);
auto test = [this](Number const& n, std::string const& expected) {
auto const result = to_string(n);
std::stringstream ss;
ss << "to_string(" << result << "). Expected: " << expected;
BEAST_EXPECTS(result == expected, ss.str());
};
test(Number(-2, 0), "-2");
test(Number(0, 0), "0");
test(Number(2, 0), "2");
test(Number(25, -3), "0.025");
test(Number(-25, -3), "-0.025");
test(Number(25, 1), "250");
test(Number(-25, 1), "-250");
test(Number(2, 20), "2e20");
test(Number(-2, -20), "-2e-20");
// Test the edges
// ((exponent < -(25)) || (exponent > -(5)))))
// or ((exponent < -(28)) || (exponent > -(8)))))
test(Number(2, -10), "0.0000000002");
test(Number(2, -11), "2e-11");
test(Number(-2, 10), "-20000000000");
test(Number(-2, 11), "-2e11");
switch (scale)
{
case MantissaRange::MantissaScale::Small:
test(Number::min(), "1e-32753");
test(Number::max(), "9999999999999999e32768");
test(Number::lowest(), "-9999999999999999e32768");
{
NumberRoundModeGuard const mg(Number::RoundingMode::TowardsZero);
auto const maxMantissa = Number::maxMantissa();
BEAST_EXPECT(maxMantissa == 9'999'999'999'999'999);
test(
Number{false, (maxMantissa * 1000) + 999, -3, Number::Normalized()},
"9999999999999999");
test(
Number{true, (maxMantissa * 1000) + 999, -3, Number::Normalized()},
"-9999999999999999");
test(Number{std::numeric_limits<std::int64_t>::max(), -3}, "9223372036854775");
test(
-(Number{std::numeric_limits<std::int64_t>::max(), -3}),
"-9223372036854775");
test(
Number{std::numeric_limits<std::int64_t>::min(), 0}, "-9223372036854775e3");
test(
-(Number{std::numeric_limits<std::int64_t>::min(), 0}),
"9223372036854775e3");
}
break;
case MantissaRange::MantissaScale::LargeLegacy:
case MantissaRange::MantissaScale::Large:
// Test the edges
// ((exponent < -(28)) || (exponent > -(8)))))
test(Number::min(), "1e-32750");
test(Number::max(), "9223372036854775807e32768");
test(Number::lowest(), "-9223372036854775807e32768");
{
NumberRoundModeGuard const mg(Number::RoundingMode::TowardsZero);
auto const maxMantissa = Number::maxMantissa();
BEAST_EXPECT(maxMantissa == 9'999'999'999'999'999'999ULL);
test(
Number{false, maxMantissa, 0, Number::Normalized{}}, "9999999999999999990");
test(
Number{true, maxMantissa, 0, Number::Normalized{}}, "-9999999999999999990");
test(
Number{std::numeric_limits<std::int64_t>::max(), 0}, "9223372036854775807");
test(
-(Number{std::numeric_limits<std::int64_t>::max(), 0}),
"-9223372036854775807");
// 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");
test(
-(Number{std::numeric_limits<std::int64_t>::min(), 0}),
"9223372036854775800");
}
test(
Number{std::numeric_limits<std::int64_t>::max(), 0} + 1, "9223372036854775810");
test(
-(Number{std::numeric_limits<std::int64_t>::max(), 0} + 1),
"-9223372036854775810");
break;
default:
BEAST_EXPECT(false);
}
}
void
testRelationals()
{
testcase << "test_relationals " << to_string(Number::getMantissaScale());
BEAST_EXPECT(!(Number{100} < Number{10}));
BEAST_EXPECT(Number{100} > Number{10});
BEAST_EXPECT(Number{100} >= Number{10});
BEAST_EXPECT(!(Number{100} <= Number{10}));
}
void
testStream()
{
testcase << "test_stream " << to_string(Number::getMantissaScale());
Number const x{100};
std::ostringstream os;
os << x;
BEAST_EXPECT(os.str() == to_string(x));
}
void
testIncDec()
{
testcase << "test_inc_dec " << to_string(Number::getMantissaScale());
Number x{100};
Number const y = +x;
BEAST_EXPECT(x == y);
BEAST_EXPECT(x++ == y);
BEAST_EXPECT(x == Number{101});
BEAST_EXPECT(x-- == Number{101});
BEAST_EXPECT(x == y);
}
void
testToStAmount()
{
NumberSO const stNumberSO{true};
Issue const issue;
Number const n{7'518'783'80596, -5};
SaveNumberRoundMode const save{Number::setround(Number::RoundingMode::ToNearest)};
auto res2 = STAmount{issue, n};
BEAST_EXPECT(res2 == STAmount{7518784});
Number::setround(Number::RoundingMode::TowardsZero);
res2 = STAmount{issue, n};
BEAST_EXPECT(res2 == STAmount{7518783});
Number::setround(Number::RoundingMode::Downward);
res2 = STAmount{issue, n};
BEAST_EXPECT(res2 == STAmount{7518783});
Number::setround(Number::RoundingMode::Upward);
res2 = STAmount{issue, n};
BEAST_EXPECT(res2 == STAmount{7518784});
}
void
testTruncate()
{
BEAST_EXPECT(Number(25, +1).truncate() == Number(250, 0));
BEAST_EXPECT(Number(25, 0).truncate() == Number(25, 0));
BEAST_EXPECT(Number(25, -1).truncate() == Number(2, 0));
BEAST_EXPECT(Number(25, -2).truncate() == Number(0, 0));
BEAST_EXPECT(Number(99, -2).truncate() == Number(0, 0));
BEAST_EXPECT(Number(-25, +1).truncate() == Number(-250, 0));
BEAST_EXPECT(Number(-25, 0).truncate() == Number(-25, 0));
BEAST_EXPECT(Number(-25, -1).truncate() == Number(-2, 0));
BEAST_EXPECT(Number(-25, -2).truncate() == Number(0, 0));
BEAST_EXPECT(Number(-99, -2).truncate() == Number(0, 0));
BEAST_EXPECT(Number(0, 0).truncate() == Number(0, 0));
BEAST_EXPECT(Number(0, 30000).truncate() == Number(0, 0));
BEAST_EXPECT(Number(0, -30000).truncate() == Number(0, 0));
BEAST_EXPECT(Number(100, -30000).truncate() == Number(0, 0));
BEAST_EXPECT(Number(100, -30000).truncate() == Number(0, 0));
BEAST_EXPECT(Number(-100, -30000).truncate() == Number(0, 0));
BEAST_EXPECT(Number(-100, -30000).truncate() == Number(0, 0));
}
void
testRounding()
{
// Test that rounding works as expected.
testcase("Rounding");
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);
BEAST_EXPECTS(
res == val,
to_string(num) + " with mode " + std::to_string(static_cast<int>(mode)) +
" expected " + std::to_string(val) + " got " + std::to_string(res));
}
}
}
void
testInt64()
{
auto const scale = Number::getMantissaScale();
testcase << "std::int64_t " << to_string(scale);
// Control case
BEAST_EXPECT(Number::maxMantissa() > 10);
Number const ten{10};
BEAST_EXPECT(ten.exponent() <= 0);
if (scale == MantissaRange::MantissaScale::Small)
{
BEAST_EXPECT(std::numeric_limits<std::int64_t>::max() > kInitialXrp.drops());
BEAST_EXPECT(Number::maxMantissa() < kInitialXrp.drops());
Number const initalXrp{kInitialXrp};
BEAST_EXPECT(initalXrp.exponent() > 0);
Number const maxInt64{Number::kMaxRep};
BEAST_EXPECT(maxInt64.exponent() > 0);
// 85'070'591'730'234'615'865'843'651'857'942'052'864 - 38 digits
BEAST_EXPECT((power(maxInt64, 2) == Number{85'070'591'730'234'62, 22}));
Number const max = Number{false, Number::maxMantissa(), 0, Number::Normalized{}};
BEAST_EXPECT(max.exponent() <= 0);
// 99'999'999'999'999'980'000'000'000'000'001 - 32 digits
BEAST_EXPECT((power(max, 2) == Number{99'999'999'999'999'98, 16}));
}
else
{
BEAST_EXPECT(std::numeric_limits<std::int64_t>::max() > kInitialXrp.drops());
BEAST_EXPECT(Number::maxMantissa() > kInitialXrp.drops());
Number const initalXrp{kInitialXrp};
BEAST_EXPECT(initalXrp.exponent() <= 0);
Number const maxInt64{Number::kMaxRep};
BEAST_EXPECT(maxInt64.exponent() <= 0);
// 85'070'591'730'234'615'847'396'907'784'232'501'249 - 38 digits
BEAST_EXPECT((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{}};
BEAST_EXPECT(max.mantissa() == maxMantissa / 10);
BEAST_EXPECT(max.exponent() == 1);
// 99'999'999'999'999'999'800'000'000'000'000'000'100 - also 38
// digits
BEAST_EXPECT(
(power(max, 2) == Number{false, (maxMantissa / 10) - 1, 20, Number::Normalized{}}));
}
}
void
testUpwardRoundsDown()
{
auto const scale = Number::getMantissaScale();
{
testcase << "upward rounding produces a value below exact at kMaxRep cusp "
<< to_string(scale);
NumberRoundModeGuard const rg{Number::RoundingMode::Upward};
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 storedValue = toBigInt(product);
BigInt const signedDifference = storedValue - exactProduct;
log << "\n"
<< " 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";
log.flush();
switch (scale)
{
case MantissaRange::MantissaScale::Large:
BEAST_EXPECT(signedDifference >= 0);
BEAST_EXPECT(signedDifference < pow10<BigInt>(product.exponent()));
BEAST_EXPECT(
product.mantissa() == (std::numeric_limits<std::int64_t>::max() / 10) + 1);
BEAST_EXPECT(product.exponent() == 19);
break;
case MantissaRange::MantissaScale::LargeLegacy:
BEAST_EXPECT(signedDifference < 0);
BEAST_EXPECT(
product.mantissa() ==
(std::numeric_limits<std::int64_t>::max() / 100) * 100);
BEAST_EXPECT(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
BEAST_EXPECT(signedDifference >= 0);
BEAST_EXPECT(
product.mantissa() ==
(std::numeric_limits<std::int64_t>::max() / 1000) + 3);
BEAST_EXPECT(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
*/
testcase << "operator/= Upward on Large returns value < truth " << to_string(scale);
NumberRoundModeGuard const roundGuard{Number::RoundingMode::Upward};
constexpr std::int64_t aValue = 2LL;
constexpr std::int64_t bValue = 1'000'000'000'000'000'007LL;
// bValue = 10^18 + 7 (prime, in [minMantissa, kMaxRep]).
Number const a{aValue, 0};
Number const b{bValue, 0};
Number const quotient = a / b;
dec const exact = dec(aValue) / dec(bValue);
dec const stored = dec(quotient.mantissa()) * pow10(quotient.exponent());
dec const diff = stored - exact;
log << "\n"
<< " a = " << aValue << "\n"
<< " b = " << bValue << "\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";
log.flush();
// Upward invariant: stored >= exact. Bug: stored < exact.
switch (scale)
{
case MantissaRange::MantissaScale::Large:
BEAST_EXPECT(stored >= exact);
BEAST_EXPECT(diff < pow10(quotient.exponent()));
break;
case MantissaRange::MantissaScale::LargeLegacy:
BEAST_EXPECT(stored < exact);
BEAST_EXPECT(diff >= -pow10(quotient.exponent()));
break;
case MantissaRange::MantissaScale::Small:
// Small mantissa doesn't have the correction for
// dropped remainders
BEAST_EXPECT(stored < exact);
break;
}
}
{
/* Companion test case for Upward positive operator/=: Downward negative
*/
testcase << "operator/= Downward on Large returns value < truth " << to_string(scale);
NumberRoundModeGuard const roundGuard{Number::RoundingMode::Downward};
constexpr std::int64_t aValue = -2LL;
constexpr std::int64_t bValue = 1'000'000'000'000'000'007LL;
// bValue = 10^18 + 7 (prime, in [minMantissa, kMaxRep]).
Number const a{aValue, 0};
Number const b{bValue, 0};
Number const quotient = a / b;
dec const exact = dec(aValue) / dec(bValue);
dec const stored = dec(quotient.mantissa()) * pow10(quotient.exponent());
dec const diff = stored - exact;
log << "\n"
<< " a = " << aValue << "\n"
<< " b = " << bValue << "\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";
log.flush();
// invariant: stored <= exact. Bug: stored > exact.
switch (scale)
{
case MantissaRange::MantissaScale::Large:
BEAST_EXPECT(stored <= exact);
BEAST_EXPECT(diff > -pow10(quotient.exponent()));
break;
case MantissaRange::MantissaScale::LargeLegacy:
BEAST_EXPECT(stored > exact);
BEAST_EXPECT(diff <= pow10(quotient.exponent()));
break;
case MantissaRange::MantissaScale::Small:
// Small mantissa doesn't have the correction for
// dropped remainders
BEAST_EXPECT(stored < exact);
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.
*/
testcase << "operator/= ToNearest on Large returns value < truth " << to_string(scale);
NumberRoundModeGuard const roundGuard{Number::RoundingMode::ToNearest};
constexpr std::int64_t aValue = 1'269'917'268'816'087'809LL;
constexpr std::int64_t bValue = 3'458'525'013'821'685'511LL;
// bValue = 10^18 + 7 (prime, in [minMantissa, kMaxRep]).
Number const a{aValue, 0};
Number const b{bValue, 0};
Number const quotient = a / b;
dec const exact = dec(aValue) / dec(bValue);
dec const stored = dec(quotient.mantissa()) * pow10(quotient.exponent());
dec const diff = stored - exact;
log << "\n"
<< " a = " << aValue << "\n"
<< " b = " << bValue << "\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";
log.flush();
// invariant: stored >= exact. Bug: stored < exact.
switch (scale)
{
case MantissaRange::MantissaScale::Large:
BEAST_EXPECT(stored >= exact);
BEAST_EXPECT(diff < pow10(quotient.exponent()));
break;
case MantissaRange::MantissaScale::LargeLegacy:
BEAST_EXPECT(stored < exact);
BEAST_EXPECT(diff >= -pow10(quotient.exponent()));
break;
case MantissaRange::MantissaScale::Small:
// Small mantissa doesn't have the correction for
// dropped remainders
BEAST_EXPECT(stored < exact);
break;
}
}
{
testcase << "normalization cusp: ToNearest and Downward disagree " << to_string(scale);
constexpr auto kMaxRep = Number::kMaxRep;
// Both ToNearest and Downward should round to `below`
auto const 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::Unchecked{}};
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);
log << "\n"
<< " 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";
log.flush();
switch (scale)
{
case MantissaRange::MantissaScale::Small:
// With the small mantissa, everything rounds up
// Upward round UP
BEAST_EXPECT(upward > above);
// ToNearest rounds UP when the DOWN neighbor is strictly closer
BEAST_EXPECT(toNearest > above);
BEAST_EXPECT(toNearest == below);
// Downward undershoots: it returns a value below `below`
BEAST_EXPECT(downward < below);
// Both should have given the same answer, but they differ
BEAST_EXPECT(toNearest > downward);
break;
case MantissaRange::MantissaScale::LargeLegacy:
// Upward round UP
BEAST_EXPECT(upward == above);
// ToNearest rounds UP when the DOWN neighbor is strictly closer
BEAST_EXPECT(toNearest == above);
BEAST_EXPECT(toNearest > below);
// Downward undershoots: it returns a value below `below`
BEAST_EXPECT(downward < below);
// Both should have given the same answer, but they differ
BEAST_EXPECT(toNearest > downward);
break;
default:
// Upward round UP
BEAST_EXPECT(upward == above);
// ToNearest rounds UP when the DOWN neighbor is strictly closer
BEAST_EXPECT(toNearest != above);
BEAST_EXPECT(toNearest == below);
// Downward undershoots: it returns a value below `below`
BEAST_EXPECT(downward == below);
// Both should have given the same answer, but they differ
BEAST_EXPECT(toNearest == downward);
}
}
{
testcase << "operator+ TowardsZero rounds away from zero " << to_string(scale);
Number const a{1LL, 20};
Number const b{-1'000'000'000'000'000'001LL};
BEAST_EXPECT(toBigInt(a) == BigInt{"100000000000000000000"});
if (scale != MantissaRange::MantissaScale::Small)
BEAST_EXPECT(toBigInt(b) == BigInt{"-1000000000000000001"});
else
BEAST_EXPECT(toBigInt(b) == BigInt{"-1000000000000000000"});
Number sum;
{
NumberRoundModeGuard const roundGuard{Number::RoundingMode::TowardsZero};
sum = a + b;
}
BigInt const exact = toBigInt(a) + toBigInt(b);
BigInt const stored = toBigInt(sum);
BigInt const diff = stored - exact;
log << "\n a = " << a << "\n b = " << b
<< "\n exact a + b = " << exact.str() << "\n TowardsZero = " << stored.str()
<< "\n difference = " << diff.str() << "\n";
log.flush();
if (scale == MantissaRange::MantissaScale::Small)
{
BEAST_EXPECT(stored == exact);
}
else if (scale == MantissaRange::MantissaScale::LargeLegacy)
{
BEAST_EXPECT(stored > exact);
}
else
{
BEAST_EXPECT(stored < exact);
BEAST_EXPECT(diff < 0);
BEAST_EXPECT(-diff < pow10<BigInt>(sum.exponent()));
}
}
}
void
run() override
{
for (auto const scale : MantissaRange::getAllScales())
{
NumberMantissaScaleGuard const sg(scale);
testZero();
testLimits();
testToString();
testAdd();
testSub();
testMul();
testDiv();
testRoot();
testRoot2();
testPower1();
testPower2();
testConversions();
testToInteger();
testSquelch();
testRelationals();
testStream();
testIncDec();
testToStAmount();
testTruncate();
testRounding();
testInt64();
testUpwardRoundsDown();
}
}
};
BEAST_DEFINE_TESTSUITE(Number, basics, xrpl);
} // namespace xrpl