[WIP] Checkpoint

This commit is contained in:
Ed Hennis
2025-11-26 00:21:40 -05:00
parent 359622981e
commit 3be46bc7d9
7 changed files with 476 additions and 346 deletions

View File

@@ -52,6 +52,7 @@ using numberint = __int128_t;
struct MantissaRange
{
using rep = std::int64_t;
using internalrep = numberint;
enum mantissa_scale { small, large };
@@ -63,7 +64,7 @@ struct MantissaRange
{
}
internalrep min;
rep min;
internalrep max;
int log;
mantissa_scale scale;
@@ -74,11 +75,9 @@ class Number
using uint128_t = numberuint;
using int128_t = numberint;
public:
using rep = std::int64_t;
using rep = MantissaRange::rep;
using internalrep = MantissaRange::internalrep;
private:
internalrep mantissa_{0};
int exponent_{std::numeric_limits<int>::lowest()};
@@ -93,11 +92,25 @@ public:
explicit unchecked() = default;
};
// Like unchecked, normalized is used with the ctors that take an
// internalrep mantissa. Unlike unchecked, those ctors will normalize the
// value.
// Only unit tests are expected to use this class
struct normalized
{
explicit normalized() = default;
};
explicit constexpr Number() = default;
Number(rep mantissa);
explicit Number(internalrep mantissa, int exponent);
explicit constexpr Number(internalrep mantissa, int exponent, unchecked) noexcept;
explicit Number(rep mantissa, int exponent);
explicit constexpr Number(
internalrep mantissa,
int exponent,
unchecked) noexcept;
// Only unit tests are expected to use this ctor
explicit Number(internalrep mantissa, int exponent, normalized);
constexpr rep
mantissa() const noexcept;
@@ -255,7 +268,8 @@ public:
static void
setMantissaScale(MantissaRange::mantissa_scale scale);
inline static internalrep
template <class T = rep>
inline static T
minMantissa()
{
return range_.get().min;
@@ -331,16 +345,31 @@ private:
bool
isnormal() const noexcept;
// Copy the number, but modify the exponent by "exponentDelta". Because the
// mantissa doesn't change, the result will be "mostly" normalized, but the
// exponent could go out of range, so it will be checked.
Number
shiftExponent(int exponentDelta) const;
class Guard;
};
inline constexpr Number::Number(internalrep mantissa, int exponent, unchecked) noexcept
inline constexpr Number::Number(
internalrep mantissa,
int exponent,
unchecked) noexcept
: mantissa_{mantissa}, exponent_{exponent}
{
}
inline Number::Number(internalrep mantissa, int exponent)
: mantissa_{mantissa}, exponent_{exponent}
inline Number::Number(internalrep mantissa, int exponent, normalized)
: Number(mantissa, exponent, unchecked{})
{
normalize();
}
inline Number::Number(rep mantissa, int exponent)
: Number(mantissa, exponent, normalized{})
{
normalize();
}
@@ -349,35 +378,36 @@ inline Number::Number(rep mantissa) : Number{mantissa, 0}
{
}
inline
constexpr Number::rep
inline constexpr Number::rep
Number::mantissa() const noexcept
{
auto m = mantissa_;
if (m > maxRep)
while (m > maxRep)
{
XRPL_ASSERT_PARTS(
m % 10 == 0,
!isnormal() || m % 10 == 0,
"ripple::Number::mantissa",
"large mantissa has no remainder");
"large normalized mantissa has no remainder");
m /= 10;
}
return static_cast<Number::rep>(m);
}
inline
constexpr int
inline constexpr int
Number::exponent() const noexcept
{
if (mantissa_ > maxRep)
auto m = mantissa_;
auto e = exponent_;
while (m > maxRep)
{
XRPL_ASSERT_PARTS(
mantissa_ % 10 == 0,
!isnormal() || m % 10 == 0,
"ripple::Number::exponent",
"large mantissa has no remainder");
return exponent_ + 1;
"large normalized mantissa has no remainder");
m /= 10;
++e;
}
return exponent_;
return e;
}
inline constexpr Number
@@ -486,6 +516,7 @@ Number::isnormal() const noexcept
MantissaRange const& range = range_;
auto const abs_m = mantissa_ < 0 ? -mantissa_ : mantissa_;
return range.min <= abs_m && abs_m <= range.max &&
(abs_m <= maxRep || abs_m % 10 == 0) &&
minExponent <= exponent_ && exponent_ <= maxExponent;
}

