[WIP] Checkpoint

This commit is contained in:
Ed Hennis
2025-11-25 00:01:53 -05:00
parent 59e6a8107a
commit 359622981e
5 changed files with 158 additions and 93 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,16 @@ 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 internalrep = numberint;
enum mantissa_scale { small, large };
explicit constexpr MantissaRange(mantissa_scale scale_, internalrep min_)
@@ -69,11 +71,14 @@ struct MantissaRange
class Number
{
using uint128_t = numberuint128;
using int128_t = numberint128;
using uint128_t = numberuint;
using int128_t = numberint;
public:
using rep = std::int64_t;
using internalrep = MantissaRange::internalrep;
private:
internalrep mantissa_{0};
int exponent_{std::numeric_limits<int>::lowest()};
@@ -92,12 +97,9 @@ public:
Number(rep mantissa);
explicit Number(internalrep mantissa, int exponent);
explicit constexpr Number(
internalrep mantissa,
int exponent,
unchecked) noexcept;
explicit constexpr Number(internalrep mantissa, int exponent, unchecked) noexcept;
constexpr internalrep
constexpr rep
mantissa() const noexcept;
constexpr int
exponent() const noexcept;
@@ -125,11 +127,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 +233,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
@@ -283,21 +294,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,16 +328,13 @@ private:
internalrep const& minMantissa,
internalrep const& maxMantissa);
constexpr bool
bool
isnormal() const noexcept;
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}
{
}
@@ -338,15 +349,34 @@ 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_;
if (m > maxRep)
{
XRPL_ASSERT_PARTS(
m % 10 == 0,
"ripple::Number::mantissa",
"large 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)
{
XRPL_ASSERT_PARTS(
mantissa_ % 10 == 0,
"ripple::Number::exponent",
"large mantissa has no remainder");
return exponent_ + 1;
}
return exponent_;
}
@@ -432,25 +462,25 @@ 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_;

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,36 @@ 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 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.
// 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 +299,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_;
@@ -306,16 +341,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)
{
@@ -451,16 +486,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)
{
@@ -521,16 +556,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;
@@ -649,12 +684,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 +841,7 @@ root(Number f, unsigned d)
return di - k2;
}();
e += ex;
f = Number{f.mantissa(), f.exponent() - e}; // f /= 10^e;
f = Number{f.mantissa_, f.exponent_ - e}; // f /= 10^e;
bool neg = false;
if (f < Number{})
{
@@ -838,7 +873,7 @@ 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};
return Number{r.mantissa_, r.exponent_ + e / di};
}
Number
@@ -857,7 +892,7 @@ 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 = Number{f.mantissa_, f.exponent_ - e}; // f /= 10^e;
// Quadratic least squares curve fit of f^(1/d) in the range [0, 1]
auto const D = 105;
@@ -878,7 +913,7 @@ root2(Number f)
} while (r != rm1 && r != rm2);
// return r * 10^(e/2) to reverse scaling
return Number{r.mantissa(), r.exponent() + e / 2};
return Number{r.mantissa_, r.exponent_ + e / 2};
}
// Returns f^(n/d)

View File

@@ -202,7 +202,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,7 +210,7 @@ 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;

View File

