Skip to content
Open
Show file tree
Hide file tree
Changes from 129 commits
Commits
Show all changes
131 commits
Select commit Hold shift + click to select a range
b40d2a8
fix: Fix a rounding error at the Number::maxRep cusp
ximinez Apr 29, 2026
a2b21d7
Fix AI-identified mistakes
ximinez May 7, 2026
b050c15
Fix clang-tidy issues, and more AI complaints
ximinez May 7, 2026
175a041
Merge branch 'develop' into ximinez/number-fix-maxrepcusp
ximinez May 7, 2026
1b6047a
More clang-tidy changes: AMMExtended_test member and Number_test incl…
ximinez May 7, 2026
d03274b
Merge branch 'develop' into ximinez/number-fix-maxrepcusp
ximinez May 7, 2026
22d2703
What is it going to take to get clang-tidy to shut up?
ximinez May 7, 2026
cd0f49a
Address more nitpicky AI comments
ximinez May 7, 2026
5558e1b
Merge branch 'develop' into ximinez/number-fix-maxrepcusp
ximinez May 7, 2026
30334cd
Merge branch 'develop' into ximinez/number-fix-maxrepcusp
ximinez May 11, 2026
5a40416
Merge branch 'develop' into ximinez/number-fix-maxrepcusp
ximinez May 12, 2026
7c9a56f
Merge branch 'develop' into ximinez/number-fix-maxrepcusp
ximinez May 12, 2026
e22938d
AI review feedback: createGuards
ximinez May 12, 2026
d7d5b83
Merge branch 'develop' into ximinez/number-fix-maxrepcusp
ximinez May 12, 2026
47f30c9
Fix broken unit tests: EscrowToken
ximinez May 13, 2026
4c7c019
Merge branch 'develop' into ximinez/number-fix-maxrepcusp
ximinez May 13, 2026
ae03b30
Merge branch 'develop' into ximinez/number-fix-maxrepcusp
ximinez May 14, 2026
46b946b
clang-tidy: missing include
ximinez May 14, 2026
69656d6
clang-tidy: Avoid nested "?:" in global Rules initialization
ximinez May 14, 2026
cd2fcf0
Merge branch 'develop' into ximinez/number-fix-maxrepcusp
ximinez May 14, 2026
b69b924
fixup! clang-tidy: Avoid nested "?:" in global Rules initialization
ximinez May 15, 2026
ddfb7ee
clang-tidy: {}
ximinez May 15, 2026
70c6e01
clang-tidy: this is ridiculous
ximinez May 15, 2026
6964013
Merge remote-tracking branch 'XRPLF/develop' into ximinez/number-fix-…
ximinez May 15, 2026
09f2d06
Merge branch 'develop' into ximinez/number-fix-maxrepcusp
ximinez May 16, 2026
09ae5b7
Merge branch 'develop' into ximinez/number-fix-maxrepcusp
ximinez May 19, 2026
c8947c6
Merge branch 'develop' into ximinez/number-fix-maxrepcusp
ximinez May 19, 2026
4c7ea64
Merge branch 'develop' into ximinez/number-fix-maxrepcusp
ximinez May 19, 2026
71cf996
Review feedback from @tapanito: lambda checks condition in doRoundUp
ximinez May 20, 2026
8b56749
Apply suggestions from Copilot code review
ximinez May 20, 2026
aea19df
Apply suggestions from @Tapanito code review
ximinez May 20, 2026
3a4b92b
Change the priority of the amendments for large mantissas
ximinez May 20, 2026
42fda85
Fix more AMM tests, and to not exclude fixCleanup3_2_0
ximinez May 21, 2026
8e06e78
Merge branch 'develop' into ximinez/number-fix-maxrepcusp
ximinez May 21, 2026
61bdd6f
Merge branch 'develop' into ximinez/number-fix-maxrepcusp
ximinez May 21, 2026
7f64c33
Merge branch 'develop' into ximinez/number-fix-maxrepcusp
ximinez May 21, 2026
4ab886b
Merge branch 'develop' into ximinez/number-fix-maxrepcusp
ximinez May 22, 2026
48b1716
Make Number::operator/= significantly more accurate
ximinez May 23, 2026
75dfc65
Merge branch 'develop' into ximinez/number-fix-maxrepcusp
ximinez May 26, 2026
84ca271
Address some AI review feedback: predeclare, include, format, comment
ximinez May 26, 2026
fbee034
clang-tidy: implicit bool conversion
ximinez May 26, 2026
d684431
clang-tidy: missing header
ximinez May 26, 2026
27456fa
Use the local range instead of calling a function
ximinez May 26, 2026
e89e6f5
Merge remote-tracking branch 'XRPLF/ximinez/number-fix-maxrepcusp' in…
ximinez May 26, 2026
100ec46
Merge branch 'develop' into ximinez/number-fix-maxrepcusp
ximinez May 26, 2026
8ab904d
Merge branch 'ximinez/number-fix-maxrepcusp' into ximinez/number-divi…
ximinez May 26, 2026
a963035
Merge branch 'develop' into ximinez/number-fix-maxrepcusp
ximinez May 26, 2026
e851e80
Merge branch 'ximinez/number-fix-maxrepcusp' into ximinez/number-divi…
ximinez May 26, 2026
1e7876a
Merge branch 'develop' into ximinez/number-fix-maxrepcusp
ximinez May 26, 2026
12670b0
Merge branch 'ximinez/number-fix-maxrepcusp' into ximinez/number-divi…
ximinez May 26, 2026
5abecb9
Significant rewrite
ximinez May 27, 2026
ae9c72b
Test optimization: Number_test::pow10
ximinez May 27, 2026
4ec049e
Minor fixes: missing include, variable init, typo
ximinez May 27, 2026
5333422
Merge remote-tracking branch 'XRPLF/develop' into ximinez/number-divi…
ximinez May 27, 2026
8dcd88e
Tweak how the denominator is handled in division
ximinez May 27, 2026
de2efa5
Remove TMax entirely from normalizeToRange; check type matching directly
ximinez May 27, 2026
18ac8a0
Merge branch 'develop' into ximinez/number-division-accuracy
ximinez May 27, 2026
7b66b42
Merge branch 'develop' into ximinez/number-division-accuracy
ximinez May 27, 2026
dadf4d7
Merge branch 'develop' into ximinez/number-division-accuracy
ximinez May 28, 2026
06a3f76
Skip clang-tidy false positive: misc-include-cleaner
ximinez May 28, 2026
2fdfd2b
Review feedback from Tapanito and AI
ximinez May 28, 2026
cd21d74
Update the testUpwardRoundsDown test to run under all scales
ximinez May 28, 2026
be9ae88
Accept AI suggestion
ximinez May 29, 2026
c056903
Apply suggestions from code review
ximinez May 29, 2026
f6a26ca
CI feedback: Add test cases covering other rounding modes
ximinez May 29, 2026
d1af39d
test: Add another rounding unit test
ximinez May 29, 2026
9f872f2
Include upward, write tests based on "expected" behavior
ximinez May 29, 2026
261508a
Merge remote-tracking branch 'XRPLF/develop' into ximinez/number-roun…
ximinez Jun 1, 2026
35bee87
Experimental: Scale addition operands up to preserve accuracy
ximinez Jun 2, 2026
9e8c3ca
Improve accuracy of Number::operator+=
ximinez Jun 2, 2026
c84939c
Remove the kMaxRep+1 rounding tests
ximinez Jun 2, 2026
51902cd
Revert "Remove the kMaxRep+1 rounding tests"
ximinez Jun 2, 2026
015d9a6
Round mantissas between kMaxRep and kMaxRepUp
ximinez Jun 3, 2026
b5574ba
Add line numbers to Number::to_string test
ximinez Jun 4, 2026
50c0d9f
Handle a whole bunch of edge cases
ximinez Jun 4, 2026
aa88887
Merge branch 'develop' into ximinez/number-round-maxrep-down
ximinez Jun 4, 2026
e1295d1
Merge branch 'ximinez/number-round-maxrep-down' into ximinez/number-r…
ximinez Jun 4, 2026
f1bb4de
clang-tidy: template param names, const correctness, braces
ximinez Jun 5, 2026
74c66d0
refactor: Construct Number::Guard from MantissaRange or relevant fields
ximinez Jun 5, 2026
012c67a
Rework subtraction rounding (again) for more accuracy
ximinez Jun 5, 2026
961ac66
Improve comment descriptions
ximinez Jun 6, 2026
2f70112
Merge remote-tracking branch 'upstream/develop' into ximinez/number-r…
ximinez Jun 6, 2026
3a5c850
Include rounding in failed unit tests
ximinez Jun 6, 2026
8743be8
Rollback Number class changes; show the fix works without side effects
ximinez Jun 6, 2026
c165af4
Revert "Rollback Number class changes; show the fix works without sid…
ximinez Jun 6, 2026
b2cea73
Merge branch 'ximinez/number-round-maxrep-down' into ximinez/number-r…
ximinez Jun 6, 2026
d7ce0e2
Fix formatting, add an assert
ximinez Jun 6, 2026
121786a
Merge remote-tracking branch 'XRPLF/ximinez/number-round-maxrep-down'…
ximinez Jun 6, 2026
fe64b99
Reorganize the subtraction tests
ximinez Jun 6, 2026
f9183aa
Merge remote-tracking branch 'XRPLF/ximinez/number-round-maxrep-down'…
ximinez Jun 7, 2026
308e46d
Clean up tests
ximinez Jun 8, 2026
5bccfe2
clang-tidy: Guard public member variable names; Missing include
ximinez Jun 8, 2026
6da1f22
Merge remote-tracking branch 'XRPLF/ximinez/number-round-maxrep-down'…
ximinez Jun 8, 2026
85980ae
Fix broken unit tests
ximinez Jun 8, 2026
12741a2
Cleanups, mostly from previous merge, plus a few outdated comments
ximinez Jun 9, 2026
d9c63cb
Update to use a new amendment, since this PR will not be part of 3.2.0
ximinez Jun 9, 2026
44f3011
Merge remote-tracking branch 'XRPLF/develop' into ximinez/number-roun…
ximinez Jun 9, 2026
5bafcee
Merge remote-tracking branch 'XRPLF/develop' into ximinez/number-roun…
ximinez Jun 9, 2026
63f1a40
Clean up the "New" names
ximinez Jun 9, 2026
9924b6c
Merge remote-tracking branch 'XRPLF/ximinez/number-round-maxrep-down'…
ximinez Jun 9, 2026
4139ebb
Fix issues introduced by prior merge, and new MantissaRange
ximinez Jun 9, 2026
66ff72f
clang-tidy: rename MantissaScale enums from "3_2_0" to "320"
ximinez Jun 9, 2026
48d1c70
Future proofing: Rename Large and Enabled to Large330 and Enabled330
ximinez Jun 9, 2026
902fbdc
Also fix local 3_2_0 variable names
ximinez Jun 9, 2026
1477747
Merge remote-tracking branch 'XRPLF/ximinez/number-round-maxrep-down'…
ximinez Jun 9, 2026
e4dafa3
Update names due to prior merge
ximinez Jun 9, 2026
d8e03a8
Update more names due to prior merge
ximinez Jun 10, 2026
1bb1d71
test: Add more Number edge case tests, showing failures
ximinez Jun 10, 2026
b087545
Number improvements
ximinez Jun 10, 2026
308e00f
Merge commit 'b087545' into ximinez/number-round-maxrep
ximinez Jun 12, 2026
520efde
Merge commit '5703ca5' into ximinez/number-round-maxrep
ximinez Jun 12, 2026
f8f899d
Merge remote-tracking branch 'XRPLF/ximinez/number-round-maxrep-down'…
ximinez Jun 12, 2026
463fb88
Apply suggestions from AI code review
ximinez Jun 12, 2026
1ef0c5a
Merge branch 'ximinez/number-round-maxrep-down' into ximinez/number-r…
ximinez Jun 12, 2026
3059770
Merge remote-tracking branch 'XRPLF/ximinez/number-round-maxrep-down'…
ximinez Jun 12, 2026
095e141
Apply suggestions from AI code review
ximinez Jun 12, 2026
a63bab2
Cleanups: Comments, variable names, one test case
ximinez Jun 12, 2026
22a7f5b
Fix assertion typo src/libxrpl/basics/Number.cpp
ximinez Jun 12, 2026
6853672
Merge branch 'ximinez/number-round-maxrep-down' into ximinez/number-r…
ximinez Jun 15, 2026
8f51891
Use constexpr in one more place
ximinez Jun 15, 2026
51781c4
Merge remote-tracking branch 'XRPLF/ximinez/number-round-maxrep-down'…
ximinez Jun 15, 2026
2f59cb0
Merge branch 'ximinez/number-round-maxrep-down' into ximinez/number-r…
ximinez Jun 15, 2026
b16a761
Merge branch 'ximinez/number-round-maxrep-down' into ximinez/number-r…
ximinez Jun 16, 2026
069fc99
Merge branch 'ximinez/number-round-maxrep-down' into ximinez/number-r…
ximinez Jun 17, 2026
39d8507
Merge branch 'ximinez/number-round-maxrep-down' into ximinez/number-r…
ximinez Jun 18, 2026
6179b05
Merge remote-tracking branch 'XRPLF/ximinez/number-round-maxrep-down'…
ximinez Jun 18, 2026
da39607
Address review feedback from @TimothyBanks and @gregtatcam
ximinez Jun 18, 2026
8250aa2
Remove changes to Number.cpp
ximinez Jun 19, 2026
4124a1d
Revert "Remove changes to Number.cpp"
ximinez Jun 19, 2026
7cb224e
Update some comments, and const correctness
ximinez Jun 19, 2026
aadf3c6
Remove some overly verbose unit test log messages.
ximinez Jun 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions include/xrpl/basics/Number.h
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,8 @@ class Number final
static constexpr internalrep kMaxRep = std::numeric_limits<rep>::max();
static_assert(kMaxRep == 9'223'372'036'854'775'807);
static_assert(-kMaxRep == std::numeric_limits<rep>::min() + 1);
static constexpr internalrep kMaxRepUp = ((kMaxRep / 10) + 1) * 10;
static_assert(kMaxRepUp == 9'223'372'036'854'775'810ULL);
Comment thread
ximinez marked this conversation as resolved.
Comment thread
ximinez marked this conversation as resolved.