View File

@@ -262,13 +262,14 @@ Number::normalize(
exponent_ = zero.exponent_;
return;
}
// When using the largeRange, m needs fit within an int64, even if
// When using the largeRange, "m" needs fit within an int64, even if
// the final mantissa_ is going to end up larger to fit within the range.
// Cut it down here so that the rounding will be done while it's smaller.
//
// Example: 9,900,000,000,000,555,555 > 9,223,372,036,854,775,808,
// so m will end up a 990,000,000,000,055,555. Then that value will be
// rounded to 990,000,000,000,055,555 or 990,000,000,000,055,556.
// so "m" will be modified to 990,000,000,000,055,555. Then that value
// will be rounded to 990,000,000,000,055,555 or
// 990,000,000,000,055,556, depending on the rounding mode. Finally,
// mantissa_ will be m*10 so it fits within the range, and end up as
// 9,900,000,000,000,555,550 or 9,900,000,000,000,555,560.
// mantissa() will return mantissa_ / 10, and exponent() will return
@@ -301,7 +302,7 @@ Number::normalize(
throw std::overflow_error("Number::normalize 2");
if (mantissa_ < minMantissa)
{
// When using the largeRange, the intermediate M needs fit within an
// When using the largeRange, the intermediate "m" needs fit within an
// int64, but mantissa_ needs to fit within the minMantissa /
// maxMantissa range.
mantissa_ *= 10;
@@ -323,6 +324,29 @@ Number::normalize()
mantissa_, exponent_, Number::minMantissa(), Number::maxMantissa());
}
// Copy the number, but set a new exponent. Because the mantissa doesn't change,
// the result will be "mostly" normalized, but the exponent could go out of
// range.
Number
Number::shiftExponent(int exponentDelta) const
{
XRPL_ASSERT_PARTS(
isnormal(), "ripple::Number::shiftExponent", "normalized");
auto const newExponent = exponent_ + exponentDelta;
if (newExponent >= maxExponent)
throw std::overflow_error("Number::shiftExponent");
if (newExponent < minExponent)
{
return Number{};
}
Number const result{mantissa_, newExponent, unchecked{}};
XRPL_ASSERT_PARTS(
result.isnormal(),
"ripple::Number::shiftExponent",
"result is normalized");
return result;
}
Number&
Number::operator+=(Number const& y)
{
@@ -442,6 +466,10 @@ Number::operator+=(Number const& y)
}
mantissa_ = xm * xn;
exponent_ = xe;
normalize();
XRPL_ASSERT(
isnormal() || *this == Number{},
"ripple::Number::operator+=(Number) : result is normal");
return *this;
}
@@ -542,6 +570,7 @@ Number::operator*=(Number const& y)
std::to_string(xe));
mantissa_ = xm * zn;
exponent_ = xe;
normalize();
XRPL_ASSERT(
isnormal() || *this == Number{},
"ripple::Number::operator*=(Number) : result is normal");
@@ -583,7 +612,7 @@ Number::operator/=(Number const& y)
uint128_t const f =
small ? 100'000'000'000'000'000 : 10'000'000'000'000'000'000ULL;
XRPL_ASSERT_PARTS(
f >= Number::minMantissa() * 10,
f >= Number::minMantissa<internalrep>() * 10,
"Number::operator/=",
"factor expected size");
@@ -841,7 +870,9 @@ root(Number f, unsigned d)
return di - k2;
}();
e += ex;
f = Number{f.mantissa_, f.exponent_ - e}; // f /= 10^e;
f = f.shiftExponent(-e); // f /= 10^e;
XRPL_ASSERT_PARTS(
f.isnormal(), "ripple::root(Number, unsigned)", "f is normalized");
bool neg = false;
if (f < Number{})
{
@@ -873,7 +904,12 @@ root(Number f, unsigned d)
} while (r != rm1 && r != rm2);
// return r * 10^(e/d) to reverse scaling
return Number{r.mantissa_, r.exponent_ + e / di};
auto const result = r.shiftExponent(e / di);
XRPL_ASSERT_PARTS(
result.isnormal(),
"ripple::root(Number, unsigned)",
"result is normalized");
return result;
}
Number
@@ -892,7 +928,8 @@ root2(Number f)
auto e = f.exponent() + Number::mantissaLog() + 1;
if (e % 2 != 0)
++e;
f = Number{f.mantissa_, f.exponent_ - e}; // f /= 10^e;
f = f.shiftExponent(-e); // f /= 10^e;
XRPL_ASSERT_PARTS(f.isnormal(), "ripple::root2(Number)", "f is normalized");
// Quadratic least squares curve fit of f^(1/d) in the range [0, 1]
auto const D = 105;
@@ -913,7 +950,11 @@ root2(Number f)
} while (r != rm1 && r != rm2);
// return r * 10^(e/2) to reverse scaling
return Number{r.mantissa_, r.exponent_ + e / 2};
auto const result = r.shiftExponent(e / 2);
XRPL_ASSERT_PARTS(
result.isnormal(), "ripple::root2(Number)", "result is normalized");
return result;
}
// Returns f^(n/d)