@@ -120,12 +120,12 @@ public:
test(
Number{
numberuint128(9'999'999'999'999'999) * 1000 + 999,
numberuint(9'999'999'999'999'999) * 1000 + 999,
-3},
"9999999999999999");
test(
-(Number{
numberuint128(9'999'999'999'999'999) * 1000 + 999,
numberuint(9'999'999'999'999'999) * 1000 + 999,
-3}),
"-9999999999999999");
}
@@ -149,14 +149,14 @@ public:
test(
Number{
numberuint128(9'999'999'999'999'999) * 1000 + 999,
numberuint(9'999'999'999'999'999) * 1000 + 999,
0},
"9999999999999999999");
"9999999999999999990");
test(
-(Number{
numberuint128(9'999'999'999'999'999) * 1000 + 999,
numberuint(9'999'999'999'999'999) * 1000 + 999,
0}),
"-9999999999999999999");
"-9999999999999999990");
}
break;
break;
@@ -206,10 +206,10 @@ 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{-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{}, Number{5}, Number{5}},
{Number{5}, Number{}, Number{5}},
{Number{5'555'555'555'555'555'000, -32768},
@@ -228,17 +228,17 @@ 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{-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{}, 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{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}}});
auto test = [this](auto const& c) {
for (auto const& [x, y, z] : c)
{
@@ -260,7 +260,7 @@ public:
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{numberint(9'999'999'999'999'999) * 1'000, 32768} +
Number{5'000'000'000'000'000'000, 32767};
}
catch (std::overflow_error const&)
@@ -275,7 +275,7 @@ public:
try
{
Number{
numberuint128(9'999'999'999'999'999) * 1000 + 999, 32768} +
numberuint(9'999'999'999'999'999) * 1000 + 999, 32768} +
Number{5'000'000'000'000'000'000, 32767};
}
catch (std::overflow_error const&)
@@ -315,11 +315,11 @@ 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{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{1'000'000'000'000'000, -15},
Number{1'000'000'000'000'000, -15},
Number{0}},
@@ -332,11 +332,11 @@ 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{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{1'000'000'000'000'000'000, -18},
Number{1'000'000'000'000'000'000, -18},
Number{0}},
@@ -423,7 +423,7 @@ 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{1000000000000000000, -32768},
Number{1000000000000000000, -32768},
Number{0}},
@@ -443,9 +443,9 @@ public:
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}},
{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}},
});
tests(cSmall, cLarge);
}
@@ -486,7 +486,7 @@ public:
Number{1999999999999999861, -18}},
{Number{3214285714285706, -15},
Number{3111111111111119, -15},
Number{numberint128(9999999999999999) * 1000 + 579, -18}},
Number{numberint(9999999999999999) * 1000 + 579, -18}},
{Number{1000000000000000000, -32768},
Number{1000000000000000000, -32768},
Number{0}},
@@ -545,7 +545,7 @@ public:
{Number{3214285714285706, -15},
Number{3111111111111119, -15},
Number{
numberint128(9'999'999'999'999'999) * 1000 + 579, -18}},
numberint(9'999'999'999'999'999) * 1000 + 579, -18}},
{Number{1000000000000000000, -32768},
Number{1000000000000000000, -32768},
Number{0}},
@@ -634,7 +634,7 @@ public:
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{numberint(9'999'999'999'999'999) * 1'000, 32768} *
Number{5'000'000'000'000'000'000, 32767};
}
catch (std::overflow_error const&)
@@ -649,7 +649,7 @@ public:
try
{
Number{
numberint128(9'999'999'999'999'999) * 1000 + 999, 32768} +
numberint(9'999'999'999'999'999) * 1000 + 999, 32768} +
Number{5'000'000'000'000'000'000, 32767};
}
catch (std::overflow_error const&)
@@ -722,10 +722,10 @@ public:
{Number{1414213562373095049, -13},
Number{1414213562373095049, -13},
Number{1}},
{Number{numberint128(9'999'999'999'999'999) * 1'000 + 999, 0},
{Number{numberint(9'999'999'999'999'999) * 1'000 + 999, 0},
Number{1'000'000'000'000'000'000},
Number{
numberint128(9'999'999'999'999'999) * 1'000 + 999,
numberint(9'999'999'999'999'999) * 1'000 + 999,
-18}}});
tests(cSmall, cLarge);
}
@@ -771,10 +771,10 @@ public:
{Number{1414213562373095049, -13},
Number{1414213562373095049, -13},
Number{1}},
{Number{numberint128(9'999'999'999'999'999) * 1'000 + 999, 0},
{Number{numberint(9'999'999'999'999'999) * 1'000 + 999, 0},
Number{1'000'000'000'000'000'000},
Number{
numberint128(9'999'999'999'999'999) * 1'000 + 999,
numberint(9'999'999'999'999'999) * 1'000 + 999,
-18}}});
tests(cSmall, cLarge);
}
@@ -820,10 +820,10 @@ public:
{Number{1414213562373095049, -13},
Number{1414213562373095049, -13},
Number{1}},
{Number{numberint128(9'999'999'999'999'999) * 1'000 + 999, 0},
{Number{numberint(9'999'999'999'999'999) * 1'000 + 999, 0},
Number{1'000'000'000'000'000'000},
Number{
numberint128(9'999'999'999'999'999) * 1'000 + 999,
numberint(9'999'999'999'999'999) * 1'000 + 999,
-18}}});
tests(cSmall, cLarge);
}
@@ -869,10 +869,10 @@ public:
{Number{1414213562373095049, -13},
Number{1414213562373095049, -13},
Number{1}},
{Number{numberint128(9'999'999'999'999'999) * 1'000 + 999, 0},
{Number{numberint(9'999'999'999'999'999) * 1'000 + 999, 0},
Number{1'000'000'000'000'000'000},
Number{
numberint128(9'999'999'999'999'999) * 1'000 + 999,
numberint(9'999'999'999'999'999) * 1'000 + 999,
-18}}});
tests(cSmall, cLarge);
}
@@ -1002,10 +1002,10 @@ public:
{Number{-64}, 3, Number{-262144}},
{Number{64},
11,
Number{numberint128(73786976294838206) * 1000 + 464, 0}},
Number{numberint(73786976294838206) * 1000 + 464, 0}},
{Number{-64},
11,
Number{-(numberint128(73786976294838206) * 1000 + 464), 0}}};
Number{-(numberint(73786976294838206) * 1000 + 464), 0}}};
for (auto const& [x, y, z] : c)
BEAST_EXPECT((power(x, y) == z));
}
@@ -1393,7 +1393,7 @@ public:
// 99999999999999999980000000000000000001 - also 38 digits
BEAST_EXPECT(
(power(max, 2) ==
Number{numberint128(9'999'999'999'999'999) * 1000 + 998, 19}));
Number{numberint(9'999'999'999'999'999) * 1000 + 998, 19}));
}
}

View File

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