Compare commits

...

3 Commits

Author SHA1 Message Date
Ed Hennis
21d66cbe78 Fix build error 2025-11-26 10:43:42 -05:00
Ed Hennis
3be46bc7d9 [WIP] Checkpoint 2025-11-26 00:21:40 -05:00
Ed Hennis
359622981e [WIP] Checkpoint 2025-11-25 00:01:53 -05:00
7 changed files with 563 additions and 368 deletions

View File

@@ -1,6 +1,8 @@
#ifndef XRPL_BASICS_NUMBER_H_INCLUDED
#define XRPL_BASICS_NUMBER_H_INCLUDED
#include <xrpl/beast/utility/instrumentation.h>
#ifdef _MSC_VER
#include <boost/multiprecision/cpp_int.hpp>
#endif
@@ -41,16 +43,17 @@ isPowerOfTen(T value)
}
#ifdef _MSC_VER
using numberuint128 = boost::multiprecision::uint128_t;
using numberint128 = boost::multiprecision::int128_t;
using numberuint = boost::multiprecision::uint128_t;
using numberint = boost::multiprecision::int128_t;
#else // !defined(_MSC_VER)
using numberuint128 = __uint128_t;
using numberint128 = __int128_t;
using numberuint = __uint128_t;
using numberint = __int128_t;
#endif // !defined(_MSC_VER)
struct MantissaRange
{
using internalrep = numberint128;
using rep = std::int64_t;
using internalrep = numberint;
enum mantissa_scale { small, large };
explicit constexpr MantissaRange(mantissa_scale scale_, internalrep min_)
@@ -61,7 +64,7 @@ struct MantissaRange
{
}
internalrep min;
rep min;
internalrep max;
int log;
mantissa_scale scale;
@@ -69,11 +72,12 @@ struct MantissaRange
class Number
{
using uint128_t = numberuint128;
using int128_t = numberint128;
using uint128_t = numberuint;
using int128_t = numberint;
using rep = std::int64_t;
using rep = MantissaRange::rep;
using internalrep = MantissaRange::internalrep;
internalrep mantissa_{0};
int exponent_{std::numeric_limits<int>::lowest()};
@@ -88,16 +92,27 @@ 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 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 internalrep
constexpr rep
mantissa() const noexcept;
constexpr int
exponent() const noexcept;
@@ -125,11 +140,11 @@ public:
Number&
operator/=(Number const& x);
static constexpr Number
static Number
min() noexcept;
static constexpr Number
static Number
max() noexcept;
static constexpr Number
static Number
lowest() noexcept;
/** Conversions to Number are implicit and conversions away from Number
@@ -231,6 +246,15 @@ public:
return os << to_string(x);
}
friend std::string
to_string(Number const& amount);
friend Number
root(Number f, unsigned d);
friend Number
root2(Number f);
// Thread local rounding control. Default is to_nearest
enum rounding_mode { to_nearest, towards_zero, downward, upward };
static rounding_mode
@@ -244,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;
@@ -283,21 +308,24 @@ private:
static thread_local rounding_mode mode_;
// The available ranges for mantissa
constexpr static internalrep maxRep =
std::numeric_limits<std::int64_t>::max();
constexpr static MantissaRange smallRange{
MantissaRange::small,
1'000'000'000'000'000LL};
static_assert(isPowerOfTen(smallRange.min));
static_assert(smallRange.max == 9'999'999'999'999'999LL);
static_assert(smallRange.log == 15);
// maxint64 9,223,372,036,854,775,808
constexpr static MantissaRange largeRange{
MantissaRange::large,
1'000'000'000'000'000'000LL};
static_assert(isPowerOfTen(largeRange.min));
// maxRep 9,223,372,036,854,775,808
static_assert(largeRange.max == internalrep(9'999'999'999'999'999'999ULL));
static_assert(largeRange.log == 18);
static_assert(largeRange.min < std::numeric_limits<std::int64_t>::max());
static_assert(largeRange.max > std::numeric_limits<std::int64_t>::max());
static_assert(largeRange.min < maxRep);
static_assert(largeRange.max > maxRep);
// The range for the mantissa when normalized.
// Use reference_wrapper to avoid making copies, and prevent accidentally
@@ -314,9 +342,15 @@ private:
internalrep const& minMantissa,
internalrep const& maxMantissa);
constexpr bool
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;
};
@@ -328,8 +362,14 @@ inline constexpr Number::Number(
{
}
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();
}
@@ -338,16 +378,36 @@ inline Number::Number(rep mantissa) : Number{mantissa, 0}
{
}
inline constexpr Number::internalrep
inline constexpr Number::rep
Number::mantissa() const noexcept
{
return mantissa_;
auto m = mantissa_;
while (m > maxRep)
{
XRPL_ASSERT_PARTS(
!isnormal() || m % 10 == 0,
"ripple::Number::mantissa",
"large normalized mantissa has no remainder");
m /= 10;
}
return static_cast<Number::rep>(m);
}
inline constexpr int
Number::exponent() const noexcept
{
return exponent_;
auto m = mantissa_;
auto e = exponent_;
while (m > maxRep)
{
XRPL_ASSERT_PARTS(
!isnormal() || m % 10 == 0,
"ripple::Number::exponent",
"large normalized mantissa has no remainder");
m /= 10;
++e;
}
return e;
}
inline constexpr Number
@@ -432,30 +492,31 @@ operator/(Number const& x, Number const& y)
return z;
}
inline constexpr Number
inline Number
Number::min() noexcept
{
return Number{range_.get().min, minExponent, unchecked{}};
}
inline constexpr Number
inline Number
Number::max() noexcept
{
return Number{range_.get().max, maxExponent, unchecked{}};
}
inline constexpr Number
inline Number
Number::lowest() noexcept
{
return -Number{range_.get().max, maxExponent, unchecked{}};
}
inline constexpr bool
inline bool
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

@@ -21,12 +21,12 @@ using uint128_t = boost::multiprecision::uint128_t;
#else // !defined(_MSC_VER)
using uint128_t = __uint128_t;
#endif // !defined(_MSC_VER)
static_assert(std::is_same_v<uint128_t, ripple::numberuint128>);
static_assert(std::is_same_v<uint128_t, ripple::numberuint>);
namespace std {
template <>
struct make_unsigned<ripple::numberint128>
struct make_unsigned<ripple::numberint>
{
using type = uint128_t;
};
@@ -256,13 +256,37 @@ Number::normalize(
m /= 10;
++exponent_;
}
mantissa_ = m;
if ((exponent_ < minExponent) || (mantissa_ < minMantissa))
if ((exponent_ < minExponent) || (m < minMantissa))
{
mantissa_ = zero.mantissa_;
exponent_ = zero.exponent_;
return;
}
// 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 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
// exponent_ + 1.
if (m > maxRep)
{
if (exponent_ >= maxExponent)
throw std::overflow_error("Number::normalize 1.5");
g.push(m % 10);
m /= 10;
++exponent_;
}
XRPL_ASSERT_PARTS(
m <= maxRep,
"ripple::Number::normalize",
"intermediate mantissa fits in int64");
mantissa_ = m;
auto r = g.round();
if (r == 1 || (r == 0 && (mantissa_ & 1) == 1))
@@ -276,6 +300,18 @@ Number::normalize(
}
if (exponent_ > maxExponent)
throw std::overflow_error("Number::normalize 2");
if (mantissa_ < minMantissa)
{
// When using the largeRange, the intermediate "m" needs fit within an
// int64, but mantissa_ needs to fit within the minMantissa /
// maxMantissa range.
mantissa_ *= 10;
--exponent_;
}
XRPL_ASSERT_PARTS(
mantissa_ >= minMantissa && mantissa_ <= maxMantissa,
"ripple::Number::normalize",
"final mantissa fits in range");
if (negative)
mantissa_ = -mantissa_;
@@ -288,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)
{
@@ -306,16 +365,16 @@ Number::operator+=(Number const& y)
XRPL_ASSERT(
isnormal() && y.isnormal(),
"ripple::Number::operator+=(Number) : is normal");
auto xm = mantissa();
auto xe = exponent();
auto xm = mantissa_;
auto xe = exponent_;
int xn = 1;
if (xm < 0)
{
xm = -xm;
xn = -1;
}
auto ym = y.mantissa();
auto ye = y.exponent();
auto ym = y.mantissa_;
auto ye = y.exponent_;
int yn = 1;
if (ym < 0)
{
@@ -407,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;
}
@@ -451,16 +514,16 @@ Number::operator*=(Number const& y)
XRPL_ASSERT(
isnormal() && y.isnormal(),
"ripple::Number::operator*=(Number) : is normal");
auto xm = mantissa();
auto xe = exponent();
auto xm = mantissa_;
auto xe = exponent_;
int xn = 1;
if (xm < 0)
{
xm = -xm;
xn = -1;
}
auto ym = y.mantissa();
auto ye = y.exponent();
auto ym = y.mantissa_;
auto ye = y.exponent_;
int yn = 1;
if (ym < 0)
{
@@ -507,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");
@@ -521,16 +585,16 @@ Number::operator/=(Number const& y)
if (*this == Number{})
return *this;
int np = 1;
auto nm = mantissa();
auto ne = exponent();
auto nm = mantissa_;
auto ne = exponent_;
if (nm < 0)
{
nm = -nm;
np = -1;
}
int dp = 1;
auto dm = y.mantissa();
auto de = y.exponent();
auto dm = y.mantissa_;
auto de = y.exponent_;
if (dm < 0)
{
dm = -dm;
@@ -548,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");
@@ -649,12 +713,12 @@ to_string(Number const& amount)
if (amount == Number{})
return "0";
auto const exponent = amount.exponent();
auto const exponent = amount.exponent_;
bool const negative = amount.mantissa() < 0;
bool const negative = amount.mantissa_ < 0;
auto const mantissa = [&]() {
auto mantissa = amount.mantissa();
auto mantissa = amount.mantissa_;
if (negative)
{
mantissa = -mantissa;
@@ -806,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{})
{
@@ -838,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
@@ -857,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;
@@ -878,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<numberint128>::max() >
std::numeric_limits<numberint>::max() >=
std::numeric_limits<decltype(parts.mantissa)>::max());
}
else
@@ -210,11 +199,11 @@ numberFromJson(SField const& field, Json::Value const& value)
Throw<std::runtime_error>("not a number");
}
numberint128 mantissa = parts.mantissa;
numberint mantissa = parts.mantissa;
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{
numberuint128(9'999'999'999'999'999) * 1000 + 999,
-3},
"9999999999999999");
test(
-(Number{
numberuint128(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{
numberuint128(9'999'999'999'999'999) * 1000 + 999,
0},
"9999999999999999999");
test(
-(Number{
numberuint128(9'999'999'999'999'999) * 1000 + 999,
0}),
"-9999999999999999999");
}
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{
-(numberint128(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{numberint128(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{
-(numberint128(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{numberint128(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{-(numberint128(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{numberint128(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{numberint128(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{
numberuint128(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{numberint128(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{
-(numberint128(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{numberint128(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{
-(numberint128(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{numberint128(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{numberint128(9'999'999'999'999'999) * 1000 + 999, 0},
Number{numberint128(9'999'999'999'999'999) * 1000 + 999, 0},
Number{numberint128(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{numberint128(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{
numberint128(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{numberint128(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{
numberint128(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{numberint128(9'999'999'999'999'999) * 1'000 + 999, 0},
{Number{maxMantissa, 0, Number::normalized{}},
Number{1'000'000'000'000'000'000},
Number{
numberint128(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{numberint128(9'999'999'999'999'999) * 1'000 + 999, 0},
{Number{maxMantissa, 0, Number::normalized{}},
Number{1'000'000'000'000'000'000},
Number{
numberint128(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{numberint128(9'999'999'999'999'999) * 1'000 + 999, 0},
{Number{maxMantissa, 0, Number::normalized{}},
Number{1'000'000'000'000'000'000},
Number{
numberint128(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{numberint128(9'999'999'999'999'999) * 1'000 + 999, 0},
{Number{maxMantissa, 0, Number::normalized{}},
Number{1'000'000'000'000'000'000},
Number{
numberint128(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{numberint128(73786976294838206) * 1000 + 464, 0}},
Number{
numberint(73786976294838206) * 1000 + 464,
0,
Number::normalized{}}},
{Number{-64},
11,
Number{-(numberint128(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'999ULL);
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{numberint128(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{
numberint128(9'223'372'036'854'775) * 1000 +
808,
0}));
numberint(9'223'372'036'854'775) * 1000 + 808,
0,
Number::normalized{}}));
}
}