Compare commits

...

6 Commits

Author SHA1 Message Date
Ed Hennis
c01bfb2b60 [WIP] Checkpoint 2026-01-23 16:01:11 -05:00
Ed Hennis
69dda6b34c Set version to 3.1.0-rc2 2026-01-22 18:13:41 -05:00
Ed Hennis
e3644f265c fix: Remove DEFAULT fields that change to the default in associateAsset (#6259)
- Add Vault creation tests for showing valid range for AssetsMaximum
2026-01-22 16:52:57 -05:00
Ayaz Salikhov
8c573cd0bb Fix dependencies so clio can use libxrpl (#6251)
- Include <functional> header in Number.h
2026-01-22 11:30:54 -05:00
Ed Hennis
c894cd2b5f Revert "Fix dependencies so clio can use libxrpl (#6251)"
This reverts commit ecfe43ece7.
2026-01-22 11:30:39 -05:00
Ayaz Salikhov
ecfe43ece7 Fix dependencies so clio can use libxrpl (#6251)
- Include <functional> header in Number.h
2026-01-21 14:11:21 -05:00
11 changed files with 428 additions and 118 deletions

View File

@@ -23,6 +23,7 @@
#include <xrpl/beast/utility/instrumentation.h>
#include <cstdint>
#include <functional>
#include <limits>
#include <optional>
#include <ostream>
@@ -35,18 +36,37 @@ class Number;
std::string
to_string(Number const& amount);
/** Returns a rough estimate of log10(value).
*
* The return value is a pair (log, rem), where log is the estimated log10,
* and rem is value divided by 10^log. If rem is 1, then value is an exact
* power of ten, and log is the exact log10(value).
*
* This function only works for positive values.
*/
template <typename T>
constexpr std::pair<int, T>
logTenEstimate(T value)
{
int log = 0;
T remainder = value;
while (value >= 10)
{
if (value % 10 == 0)
remainder = remainder / 10;
value /= 10;
++log;
}
return {log, remainder};
}
template <typename T>
constexpr std::optional<int>
logTen(T value)
{
int log = 0;
while (value >= 10 && value % 10 == 0)
{
value /= 10;
++log;
}
if (value == 1)
return log;
auto const est = logTenEstimate(value);
if (est.second == 1)
return est.first;
return std::nullopt;
}
@@ -60,8 +80,6 @@ isPowerOfTen(T value)
/** MantissaRange defines a range for the mantissa of a normalized Number.
*
* The mantissa is in the range [min, max], where
* * min is a power of 10, and
* * max = min * 10 - 1.
*
* The mantissa_scale enum indicates whether the range is "small" or "large".
* This intentionally restricts the number of MantissaRanges that can be
@@ -79,8 +97,8 @@ isPowerOfTen(T value)
* "large" scale.
*
* The "large" scale is intended to represent all values that can be represented
* by an STAmount - IOUs, XRP, and MPTs. It has a min value of 10^18, and a max
* value of 10^19-1.
* by an STAmount - IOUs, XRP, and MPTs. It has a min value of 2^63/10+1
* (truncated), and a max value of 2^63-1.
*
* Note that if the mentioned amendments are eventually retired, this class
* should be left in place, but the "small" scale option should be removed. This
@@ -92,28 +110,38 @@ struct MantissaRange
enum mantissa_scale { small, large };
explicit constexpr MantissaRange(mantissa_scale scale_)
: min(getMin(scale_))
, max(min * 10 - 1)
, log(logTen(min).value_or(-1))
: max(getMax(scale_))
, min(computeMin(max))
, log(computeLog(min))
, scale(scale_)
{
// Since this is constexpr, if any of these throw, it won't compile
if (min * 10 <= max)
throw std::out_of_range("min * 10 <= max");
if (max / 10 >= min)
throw std::out_of_range("max / 10 >= min");
if ((min - 1) * 10 > max)
throw std::out_of_range("(min - 1) * 10 > max");
// This is a little hacky
if ((max + 10) / 10 < min)
throw std::out_of_range("(max + 10) / 10 < min");
}
rep min;
rep max;
rep min;
int log;
mantissa_scale scale;
private:
static constexpr rep
getMin(mantissa_scale scale_)
getMax(mantissa_scale scale_)
{
switch (scale_)
{
case small:
return 1'000'000'000'000'000ULL;
return 9'999'999'999'999'999ULL;
case large:
return 1'000'000'000'000'000'000ULL;
return std::numeric_limits<std::int64_t>::max();
default:
// Since this can never be called outside a non-constexpr
// context, this throw assures that the build fails if an
@@ -121,6 +149,24 @@ private:
throw std::runtime_error("Unknown mantissa scale");
}
}
static constexpr rep
computeMin(rep max)
{
auto min = max + 1;
auto const r = min % 10;
min /= 10;
if (r != 0)
++min;
return min;
}
static constexpr rep
computeLog(rep min)
{
auto const estimate = logTenEstimate(min);
return estimate.first + (estimate.second == 1 ? 0 : 1);
}
};
// Like std::integral, but only 64-bit integral types.
@@ -155,9 +201,7 @@ concept Integral64 =
* 1. Normalization can be disabled by using the "unchecked" ctor tag. This
* should only be used at specific conversion points, some constexpr
* values, and in unit tests.
* 2. The max of the "large" range, 10^19-1, is the largest 10^X-1 value that
* fits in an unsigned 64-bit number. (10^19-1 < 2^64-1 and
* 10^20-1 > 2^64-1). This avoids under- and overflows.
* 2. The max of the "large" range, 2^63-1, TODO: explain the large range.
*
* ---- External Interface ----
*
@@ -171,7 +215,7 @@ concept Integral64 =
*
* Note:
* 1. 2^63-1 is between 10^18 and 10^19-1, which are the limits of the "large"
* mantissa range.
* mantissa range. TODO: update this explanation.
* 2. The functions mantissa() and exponent() return the external view of the
* Number value, specifically using a signed 63-bit mantissa. This may
* require altering the internal representation to fit into that range
@@ -231,6 +275,7 @@ class Number
using rep = std::int64_t;
using internalrep = MantissaRange::rep;
// TODO: Get rid of negative_ and convert mantissa back to rep
bool negative_{false};
internalrep mantissa_{0};
int exponent_{std::numeric_limits<int>::lowest()};
@@ -240,9 +285,11 @@ public:
constexpr static int minExponent = -32768;
constexpr static int maxExponent = 32768;
#if MAXREP
constexpr static internalrep maxRep = std::numeric_limits<rep>::max();
static_assert(maxRep == 9'223'372'036'854'775'807);
static_assert(-maxRep == std::numeric_limits<rep>::min() + 1);
#endif
// May need to make unchecked private
struct unchecked
@@ -476,15 +523,27 @@ private:
static_assert(smallRange.min == 1'000'000'000'000'000LL);
static_assert(smallRange.max == 9'999'999'999'999'999LL);
static_assert(smallRange.log == 15);
#if MAXREP
static_assert(smallRange.min < maxRep);
static_assert(smallRange.max < maxRep);
#endif
constexpr static MantissaRange largeRange{MantissaRange::large};
static_assert(isPowerOfTen(largeRange.min));
static_assert(largeRange.min == 1'000'000'000'000'000'000ULL);
static_assert(largeRange.max == internalrep(9'999'999'999'999'999'999ULL));
static_assert(!isPowerOfTen(largeRange.min));
static_assert(largeRange.min == 922337203685477581ULL);
static_assert(largeRange.max == internalrep(9223372036854775807ULL));
static_assert(largeRange.max == std::numeric_limits<rep>::max());
static_assert(largeRange.log == 18);
// There are 2 values that will not fit in largeRange without some extra
// work
// * 9223372036854775808
// * 9223372036854775809
// They both end up < min, but with a leftover. If they round up, everything
// will be fine. If they don't, well need to bring them up into range.
#if MAXREP
static_assert(largeRange.min < maxRep);
static_assert(largeRange.max > maxRep);
#endif
// The range for the mantissa when normalized.
// Use reference_wrapper to avoid making copies, and prevent accidentally
@@ -535,6 +594,9 @@ private:
externalToInternal(rep mantissa);
class Guard;
public:
constexpr static internalrep largestMantissa = largeRange.max;
};
inline constexpr Number::Number(
@@ -588,17 +650,8 @@ inline Number::Number(rep mantissa) : Number{mantissa, 0}
inline constexpr Number::rep
Number::mantissa() const noexcept
{
auto m = mantissa_;
if (m > maxRep)
{
XRPL_ASSERT_PARTS(
!isnormal() || (m % 10 == 0 && m / 10 <= maxRep),
"xrpl::Number::mantissa",
"large normalized mantissa has no remainder");
m /= 10;
}
auto const sign = negative_ ? -1 : 1;
return sign * static_cast<Number::rep>(m);
return sign * static_cast<Number::rep>(mantissa_);
}
/** Returns the exponent of the external view of the Number.
@@ -609,16 +662,7 @@ Number::mantissa() const noexcept
inline constexpr int
Number::exponent() const noexcept
{
auto e = exponent_;
if (mantissa_ > maxRep)
{
XRPL_ASSERT_PARTS(
!isnormal() || (mantissa_ % 10 == 0 && mantissa_ / 10 <= maxRep),
"xrpl::Number::exponent",
"large normalized mantissa has no remainder");
++e;
}
return e;
return exponent_;
}
inline constexpr Number
@@ -714,15 +758,13 @@ Number::min() noexcept
inline Number
Number::max() noexcept
{
return Number{
false, std::min(range_.get().max, maxRep), maxExponent, unchecked{}};
return Number{false, range_.get().max, maxExponent, unchecked{}};
}
inline Number
Number::lowest() noexcept
{
return Number{
true, std::min(range_.get().max, maxRep), maxExponent, unchecked{}};
return Number{true, range_.get().max, maxExponent, unchecked{}};
}
inline bool
@@ -731,9 +773,8 @@ Number::isnormal() const noexcept
MantissaRange const& range = range_;
auto const abs_m = mantissa_;
return *this == Number{} ||
(range.min <= abs_m && abs_m <= range.max &&
(abs_m <= maxRep || abs_m % 10 == 0) && minExponent <= exponent_ &&
exponent_ <= maxExponent);
(range.min <= abs_m && abs_m <= range.max && //
minExponent <= exponent_ && exponent_ <= maxExponent);
}
template <Integral64 T>

View File

@@ -252,7 +252,7 @@ std::size_t constexpr maxMPTokenMetadataLength = 1024;
/** The maximum amount of MPTokenIssuance */
std::uint64_t constexpr maxMPTokenAmount = 0x7FFF'FFFF'FFFF'FFFFull;
static_assert(Number::maxRep >= maxMPTokenAmount);
static_assert(Number::largestMantissa >= maxMPTokenAmount);
/** The maximum length of Data payload */
std::size_t constexpr maxDataPayloadLength = 256;

View File

@@ -416,6 +416,9 @@ public:
void
delField(int index);
SOEStyle
getStyle(SField const& field) const;
bool
hasMatchingEntry(STBase const&);

View File

@@ -43,7 +43,7 @@ systemName()
/** Number of drops in the genesis account. */
constexpr XRPAmount INITIAL_XRP{100'000'000'000 * DROPS_PER_XRP};
static_assert(INITIAL_XRP.drops() == 100'000'000'000'000'000);
static_assert(Number::maxRep >= INITIAL_XRP.drops());
static_assert(Number::largestMantissa >= INITIAL_XRP.drops());
/** Returns true if the amount does not exceed the initial XRP in existence. */
inline bool

View File

@@ -279,7 +279,7 @@ Number::Guard::doRoundUp(
++mantissa;
// Ensure mantissa after incrementing fits within both the
// min/maxMantissa range and is a valid "rep".
if (mantissa > maxMantissa || mantissa > maxRep)
if (mantissa > maxMantissa)
{
mantissa /= 10;
++exponent;
@@ -318,7 +318,7 @@ Number::Guard::doRound(rep& drops, std::string location)
auto r = round();
if (r == 1 || (r == 0 && (drops & 1) == 1))
{
if (drops >= maxRep)
if (drops >= maxMantissa())
{
static_assert(sizeof(internalrep) == sizeof(rep));
// This should be impossible, because it's impossible to represent
@@ -406,7 +406,6 @@ doNormalize(
{
auto constexpr minExponent = Number::minExponent;
auto constexpr maxExponent = Number::maxExponent;
auto constexpr maxRep = Number::maxRep;
using Guard = Number::Guard;
@@ -443,33 +442,8 @@ doNormalize(
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
// MantissaRange. Cut it down here so that the rounding will be done while
// it's smaller.
//
// Example: 9,900,000,000,000,123,456 > 9,223,372,036,854,775,807,
// so "m" will be modified to 990,000,000,000,012,345. Then that value
// will be rounded to 990,000,000,000,012,345 or
// 990,000,000,000,012,346, 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,123,450 or 9,900,000,000,000,123,460.
// 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_;
}
// Before modification, m should be within the min/max range. After
// modification, it must be less than maxRep. In other words, the original
// value should have been no more than maxRep * 10.
// (maxRep * 10 > maxMantissa)
XRPL_ASSERT_PARTS(
m <= maxRep,
m <= maxMantissa,
"xrpl::doNormalize",
"intermediate mantissa fits in int64");
mantissa_ = m;
@@ -616,7 +590,7 @@ Number::operator+=(Number const& y)
if (xn == yn)
{
xm += ym;
if (xm > maxMantissa || xm > maxRep)
if (xm > maxMantissa)
{
g.push(xm % 10);
xm /= 10;
@@ -637,7 +611,7 @@ Number::operator+=(Number const& y)
xe = ye;
xn = yn;
}
while (xm < minMantissa && xm * 10 <= maxRep)
while (xm < minMantissa)
{
xm *= 10;
xm -= g.pop();
@@ -719,7 +693,7 @@ Number::operator*=(Number const& y)
auto const& minMantissa = range.min;
auto const& maxMantissa = range.max;
while (zm > maxMantissa || zm > maxRep)
while (zm > maxMantissa)
{
// The following is optimization for:
// g.push(static_cast<unsigned>(zm % 10));
@@ -861,7 +835,7 @@ Number::operator rep() const
}
for (; offset > 0; --offset)
{
if (drops > maxRep / 10)
if (drops > largeRange.max / 10)
throw std::overflow_error("Number::operator rep() overflow");
drops *= 10;
}

View File

@@ -36,7 +36,7 @@ namespace BuildInfo {
// and follow the format described at http://semver.org/
//------------------------------------------------------------------------------
// clang-format off
char const* const versionString = "3.1.0-rc1"
char const* const versionString = "3.1.0-rc2"
// clang-format on
#if defined(DEBUG) || defined(SANITIZER)

View File

@@ -142,6 +142,8 @@ areComparable(STAmount const& v1, STAmount const& v2)
return false;
}
static_assert(INITIAL_XRP.drops() == STAmount::cMaxNativeN);
STAmount::STAmount(SerialIter& sit, SField const& name) : STBase(name)
{
std::uint64_t value = sit.get64();

View File

@@ -599,6 +599,12 @@ STObject::delField(int index)
v_.erase(v_.begin() + index);
}
SOEStyle
STObject::getStyle(SField const& field) const
{
return mType ? mType->style(field) : soeINVALID;
}
unsigned char
STObject::getFieldU8(SField const& field) const
{

View File

@@ -18,10 +18,28 @@ associateAsset(SLE& sle, Asset const& asset)
// If the field is not set or present, skip it.
if (type == STI_NOTPRESENT)
continue;
// If the type doesn't downcast, then the flag shouldn't be on the
// SField
auto& ta = entry.downcast<STTakesAsset>();
auto const style = sle.getStyle(ta.getFName());
XRPL_ASSERT_PARTS(
style != soeINVALID,
"ripple::associateAsset",
"valid template element style");
XRPL_ASSERT_PARTS(
style != soeDEFAULT || !ta.isDefault(),
"ripple::associateAsset",
"non-default value");
ta.associateAsset(asset);
// associateAsset in derived classes may change the underlying
// value, but it won't know anything about how the value relates to
// the SLE. If the template element is soeDEFAULT, and the value
// changed to the default value, remove the field.
if (style == soeDEFAULT && ta.isDefault())
sle.makeFieldAbsent(field);
}
}
}

View File

@@ -5801,6 +5801,263 @@ class Vault_test : public beast::unit_test::suite
testCase(MPT, "MPT", owner, depositor, issuer);
}
void
testAssetsMaximum()
{
testcase("Assets Maximum");
using namespace test::jtx;
Env env{*this, testable_amendments() | featureSingleAssetVault};
Account const owner{"owner"};
Account const issuer{"issuer"};
Vault vault{env};
env.fund(XRP(1'000'000), issuer, owner);
env.close();
auto const maxInt64 =
std::to_string(std::numeric_limits<std::int64_t>::max());
BEAST_EXPECT(maxInt64 == "9223372036854775807");
// Naming things is hard
auto const maxInt64Plus1 = std::to_string(
static_cast<std::uint64_t>(
std::numeric_limits<std::int64_t>::max()) +
1);
BEAST_EXPECT(maxInt64Plus1 == "9223372036854775808");
auto const initialXRP = to_string(INITIAL_XRP);
BEAST_EXPECT(initialXRP == "100000000000000000");
auto const initialXRPPlus1 = to_string(INITIAL_XRP + 1);
BEAST_EXPECT(initialXRPPlus1 == "100000000000000001");
{
testcase("Assets Maximum: XRP");
PrettyAsset const xrpAsset = xrpIssue();
auto [tx, keylet] =
vault.create({.owner = owner, .asset = xrpAsset});
tx[sfData] = "4D65746144617461";
tx[sfAssetsMaximum] = maxInt64;
env(tx, ter(tefEXCEPTION), THISLINE);
env.close();
tx[sfAssetsMaximum] = initialXRPPlus1;
env(tx, ter(tefEXCEPTION), THISLINE);
env.close();
tx[sfAssetsMaximum] = initialXRP;
env(tx, THISLINE);
env.close();
tx[sfAssetsMaximum] = maxInt64Plus1;
env(tx, ter(tefEXCEPTION), THISLINE);
env.close();
// This value will be rounded
auto const insertAt = maxInt64Plus1.size() - 3;
auto const decimalTest = maxInt64Plus1.substr(0, insertAt) + "." +
maxInt64Plus1.substr(insertAt); // (max int64+1) / 1000
BEAST_EXPECT(decimalTest == "9223372036854775.808");
tx[sfAssetsMaximum] = decimalTest;
auto const newKeylet = keylet::vault(owner.id(), env.seq(owner));
env(tx, THISLINE);
env.close();
auto const vaultSle = env.le(newKeylet);
if (!BEAST_EXPECT(vaultSle))
return;
BEAST_EXPECT(vaultSle->at(sfAssetsMaximum) == 9223372036854776);
}
{
testcase("Assets Maximum: MPT");
PrettyAsset const mptAsset = [&]() {
MPTTester mptt{env, issuer, mptInitNoFund};
mptt.create(
{.flags =
tfMPTCanClawback | tfMPTCanTransfer | tfMPTCanLock});
env.close();
PrettyAsset const mptAsset = mptt["MPT"];
mptt.authorize({.account = owner});
env.close();
return mptAsset;
}();
env(pay(issuer, owner, mptAsset(100'000)), THISLINE);
env.close();
auto [tx, keylet] =
vault.create({.owner = owner, .asset = mptAsset});
tx[sfData] = "4D65746144617461";
tx[sfAssetsMaximum] = maxInt64;
env(tx, THISLINE);
env.close();
tx[sfAssetsMaximum] = initialXRPPlus1;
env(tx, THISLINE);
env.close();
tx[sfAssetsMaximum] = initialXRP;
env(tx, THISLINE);
env.close();
tx[sfAssetsMaximum] = maxInt64Plus1;
env(tx, ter(tefEXCEPTION), THISLINE);
env.close();
// This value will be rounded
auto const insertAt = maxInt64Plus1.size() - 1;
auto const decimalTest = maxInt64Plus1.substr(0, insertAt) + "." +
maxInt64Plus1.substr(insertAt); // (max int64+1) / 10
BEAST_EXPECT(decimalTest == "922337203685477580.8");
tx[sfAssetsMaximum] = decimalTest;
auto const newKeylet = keylet::vault(owner.id(), env.seq(owner));
env(tx, THISLINE);
env.close();
auto const vaultSle = env.le(newKeylet);
if (!BEAST_EXPECT(vaultSle))
return;
BEAST_EXPECT(vaultSle->at(sfAssetsMaximum) == 922337203685477581);
}
{
testcase("Assets Maximum: IOU");
// Almost anything goes with IOUs
PrettyAsset iouAsset = issuer["IOU"];
env.trust(iouAsset(1000), owner);
env(pay(issuer, owner, iouAsset(200)));
env.close();
auto [tx, keylet] =
vault.create({.owner = owner, .asset = iouAsset});
tx[sfData] = "4D65746144617461";
tx[sfAssetsMaximum] = maxInt64;
env(tx, THISLINE);
env.close();
tx[sfAssetsMaximum] = initialXRPPlus1;
env(tx, THISLINE);
env.close();
tx[sfAssetsMaximum] = initialXRP;
env(tx, THISLINE);
env.close();
tx[sfAssetsMaximum] = maxInt64Plus1;
env(tx, THISLINE);
env.close();
tx[sfAssetsMaximum] = "1000000000000000e80";
env.close();
tx[sfAssetsMaximum] = "1000000000000000e-96";
env.close();
// These values will be rounded to 15 significant digits
{
auto const insertAt = maxInt64Plus1.size() - 1;
auto const decimalTest = maxInt64Plus1.substr(0, insertAt) +
"." + maxInt64Plus1.substr(insertAt); // (max int64+1) / 10
BEAST_EXPECT(decimalTest == "922337203685477580.8");
tx[sfAssetsMaximum] = decimalTest;
auto const newKeylet =
keylet::vault(owner.id(), env.seq(owner));
env(tx, THISLINE);
env.close();
auto const vaultSle = env.le(newKeylet);
if (!BEAST_EXPECT(vaultSle))
return;
BEAST_EXPECT(
(vaultSle->at(sfAssetsMaximum) ==
Number{9223372036854776, 2, Number::normalized{}}));
}
{
tx[sfAssetsMaximum] =
"9223372036854775807e40"; // max int64 * 10^40
auto const newKeylet =
keylet::vault(owner.id(), env.seq(owner));
env(tx, THISLINE);
env.close();
auto const vaultSle = env.le(newKeylet);
if (!BEAST_EXPECT(vaultSle))
return;
BEAST_EXPECT(
(vaultSle->at(sfAssetsMaximum) ==
Number{9223372036854776, 43, Number::normalized{}}));
}
{
tx[sfAssetsMaximum] =
"9223372036854775807e-40"; // max int64 * 10^-40
auto const newKeylet =
keylet::vault(owner.id(), env.seq(owner));
env(tx, THISLINE);
env.close();
auto const vaultSle = env.le(newKeylet);
if (!BEAST_EXPECT(vaultSle))
return;
BEAST_EXPECT(
(vaultSle->at(sfAssetsMaximum) ==
Number{9223372036854776, -37, Number::normalized{}}));
}
{
tx[sfAssetsMaximum] =
"9223372036854775807e-100"; // max int64 * 10^-100
auto const newKeylet =
keylet::vault(owner.id(), env.seq(owner));
env(tx, THISLINE);
env.close();
// Field 'AssetsMaximum' may not be explicitly set to default.
auto const vaultSle = env.le(newKeylet);
if (!BEAST_EXPECT(vaultSle))
return;
BEAST_EXPECT(vaultSle->at(sfAssetsMaximum) == numZero);
}
// What _can't_ IOUs do?
// 1. Exceed maximum exponent / offset
tx[sfAssetsMaximum] = "1000000000000000e81";
env(tx, ter(tefEXCEPTION), THISLINE);
env.close();
// 2. Mantissa larger than uint64 max
try
{
tx[sfAssetsMaximum] =
"18446744073709551617e5"; // uint64 max + 1
env(tx, THISLINE);
BEAST_EXPECT(false);
}
catch (parse_error const& e)
{
using namespace std::string_literals;
BEAST_EXPECT(
e.what() ==
"invalidParamsField 'tx_json.AssetsMaximum' has invalid "
"data."s);
}
}
}
public:
void
run() override
@@ -5821,6 +6078,7 @@ public:
testDelegate();
testVaultClawbackBurnShares();
testVaultClawbackAssets();
testAssetsMaximum();
}
};

View File

@@ -217,12 +217,12 @@ public:
9'999'999'999'999'999'990ULL,
-19,
Number::normalized{}}},
{Number{Number::maxRep},
{Number{Number::largestMantissa},
Number{6, -1},
Number{Number::maxRep / 10, 1}},
{Number{Number::maxRep - 1},
Number{Number::largestMantissa / 10, 1}},
{Number{Number::largestMantissa - 1},
Number{1, 0},
Number{Number::maxRep}},
Number{Number::largestMantissa}},
// Test extremes
{
// Each Number operand rounds up, so the actual mantissa is
@@ -240,11 +240,11 @@ public:
Number{2, 19},
},
{
// Does not round. Mantissas are going to be > maxRep, so if
// added together as uint64_t's, the result will overflow.
// With addition using uint128_t, there's no problem. After
// normalizing, the resulting mantissa ends up less than
// maxRep.
// Does not round. Mantissas are going to be >
// largestMantissa, so if added together as uint64_t's, the
// result will overflow. With addition using uint128_t,
// there's no problem. After normalizing, the resulting
// mantissa ends up less than largestMantissa.
Number{
false,
9'999'999'999'999'999'990ULL,
@@ -371,16 +371,24 @@ public:
{Number{1'000'000'000'000'000'001, -18},
Number{1'000'000'000'000'000'000, -18},
Number{1'000'000'000'000'000'000, -36}},
{Number{Number::maxRep},
{Number{Number::largestMantissa},
Number{6, -1},
Number{Number::maxRep - 1}},
{Number{false, Number::maxRep + 1, 0, Number::normalized{}},
Number{Number::largestMantissa - 1}},
{Number{
false,
Number::largestMantissa + 1,
0,
Number::normalized{}},
Number{1, 0},
Number{Number::maxRep / 10 + 1, 1}},
{Number{false, Number::maxRep + 1, 0, Number::normalized{}},
Number{Number::largestMantissa / 10 + 1, 1}},
{Number{
false,
Number::largestMantissa + 1,
0,
Number::normalized{}},
Number{3, 0},
Number{Number::maxRep}},
{power(2, 63), Number{3, 0}, Number{Number::maxRep}},
Number{Number::largestMantissa}},
{power(2, 63), Number{3, 0}, Number{Number::largestMantissa}},
});
auto test = [this](auto const& c) {
for (auto const& [x, y, z] : c)
@@ -489,8 +497,8 @@ public:
Number{false, maxMantissa, 0, Number::normalized{}},
Number{1, 38}},
// Maximum int64 range
{Number{Number::maxRep, 0},
Number{Number::maxRep, 0},
{Number{Number::largestMantissa, 0},
Number{Number::largestMantissa, 0},
Number{85'070'591'730'234'615'85, 19}},
});
tests(cSmall, cLarge);
@@ -567,8 +575,8 @@ public:
Number::normalized{}}},
// Maximum int64 range
// 85'070'591'730'234'615'847'396'907'784'232'501'249
{Number{Number::maxRep, 0},
Number{Number::maxRep, 0},
{Number{Number::largestMantissa, 0},
Number{Number::largestMantissa, 0},
Number{85'070'591'730'234'615'84, 19}},
});
tests(cSmall, cLarge);
@@ -645,8 +653,8 @@ public:
Number::normalized{}}},
// Maximum int64 range
// 85'070'591'730'234'615'847'396'907'784'232'501'249
{Number{Number::maxRep, 0},
Number{Number::maxRep, 0},
{Number{Number::largestMantissa, 0},
Number{Number::largestMantissa, 0},
Number{85'070'591'730'234'615'84, 19}},
});
tests(cSmall, cLarge);
@@ -715,8 +723,8 @@ public:
Number{1, 38}},
// Maximum int64 range
// 85'070'591'730'234'615'847'396'907'784'232'501'249
{Number{Number::maxRep, 0},
Number{Number::maxRep, 0},
{Number{Number::largestMantissa, 0},
Number{Number::largestMantissa, 0},
Number{85'070'591'730'234'615'85, 19}},
});
tests(cSmall, cLarge);
@@ -1008,10 +1016,10 @@ public:
2,
Number{
false, 3'162'277'660'168'379'330, -9, Number::normalized{}}},
{Number{Number::maxRep},
{Number{Number::largestMantissa},
2,
Number{false, 3'037'000'499'976049692, -9, Number::normalized{}}},
{Number{Number::maxRep},
{Number{Number::largestMantissa},
4,
Number{false, 55'108'98747006743627, -14, Number::normalized{}}},
});
@@ -1070,7 +1078,7 @@ public:
Number{5, -1},
Number{0},
Number{5625, -4},
Number{Number::maxRep},
Number{Number::largestMantissa},
});
test(cSmall);
bool caught = false;
@@ -1690,7 +1698,7 @@ public:
Number const initalXrp{INITIAL_XRP};
BEAST_EXPECT(initalXrp.exponent() > 0);
Number const maxInt64{Number::maxRep};
Number const maxInt64{Number::largestMantissa};
BEAST_EXPECT(maxInt64.exponent() > 0);
// 85'070'591'730'234'615'865'843'651'857'942'052'864 - 38 digits
BEAST_EXPECT(
@@ -1710,7 +1718,7 @@ public:
Number const initalXrp{INITIAL_XRP};
BEAST_EXPECT(initalXrp.exponent() <= 0);
Number const maxInt64{Number::maxRep};
Number const maxInt64{Number::largestMantissa};
BEAST_EXPECT(maxInt64.exponent() <= 0);
// 85'070'591'730'234'615'847'396'907'784'232'501'249 - 38 digits
BEAST_EXPECT(