// May need to make unchecked private
struct Unchecked
Expand Down
193 changes: 148 additions & 45 deletions src/libxrpl/basics/Number.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,25 @@ class Number::Guard
void
doDropDigit(T& mantissa, int& exponent) noexcept;

// Modify the result to the correctly rounded value
template <UnsignedMantissa T>
void
doRoundUp(bool& negative, T& mantissa, int& exponent, std::string location);

// Modify the result to the correctly rounded value
template <UnsignedMantissa T>
void
doRoundDown(bool& negative, T& mantissa, int& exponent);

// Modify the result to the correctly rounded value
void
doRound(rep& drops, std::string location);

private:
template <UnsignedMantissa T>
void
pushOverflow(T mantissa);

enum class Round {
// The result is exact. No rounding is needed. Only used if cuspRoundingFix is Enabled330 or
// higher.
Expand All @@ -299,31 +318,16 @@ class Number::Guard
// The result was exactly half-way between two integers. This will round to even.
Even = 0,
// Round up. Always adds 1 (or subtracts 1 in some cases if cuspRoundingFix is not
// Enabled)
// Enabled330)
Up = 1,
};

// Indicate round direction: 1 is up, -1 is down, 0 is even
// Indicate round direction. See Round enum above.
// This enables the client to round towards nearest, and on
// tie, round towards even.
[[nodiscard]] Round
round() const noexcept;