View File

@@ -1,5 +1,6 @@
#include <xrpl/protocol/IOUAmount.h>
//
// Do not remove. Forces IOUAmount.h to stay first, to verify it can compile
// without any hidden dependencies
#include <xrpl/basics/LocalValue.h>
#include <xrpl/basics/Number.h>
#include <xrpl/basics/contract.h>

View File

@@ -1,5 +1,6 @@
#include <xrpl/protocol/Rules.h>
//
// Do not remove. Forces Rules.h to stay first, to verify it can compile
// without any hidden dependencies
#include <xrpl/basics/LocalValue.h>
#include <xrpl/basics/Number.h>
#include <xrpl/basics/base_uint.h>

View File

@@ -50,27 +50,16 @@ STNumber::add(Serializer& s) const
XRPL_ASSERT(
getFName().fieldType == getSType(),
"ripple::STNumber::add : field type match");
if (value_.mantissa() <= std::numeric_limits<std::int64_t>::max() &&
value_.mantissa() >= std::numeric_limits<std::int64_t>::min())
{
// If the mantissa fits in the range of std::int64_t, write it directly.
// This preserves the maximum available precision.
// With the small range, all numbers should be written this way. With
// the large range, it's likely that most numbers will be written this
// way.
s.add64(static_cast<std::int64_t>(value_.mantissa()));
s.add32(value_.exponent());
}
else
{
constexpr std::int64_t min = 100'000'000'000'000'000LL;
constexpr std::int64_t max = min * 10 - 1;
static_assert(
min < (std::numeric_limits<std::int64_t>::max() - 1 / 10));
auto const [mantissa, exponent] = value_.normalizeToRange(min, max);
s.add64(mantissa);
s.add32(exponent);
}
auto const mantissa = value_.mantissa();
auto const exponent = value_.exponent();
XRPL_ASSERT_PARTS(
mantissa <= std::numeric_limits<std::int64_t>::max() &&
mantissa >= std::numeric_limits<std::int64_t>::min(),
"ripple::STNumber::add",
"mantissa in valid range");
s.add64(mantissa);
s.add32(exponent);
}
Number const&
@@ -202,7 +191,7 @@ numberFromJson(SField const& field, Json::Value const& value)
// Number mantissas are much bigger than the allowable parsed values, so
// it can't be out of range.
static_assert(
std::numeric_limits<numberint>::max() >
std::numeric_limits<numberint>::max() >=
std::numeric_limits<decltype(parts.mantissa)>::max());
}
else
@@ -214,7 +203,7 @@ numberFromJson(SField const& field, Json::Value const& value)
if (parts.negative)
mantissa = -mantissa;
return STNumber{field, Number{mantissa, parts.exponent}};
return STNumber{field, Number{mantissa, parts.exponent, Number::normalized{}}};
}
} // namespace ripple