// Modify the result to the correctly rounded value
template <UnsignedMantissa T>
void
doRoundUp(bool& negative, T& mantissa, int& exponent, std::string location);

// Modify the result to the correctly rounded value
template <UnsignedMantissa T>
void
doRoundDown(bool& negative, T& mantissa, int& exponent);

// Modify the result to the correctly rounded value
void
doRound(rep& drops, std::string location) const;

private:
void
doPush(unsigned d) noexcept;

Expand Down Expand Up @@ -406,10 +410,78 @@ Number::Guard::doDropDigit<uint128_t>(uint128_t& mantissa, int& exponent) noexce
++exponent;
}

template <UnsignedMantissa T>
void
Number::Guard::pushOverflow(T mantissa)
{
XRPL_ASSERT(mantissa <= kMaxRepUp, "xrpl::Number::Guard::pushOverflow : valid mantissa");
if (cuspRoundingFix >= MantissaRange::CuspRoundingFix::Enabled330 && mantissa >= kMaxRep &&
mantissa < kMaxRepUp)
{
// Special case rounding rules for the values in the range [kMaxRep, kMaxRepUp).

auto constexpr spread = kMaxRepUp - kMaxRep;
static_assert(spread == 3);

// Round in two steps.

// The first step uses the digits _already_ in the Guard to round the
// intermediate mantissa, using only the last digit. Then update the mantissa for the
// second step. Ultimately, the purpose of this step is to capture rounding where the stored
// digits would change the decision without those digits. (e.g. From just _below_ the
// midpoint to just _above_ the midpoint for ToNearest, or from kMaxRep into the in-between
// for Upward.
// Make an exception if the final digit is 9, because it can only get larger, and we want to
// stay in single digits.
if (auto finalDigit = mantissa % 10; finalDigit < 9)
{
// Intentionally use integer math to get the largest value under the midpoint.
auto constexpr kMidpoint = kMaxRep + (spread / 2);
static_assert(kMidpoint == kMaxRep + 1);
auto const r = round();
if (r == Round::Up || (r == Round::Even && mantissa == kMidpoint))
{
++mantissa;
}
}

if (mantissa == kMaxRep)
{
// If the mantissa ends up exactly kMaxRep, there's nothing more to do.
return;
}

// The second step scales the final digit of the update mantissa proportionally from kMaxRep
// and kMaxRepUp to 1 to 9. It then pushes that scaled digit onto the guard as if it was a
// digit that got removed, but don't actually remove it. This method should be is
// future-proof in case the number of mantissa bits ever changes. (Though for integer values
// that are a power of two themselves, the spread will always be the same.) Effects:
// * For round to nearest
// * if the updated mantissa is below the midpoint, it'll round "down" to kMaxRep
// * if above the midpoint, it'll round "up" to kMaxRepUp
// * it can never be exactly at the midpoint, because kMaxRepUp is always even, and
// kMaxRep is always odd, so don't worry about that case.
// * For round upward, will round up to kMaxRepUp for positive values, down to kMaxRep for
Comment thread
ximinez marked this conversation as resolved.
// negative.
// * For round downward, does the opposite of upward.
// * For round toward zero, always rounds down to kMaxRep.

auto const diff = mantissa - kMaxRep;
auto digit = (diff * 10) / spread;
XRPL_ASSERT(
digit > 0 && digit < 10 && digit != 5,
"xrpl::Number::Guard::pushOverflow : valid overflow digit");

// Don't remove the digit from the mantissa, but add it to the guard as if it was.
push(digit);
}
}