View File

@@ -34,43 +34,52 @@ public:
auto const scale = Number::getMantissaScale();
testcase << "test_limits " << to_string(scale);
bool caught = false;
auto const minMantissa = Number::minMantissa();
auto const minMantissa = Number::minMantissa<numberint>();
try
{
Number x{minMantissa * 10, 32768};
Number x = Number{minMantissa * 10, 32768, Number::normalized{}};
}
catch (std::overflow_error const&)
{
caught = true;
}
BEAST_EXPECT(caught);
Number x{minMantissa * 10, 32767};
BEAST_EXPECT((x == Number{minMantissa, 32768}));
Number z{minMantissa, -32769};
BEAST_EXPECT(z == Number{});
Number y{minMantissa * 1'000 + 1'500, 32000};
BEAST_EXPECT((y == Number{minMantissa + 2, 32003}));
Number m{std::numeric_limits<std::int64_t>::min()};
auto test = [this](auto const& x, auto const& y) {
auto const result = x == y;
std::stringstream ss;
ss << x << " == " << y << " -> " << (result ? "true" : "false");
BEAST_EXPECTS(result, ss.str());
};
test(
Number{minMantissa * 10, 32767, Number::normalized{}},
Number{minMantissa, 32768, Number::normalized{}});
test(Number{minMantissa, -32769, Number::normalized{}}, Number{});
test(
Number{minMantissa * 1'000 + 1'500, 32000, Number::normalized{}},
Number{minMantissa + 2, 32003, Number::normalized{}});
// 9,223,372,036,854,775,808
BEAST_EXPECT(
(m ==
Number{
scale == MantissaRange::small
? -9'223'372'036'854'776
: std::numeric_limits<std::int64_t>::min(),
18 - Number::mantissaLog()}));
Number M{std::numeric_limits<std::int64_t>::max()};
BEAST_EXPECT(
(M ==
Number{
scale == MantissaRange::small
? 9'223'372'036'854'776
: std::numeric_limits<std::int64_t>::max(),
18 - Number::mantissaLog()}));
test(
Number{std::numeric_limits<std::int64_t>::min()},
Number{
scale == MantissaRange::small
? -9'223'372'036'854'776
: std::numeric_limits<std::int64_t>::min(),
18 - Number::mantissaLog()});
test(
Number{std::numeric_limits<std::int64_t>::max()},
Number{
scale == MantissaRange::small
? 9'223'372'036'854'776
: std::numeric_limits<std::int64_t>::max(),
18 - Number::mantissaLog()});
caught = false;
try
{
Number q{minMantissa * 100 - 1, 32767};
Number q =
Number{minMantissa * 100 - 1, 32767, Number::normalized{}};
}
catch (std::overflow_error const&)
{
@@ -79,92 +88,6 @@ public:
BEAST_EXPECT(caught);
}
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");
switch (scale)
{
case MantissaRange::small:
test(Number(2, 20), "2000000000000000e5");
test(Number(-2, -20), "-2000000000000000e-35");
// Test the edges
// ((exponent < -(25)) || (exponent > -(5)))))
test(Number(2, -10), "0.0000000002");
test(Number(2, -11), "2000000000000000e-26");
test(Number(-2, 10), "-20000000000");
test(Number(-2, 11), "-2000000000000000e-4");
test(Number::min(), "1000000000000000e-32768");
test(Number::max(), "9999999999999999e32768");
test(Number::lowest(), "-9999999999999999e32768");
{
NumberRoundModeGuard mg(Number::towards_zero);
test(
Number{
numberuint(9'999'999'999'999'999) * 1000 + 999,
-3},
"9999999999999999");
test(
-(Number{
numberuint(9'999'999'999'999'999) * 1000 + 999,
-3}),
"-9999999999999999");
}
break;
case MantissaRange::large:
test(Number(2, 20), "2000000000000000000e2");
test(Number(-2, -20), "-2000000000000000000e-38");
// Test the edges
// ((exponent < -(28)) || (exponent > -(8)))))
test(Number(2, -10), "0.0000000002");
test(Number(2, -11), "2000000000000000000e-29");
test(Number(-2, 10), "-20000000000");
test(Number(-2, 11), "-2000000000000000000e-7");
test(Number::min(), "1000000000000000000e-32768");
test(Number::max(), "9999999999999999999e32768");
test(Number::lowest(), "-9999999999999999999e32768");
{
NumberRoundModeGuard mg(Number::towards_zero);
test(
Number{
numberuint(9'999'999'999'999'999) * 1000 + 999,
0},
"9999999999999999990");
test(
-(Number{
numberuint(9'999'999'999'999'999) * 1000 + 999,
0}),
"-9999999999999999990");
}
break;
break;
default:
BEAST_EXPECT(false);
}
}
void
test_add()
{
@@ -206,10 +129,15 @@ public:
{Number{-1'000'000'000'000'000, -15},
Number{6'555'555'555'555'555, -29},
Number{
-(numberint(9'999'999'999'999'344) * 1'000 + 444), -19}},
-(numberint(9'999'999'999'999'344) * 1'000 + 444),
-19,
Number::normalized{}}},
{Number{-6'555'555'555'555'555, -29},
Number{1'000'000'000'000'000, -15},
Number{numberint(9'999'999'999'999'344) * 1'000 + 444, -19}},
Number{
numberint(9'999'999'999'999'344) * 1'000 + 444,
-19,
Number::normalized{}}},
{Number{}, Number{5}, Number{5}},
{Number{5}, Number{}, Number{5}},
{Number{5'555'555'555'555'555'000, -32768},
@@ -228,17 +156,28 @@ public:
{Number{-1'000'000'000'000'000'000, -18},
Number{6'555'555'555'555'555'555, -35},
Number{
-(numberint(9'999'999'999'999'999) * 1'000 + 344), -19}},
-(numberint(9'999'999'999'999'999) * 1'000 + 344),
-19,
Number::normalized{}}},
{Number{-6'555'555'555'555'555'555, -35},
Number{1'000'000'000'000'000'000, -18},
Number{numberint(9'999'999'999'999'999) * 1'000 + 344, -19}},
Number{
numberint(9'999'999'999'999'999) * 1'000 + 344,
-19,
Number::normalized{}}},
{Number{}, Number{5}, Number{5}},
{Number{5'555'555'555'555'555'555, -32768},
Number{-5'555'555'555'555'555'554, -32768},
Number{0}},
{Number{-(numberint(9'999'999'999'999'999) * 1'000 + 999), -37},
{Number{
-(numberint(9'999'999'999'999'999) * 1'000 + 999),
-37,
Number::normalized{}},
Number{1'000'000'000'000'000'000, -18},
Number{numberint(9'999'999'999'999'999) * 1'000 + 990, -19}}});
Number{
numberint(9'999'999'999'999'999) * 1'000 + 990,
-19,
Number::normalized{}}}});
auto test = [this](auto const& c) {
for (auto const& [x, y, z] : c)
{
@@ -256,27 +195,8 @@ public:
bool caught = false;
try
{
if (scale == MantissaRange::small)
Number{9'999'999'999'999'999, 32768} +
Number{5'000'000'000'000'000, 32767};
else
Number{numberint(9'999'999'999'999'999) * 1'000, 32768} +
Number{5'000'000'000'000'000'000, 32767};
}
catch (std::overflow_error const&)
{
caught = true;
}
BEAST_EXPECT(caught);
}
if (scale != MantissaRange::small)
{
bool caught = false;
try
{
Number{
numberuint(9'999'999'999'999'999) * 1000 + 999, 32768} +
Number{5'000'000'000'000'000'000, 32767};
Number{Number::maxMantissa(), 32768, Number::normalized{}} +
Number{Number::minMantissa() * 5, 32767};
}
catch (std::overflow_error const&)
{
@@ -315,11 +235,16 @@ public:
// with larger mantissa
{{Number{1'000'000'000'000'000, -15},
Number{6'555'555'555'555'555, -29},
Number{numberint(9'999'999'999'999'344) * 1'000 + 444, -19}},
Number{
numberint(9'999'999'999'999'344) * 1'000 + 444,
-19,
Number::normalized{}}},
{Number{6'555'555'555'555'555, -29},
Number{1'000'000'000'000'000, -15},
Number{
-(numberint(9'999'999'999'999'344) * 1'000 + 444), -19}},
-(numberint(9'999'999'999'999'344) * 1'000 + 444),
-19,
Number::normalized{}}},
{Number{1'000'000'000'000'000, -15},
Number{1'000'000'000'000'000, -15},
Number{0}},
@@ -332,11 +257,16 @@ public:
// 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{numberint(9'999'999'999'999'344) * 1'000 + 444, -19}},
Number{
numberint(9'999'999'999'999'344) * 1'000 + 444,
-19,
Number::normalized{}}},
{Number{6'555'555'555'555'555'555, -32},
Number{1'000'000'000'000'000'000, -18},
Number{
-(numberint(9'999'999'999'999'344) * 1'000 + 444), -19}},
-(numberint(9'999'999'999'999'344) * 1'000 + 444),
-19,
Number::normalized{}}},
{Number{1'000'000'000'000'000'000, -18},
Number{1'000'000'000'000'000'000, -18},
Number{0}},
@@ -383,6 +313,9 @@ public:
else
test(cLarge);
};
auto const maxMantissa = Number::maxMantissa();
auto const maxInt64 = std::numeric_limits<std::int64_t>::max();
saveNumberRoundMode save{Number::setround(Number::to_nearest)};
{
auto const cSmall = std::to_array<Case>({
@@ -423,7 +356,10 @@ public:
Number{1999999999999999862, -18}},
{Number{3214285714285706, -15},
Number{3111111111111119, -15},
Number{numberint(9'999'999'999'999'999) * 1000 + 579, -18}},
Number{
numberint(9'999'999'999'999'999) * 1000 + 579,
-18,
Number::normalized{}}},
{Number{1000000000000000000, -32768},
Number{1000000000000000000, -32768},
Number{0}},
@@ -442,10 +378,14 @@ public:
{Number{3214285714285714278, -18},
Number{3111111111111111119, -18},
Number{10, 0}},
// Maximum mantissa range
{Number{numberint(9'999'999'999'999'999) * 1000 + 999, 0},
Number{numberint(9'999'999'999'999'999) * 1000 + 999, 0},
Number{numberint(9'999'999'999'999'999) * 1000 + 998, 19}},
// Maximum mantissa range - rounds up to 1e19
{Number{maxMantissa, 0, Number::normalized{}},
Number{maxMantissa, 0, Number::normalized{}},
Number{1, 38}},
// Maximum int64 range
{Number{maxInt64, 0},
Number{maxInt64, 0},
Number{85'070'591'730'234'615'85, 19}},
});
tests(cSmall, cLarge);
}
@@ -474,37 +414,52 @@ public:
// 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{numberint(9999999999999999) * 1000 + 579, -18}},
{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}}});
{
{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{
numberint(9999999999999999) * 1000 + 579,
-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{maxMantissa, 0, Number::normalized{}},
Number{maxMantissa, 0, Number::normalized{}},
Number{maxMantissa / 10 - 1, 20, Number::normalized{}}},
// Maximum int64 range
// 85'070'591'730'234'615'847'396'907'784'232'501'249
{Number{maxInt64, 0},
Number{maxInt64, 0},
Number{85'070'591'730'234'615'84, 19}},
});
tests(cSmall, cLarge);
}
Number::setround(Number::downward);
@@ -532,38 +487,52 @@ public:
// 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{
numberint(9'999'999'999'999'999) * 1000 + 579, -18}},
{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}}});
{
{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{
numberint(9'999'999'999'999'999) * 1000 + 579,
-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{maxMantissa, 0, Number::normalized{}},
Number{maxMantissa, 0, Number::normalized{}},
Number{maxMantissa / 10 - 1, 20, Number::normalized{}}},
// Maximum int64 range
// 85'070'591'730'234'615'847'396'907'784'232'501'249
{Number{maxInt64, 0},
Number{maxInt64, 0},
Number{85'070'591'730'234'615'84, 19}},
});
tests(cSmall, cLarge);
}
Number::setround(Number::upward);
@@ -591,37 +560,49 @@ public:
// 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}}});
{
{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{maxMantissa, 0, Number::normalized{}},
Number{maxMantissa, 0, Number::normalized{}},
Number{1, 38}},
// Maximum int64 range
// 85'070'591'730'234'615'847'396'907'784'232'501'249
{Number{maxInt64, 0},
Number{maxInt64, 0},
Number{85'070'591'730'234'615'85, 19}},
});
tests(cSmall, cLarge);
}
testcase << "test_mul " << to_string(Number::getMantissaScale())
@@ -630,27 +611,8 @@ public:
bool caught = false;
try
{
if (scale == MantissaRange::small)
Number{9'999'999'999'999'999, 32768} *
Number{5'000'000'000'000'000, 32767};
else
Number{numberint(9'999'999'999'999'999) * 1'000, 32768} *
Number{5'000'000'000'000'000'000, 32767};
}
catch (std::overflow_error const&)
{
caught = true;
}
BEAST_EXPECT(caught);
}
if (scale != MantissaRange::small)
{
bool caught = false;
try
{
Number{
numberint(9'999'999'999'999'999) * 1000 + 999, 32768} +
Number{5'000'000'000'000'000'000, 32767};
Number{maxMantissa, 32768, Number::normalized{}} *
Number{Number::minMantissa() * 5, 32767};
}
catch (std::overflow_error const&)
{
@@ -676,6 +638,7 @@ public:
BEAST_EXPECTS(result == z, ss.str());
}
};
auto const maxMantissa = Number::maxMantissa();
auto tests = [&](auto const& cSmall, auto const& cLarge) {
if (scale == MantissaRange::small)
test(cSmall);
@@ -722,11 +685,9 @@ public:
{Number{1414213562373095049, -13},
Number{1414213562373095049, -13},
Number{1}},
{Number{numberint(9'999'999'999'999'999) * 1'000 + 999, 0},
{Number{maxMantissa, 0, Number::normalized{}},
Number{1'000'000'000'000'000'000},
Number{
numberint(9'999'999'999'999'999) * 1'000 + 999,
-18}}});
Number{maxMantissa, -18, Number::normalized{}}}});
tests(cSmall, cLarge);
}
testcase << "test_div " << to_string(Number::getMantissaScale())
@@ -771,11 +732,9 @@ public:
{Number{1414213562373095049, -13},
Number{1414213562373095049, -13},
Number{1}},
{Number{numberint(9'999'999'999'999'999) * 1'000 + 999, 0},
{Number{maxMantissa, 0, Number::normalized{}},
Number{1'000'000'000'000'000'000},
Number{
numberint(9'999'999'999'999'999) * 1'000 + 999,
-18}}});
Number{maxMantissa, -18, Number::normalized{}}}});
tests(cSmall, cLarge);
}
testcase << "test_div " << to_string(Number::getMantissaScale())
@@ -820,11 +779,9 @@ public:
{Number{1414213562373095049, -13},
Number{1414213562373095049, -13},
Number{1}},
{Number{numberint(9'999'999'999'999'999) * 1'000 + 999, 0},
{Number{maxMantissa, 0, Number::normalized{}},
Number{1'000'000'000'000'000'000},
Number{
numberint(9'999'999'999'999'999) * 1'000 + 999,
-18}}});
Number{maxMantissa, -18, Number::normalized{}}}});
tests(cSmall, cLarge);
}
testcase << "test_div " << to_string(Number::getMantissaScale())
@@ -869,11 +826,9 @@ public:
{Number{1414213562373095049, -13},
Number{1414213562373095049, -13},
Number{1}},
{Number{numberint(9'999'999'999'999'999) * 1'000 + 999, 0},
{Number{maxMantissa, 0, Number::normalized{}},
Number{1'000'000'000'000'000'000},
Number{
numberint(9'999'999'999'999'999) * 1'000 + 999,
-18}}});
Number{maxMantissa, -18, Number::normalized{}}}});
tests(cSmall, cLarge);
}
testcase << "test_div " << to_string(Number::getMantissaScale())
@@ -1002,10 +957,16 @@ public:
{Number{-64}, 3, Number{-262144}},
{Number{64},
11,
Number{numberint(73786976294838206) * 1000 + 464, 0}},
Number{
numberint(73786976294838206) * 1000 + 464,
0,
Number::normalized{}}},
{Number{-64},
11,
Number{-(numberint(73786976294838206) * 1000 + 464), 0}}};
-(Number{
numberint(73786976294838206) * 1000 + 464,
0,
Number::normalized{}})}};
for (auto const& [x, y, z] : c)
BEAST_EXPECT((power(x, y) == z));
}
@@ -1263,6 +1224,105 @@ public:
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");
switch (scale)
{
case MantissaRange::small:
test(Number(2, 20), "2000000000000000e5");
test(Number(-2, -20), "-2000000000000000e-35");
// Test the edges
// ((exponent < -(25)) || (exponent > -(5)))))
test(Number(2, -10), "0.0000000002");
test(Number(2, -11), "2000000000000000e-26");
test(Number(-2, 10), "-20000000000");
test(Number(-2, 11), "-2000000000000000e-4");
test(Number::min(), "1000000000000000e-32768");
test(Number::max(), "9999999999999999e32768");
test(Number::lowest(), "-9999999999999999e32768");
{
NumberRoundModeGuard mg(Number::towards_zero);
auto const maxMantissa = Number::maxMantissa();
BEAST_EXPECT(maxMantissa == 9'999'999'999'999'999);
test(
Number{
maxMantissa * 1000 + 999, -3, Number::normalized()},
"9999999999999999");
test(
-(Number{
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");
}
break;
case MantissaRange::large:
test(Number(2, 20), "2000000000000000000e2");
test(Number(-2, -20), "-2000000000000000000e-38");
// Test the edges
// ((exponent < -(28)) || (exponent > -(8)))))
test(Number(2, -10), "0.0000000002");
test(Number(2, -11), "2000000000000000000e-29");
test(Number(-2, 10), "-20000000000");
test(Number(-2, 11), "-2000000000000000000e-7");
test(Number::min(), "1000000000000000000e-32768");
test(Number::max(), "9999999999999999999e32768");
test(Number::lowest(), "-9999999999999999999e32768");
{
NumberRoundModeGuard mg(Number::towards_zero);
auto const maxMantissa = Number::maxMantissa();
BEAST_EXPECT(maxMantissa == 9'999'999'999'999'999'999);
test(
Number{maxMantissa, 0, Number::normalized{}},
"9999999999999999990");
test(
-(Number{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");
}
break;
default:
BEAST_EXPECT(false);
}
}
void
test_relationals()
{
@@ -1369,7 +1429,8 @@ public:
BEAST_EXPECT(
(power(maxInt64, 2) == Number{85'070'591'730'234'62, 22}));
Number const max = Number{Number::maxMantissa(), 0};
Number const max =
Number{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}));
@@ -1388,12 +1449,18 @@ public:
BEAST_EXPECT(
(power(maxInt64, 2) == Number{85'070'591'730'234'615'85, 19}));
Number const max = Number{Number::maxMantissa(), 0};
BEAST_EXPECT(max.exponent() <= 0);
// 99999999999999999980000000000000000001 - also 38 digits
BEAST_EXPECT(
(power(max, 2) ==
Number{numberint(9'999'999'999'999'999) * 1000 + 998, 19}));
NumberRoundModeGuard mg(Number::towards_zero);
auto const maxMantissa = Number::maxMantissa();
Number const max = Number{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{
maxMantissa / 10 - 1, 20, Number::normalized{}}));
}
}

View File

@@ -153,9 +153,9 @@ struct STNumber_test : public beast::unit_test::suite
STNumber(
sfNumber,
-Number{
numberint(9'223'372'036'854'775) * 1000 +
808,
0}));
numberint(9'223'372'036'854'775) * 1000 + 808,
0,
Number::normalized{}}));
}
}