// Returns:
// -1 if Guard is less than half
// 0 if Guard is exactly half
// 1 if Guard is greater than half
// Exact if Guard is _zero_, and appropriate amendments are enabled
// Down if Guard is less than half
// Even if Guard is exactly half
// Up if Guard is greater than half
Number::Guard::Round
Number::Guard::round() const noexcept
{
Expand Down Expand Up @@ -458,13 +530,16 @@ void
Number::Guard::bringIntoRange(bool& negative, T& mantissa, int& exponent)
{
// Bring mantissa back into the minMantissa / maxMantissa range AFTER
// rounding
// rounding. Mantissa should never be 0.
XRPL_ASSERT(mantissa != 0, "xrpl::Number::Guard::bringIntoRange : valid mantissa");
if (mantissa < minMantissa)
{
mantissa *= 10;
--exponent;
}
if (exponent < kMinExponent)
// mantissa should never be 0, but if it _is_ make the result kZero.
if (exponent < kMinExponent ||
(cuspRoundingFix >= MantissaRange::CuspRoundingFix::Enabled330 && mantissa == 0))
{
static constexpr Number kZero = Number{};

Expand All @@ -478,7 +553,9 @@ template <UnsignedMantissa T>
void
Number::Guard::doRoundUp(bool& negative, T& mantissa, int& exponent, std::string location)
{
auto r = round();
pushOverflow(mantissa);

auto const r = round();
if (r == Round::Up || (r == Round::Even && (mantissa & 1) == 1))
{
auto const safeToIncrement = [this](auto const& mantissa) {
Expand All @@ -495,18 +572,27 @@ Number::Guard::doRoundUp(bool& negative, T& mantissa, int& exponent, std::string
}
else
{
// Incrementing the mantissa will require dividing, which will require rounding. So
// _don't_ increment the mantissa. Instead, divide and round recursively. It should
// be impossible to recurse more than once, because once the mantissa is divided by
// 10, it will be _well_ under maxMantissa and kMaxRep, so adding 1 will have no
// chance of bringing it back over.
doDropDigit(mantissa, exponent);
XRPL_ASSERT_PARTS(
safeToIncrement(mantissa),
"xrpl::Number::Guard::doRoundUp",
"can't recurse more than once");
doRoundUp(negative, mantissa, exponent, location);
return;
if (cuspRoundingFix >= MantissaRange::CuspRoundingFix::Enabled330 &&
mantissa > kMaxRep && mantissa < kMaxRepUp)
{
//
mantissa = kMaxRepUp;
}
else
{
// Incrementing the mantissa will require dividing, which will require rounding.
// So _don't_ increment the mantissa. Instead, divide and round recursively. It
// should be impossible to recurse more than once, because once the mantissa is
// divided by 10, it will be _well_ under maxMantissa and kMaxRep, so adding 1
// will have no chance of bringing it back over.
doDropDigit(mantissa, exponent);
XRPL_ASSERT_PARTS(
safeToIncrement(mantissa),
"xrpl::Number::Guard::doRoundUp",
"can't recurse more than once");
doRoundUp(negative, mantissa, exponent, location);
return;
}
}
}
else
Expand All @@ -524,6 +610,12 @@ Number::Guard::doRoundUp(bool& negative, T& mantissa, int& exponent, std::string
}
}
}
else if (
cuspRoundingFix >= MantissaRange::CuspRoundingFix::Enabled330 && mantissa > kMaxRep &&
mantissa < kMaxRepUp)
{
mantissa = kMaxRep;
}
bringIntoRange(negative, mantissa, exponent);
if (exponent > kMaxExponent)
Throw<std::overflow_error>(std::string(location));
Expand All @@ -533,6 +625,8 @@ template <UnsignedMantissa T>
void
Number::Guard::doRoundDown(bool& negative, T& mantissa, int& exponent)
{
// Do not pushOverflow here.

auto r = round();
if (cuspRoundingFix >= MantissaRange::CuspRoundingFix::Enabled330)
{
Expand Down Expand Up @@ -565,7 +659,7 @@ Number::Guard::doRoundDown(bool& negative, T& mantissa, int& exponent)

// Modify the result to the correctly rounded value
void
Number::Guard::doRound(rep& drops, std::string location) const
Number::Guard::doRound(rep& drops, std::string location)
{
auto r = round();
if (r == Round::Up || (r == Round::Even && (drops & 1) == 1))
Expand All @@ -583,6 +677,8 @@ Number::Guard::doRound(rep& drops, std::string location) const
}
++drops;
}
XRPL_ASSERT(drops >= 0, "xrpl::Number::Guard::doRound : positive magnitude");

if (isNegative())
drops = -drops;
}
Expand Down Expand Up @@ -632,7 +728,9 @@ doNormalize(
{
static constexpr auto kMinExponent = Number::kMinExponent;
static constexpr auto kMaxExponent = Number::kMaxExponent;
static constexpr auto kMaxRep = Number::kMaxRep;
auto const repLimit = cuspRoundingFix >= MantissaRange::CuspRoundingFix::Enabled330
? Number::kMaxRepUp
: Number::kMaxRep;

using Guard = Number::Guard;

Expand Down Expand Up @@ -682,17 +780,17 @@ doNormalize(
// 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 > kMaxRep)
if (m > repLimit)
{
if (exponent >= kMaxExponent)
throw std::overflow_error("Number::normalize 1.5");
g.doDropDigit(m, exponent);
}
// Before modification, m should be within the min/max range. After
// modification, it must be less than kMaxRep. In other words, the original
// value should have been no more than kMaxRep * 10.
// (kMaxRep * 10 > maxMantissa)
XRPL_ASSERT_PARTS(m <= kMaxRep, "xrpl::doNormalize", "intermediate mantissa fits in int64");
// modification, it must be less than repLimit. In other words, the original
// value should have been no more than repLimit * 10.
// (repLimit * 10 > maxMantissa)
XRPL_ASSERT_PARTS(m <= repLimit, "xrpl::doNormalize", "intermediate mantissa fits in limit");
mantissa = m;

g.doRoundUp(negative, mantissa, exponent, "Number::normalize 2");
Expand Down Expand Up @@ -824,6 +922,9 @@ Number::operator+=(Number const& y)
auto const& maxMantissa = g.maxMantissa;
auto const cuspRoundingFix = g.cuspRoundingFix;

auto const repLimit =
cuspRoundingFix >= MantissaRange::CuspRoundingFix::Enabled330 ? kMaxRepUp : kMaxRep;

// Bring the exponents of both values into agreement, so the mantissas are on the same scale
// and can be added directly together.

Expand Down Expand Up @@ -908,7 +1009,7 @@ Number::operator+=(Number const& y)
}
else
{
if (xm > maxMantissa || xm > kMaxRep)
if (xm > maxMantissa || xm > repLimit)
{
g.doDropDigit(xm, xe);
}
Expand Down Expand Up @@ -952,7 +1053,7 @@ Number::operator+=(Number const& y)
{
// Grow xm/xe and pull digits out of the Guard until it's back in the
// minMantissa/maxMantissa range.
while (xm < minMantissa && xm * 10 <= kMaxRep)
while (xm < minMantissa && xm * 10 <= repLimit)
{
xm *= 10;
xm -= g.pop();
Expand Down Expand Up @@ -1026,8 +1127,10 @@ Number::operator*=(Number const& y)
g.setNegative();

auto const& maxMantissa = g.maxMantissa;
auto const repLimit =
g.cuspRoundingFix >= MantissaRange::CuspRoundingFix::Enabled330 ? kMaxRepUp : kMaxRep;

while (zm > maxMantissa || zm > kMaxRep)
while (zm > maxMantissa || zm > repLimit)
{
g.doDropDigit(zm, ze);
}
Expand Down
54 changes: 45 additions & 9 deletions src/test/app/LoanBroker_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1452,18 +1452,54 @@ class LoanBroker_test : public beast::unit_test::Suite
env(tx2, Ter(temINVALID));
}

if (Number::getMantissaScale() == MantissaRange::MantissaScale::Large330)
{
auto const dm = power(2, 63) - 1;
BEAST_EXPECTS(dm > kMaxMpTokenAmount, to_string(dm));
tx2[sfDebtMaximum] = dm;
env(tx2, Ter(temINVALID));
}
// For the Large330 scale, 2^63 rounds _down_ to Number::kMaxRep
{
auto const dm = power(2, 63);
BEAST_EXPECTS(dm == kMaxMpTokenAmount, to_string(dm));
tx2[sfDebtMaximum] = dm;
env(tx2, Ter(tesSUCCESS));
}

{
auto const dm = power(2, 63) - 1;
BEAST_EXPECTS(dm < kMaxMpTokenAmount, to_string(dm));
tx2[sfDebtMaximum] = dm;
env(tx2, Ter(tesSUCCESS));
}

{
auto const dm = power(2, 63) - 3;
BEAST_EXPECTS(dm < kMaxMpTokenAmount, to_string(dm));
tx2[sfDebtMaximum] = dm;
env(tx2, Ter(tesSUCCESS));
}

{
auto const dm = power(2, 63) + 3;
BEAST_EXPECTS(dm > kMaxMpTokenAmount, to_string(dm));
tx2[sfDebtMaximum] = dm;
env(tx2, Ter(temINVALID));
}
}
else
{
auto const dm = power(2, 63) - 3;
BEAST_EXPECTS(dm == kMaxMpTokenAmount, to_string(dm));
tx2[sfDebtMaximum] = dm;
env(tx2, Ter(tesSUCCESS));
// For other scales, 2^63 rounds _up_ to Number::kMaxRepUp. Subtracting 1 rounds up
// again.
{
auto const dm = power(2, 63) - 1;
BEAST_EXPECTS(dm > kMaxMpTokenAmount, to_string(dm));
tx2[sfDebtMaximum] = dm;
env(tx2, Ter(temINVALID));
}

{
auto const dm = power(2, 63) - 3;
BEAST_EXPECTS(dm == kMaxMpTokenAmount, to_string(dm));
tx2[sfDebtMaximum] = dm;
env(tx2, Ter(tesSUCCESS));
}
}

{
Expand Down
Loading
Loading