From dc88533112604cacb65bbb7e7e4063aeb5fdb687 Mon Sep 17 00:00:00 2001 From: Ed Hennis Date: Tue, 28 Apr 2026 22:28:14 -0400 Subject: [PATCH] [WIP] Checkpoint --- src/libxrpl/basics/Number.cpp | 60 +++++++++++++++++--- src/test/basics/Number_test.cpp | 98 +++++++++++++++++++++++++++++++++ 2 files changed, 150 insertions(+), 8 deletions(-) diff --git a/src/libxrpl/basics/Number.cpp b/src/libxrpl/basics/Number.cpp index 3b2b8670cb..c13a517edf 100644 --- a/src/libxrpl/basics/Number.cpp +++ b/src/libxrpl/basics/Number.cpp @@ -222,9 +222,12 @@ Number::Guard::bringIntoRange( { mantissa *= 10; --exponent; + std::cout << "bringIntoRange. mantissa*=10: " << mantissa << ", exponent: " << exponent + << std::endl; } if (exponent < minExponent) { + std::cout << "bringIntoRange. zero\n"; constexpr Number zero = Number{}; negative = zero.negative_; @@ -246,13 +249,50 @@ Number::Guard::doRoundUp( auto r = round(); if (r == 1 || (r == 0 && (mantissa & 1) == 1)) { - ++mantissa; - // Ensure mantissa after incrementing fits within both the - // min/maxMantissa range and is a valid "rep". - if (mantissa > maxMantissa || mantissa > maxRep) + std::cout << "doRoundUp. r: " << r << std::endl; + if (isFeatureEnabled(fixCleanup3_2_0) || !getCurrentTransactionRules()) { - mantissa /= 10; - ++exponent; + // Ensure mantissa after incrementing fits within both the + // min/maxMantissa range and is a valid "rep". + if (mantissa < maxMantissa && mantissa < maxRep) + { + // Nothing unusual here, just increment the mantissa + ++mantissa; + std::cout << "\tmantissa++: " << mantissa << std::endl; + } + 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 maxRep, so adding 1 will have no + // change of bringing it back over. + push(mantissa % 10); + mantissa /= 10; + ++exponent; + XRPL_ASSERT_PARTS( + mantissa < maxMantissa && mantissa < maxRep, + "xrpl::Number::Guard::doRoundUp", + "can't recurse more than once"); + std::cout << "\tmantissa/=10: " << mantissa << ", exponent: " << exponent + << std::endl; + // Here be dragons + doRoundUp(negative, mantissa, exponent, minMantissa, maxMantissa, location); + return; + } + } + else + { + // Need to preserve the incorrect behavior until the fix amendment can be retired, + // because otherwise would risk an unplanned ledger fork. + ++mantissa; + // Ensure mantissa after incrementing fits within both the + // min/maxMantissa range and is a valid "rep". + if (mantissa > maxMantissa || mantissa > maxRep) + { + mantissa /= 10; + ++exponent; + } } } bringIntoRange(negative, mantissa, exponent, minMantissa); @@ -667,6 +707,8 @@ Number::operator*=(Number const& y) auto const& minMantissa = range.min; auto const& maxMantissa = range.max; + std::cout << "zn: " << zn << ", zm: " << zm << ", ze: " << ze << std::endl; + while (zm > maxMantissa || zm > maxRep) { // The following is optimization for: @@ -675,6 +717,9 @@ Number::operator*=(Number const& y) g.push(divu10(zm)); ++ze; } + + std::cout << "zn: " << zn << ", zm: " << zm << ", ze: " << ze << std::endl; + xm = static_cast(zm); xe = ze; g.doRoundUp( @@ -787,8 +832,7 @@ Number::operator/=(Number const& y) return *this; } -Number:: -operator rep() const +Number::operator rep() const { rep drops = mantissa(); int offset = exponent(); diff --git a/src/test/basics/Number_test.cpp b/src/test/basics/Number_test.cpp index cc7ccaa8c2..5d7b185330 100644 --- a/src/test/basics/Number_test.cpp +++ b/src/test/basics/Number_test.cpp @@ -1584,3 +1584,101 @@ public: BEAST_DEFINE_TESTSUITE(Number, basics, xrpl); } // namespace xrpl +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +namespace xrpl { + +class NumberUpwardWrongDirection_test : public beast::unit_test::suite +{ + using BigInt = boost::multiprecision::cpp_int; + + static std::string + fmt(BigInt const& value) + { + std::ostringstream os; + os << value; + auto s = os.str(); + std::string out; + int count = 0; + for (auto it = s.rbegin(); it != s.rend(); ++it) + { + if (count && count % 3 == 0) + out.insert(out.begin(), '_'); + out.insert(out.begin(), *it); + ++count; + } + return out; + } + +public: + void + testUpwardRoundsDown() + { + testcase << "upward rounding produces a value below exact at maxRep cusp"; + + auto const origScale = Number::getMantissaScale(); + auto const origRound = Number::setround(Number::upward); + Number::setMantissaScale(MantissaRange::large); + + constexpr std::int64_t aValue = 1'000'000'000'000'049'863LL; + constexpr std::int64_t bValue = 9'223'372'036'854'315'903LL; + + // JSON -> STAmount -> Number + AccountID const dummyIssuer = AccountID{42u}; + MPTIssue const issue(/*sequence=*/1u, dummyIssuer); + + STAmount const amountA{MPTAmount{aValue}, issue}; + STAmount const amountB{MPTAmount{bValue}, issue}; + + // Public conversion operator: STAmount::operator Number() const. + Number const a = amountA; + Number const b = amountB; + Number const product = a * b; + + // Exact reference in BigInt. + BigInt const exactProduct = BigInt(aValue) * BigInt(bValue); + + // What Number actually stored. + BigInt storedValue = BigInt(product.mantissa()); + for (int i = 0; i < product.exponent(); ++i) + storedValue *= 10; + + BigInt const signedDifference = storedValue - exactProduct; + + log << "\n" + << " a = " << fmt(BigInt(aValue)) << "\n" + << " b = " << fmt(BigInt(bValue)) << "\n" + << " exact a*b = " << fmt(exactProduct) << "\n" + << " stored = " << fmt(storedValue) << "\n" + << " stored - exact = " << fmt(signedDifference) << "\n" + << " upward = " << (signedDifference >= 0 ? "held" : "VIOLATED") << "\n"; + + BEAST_EXPECT(signedDifference >= 0); + BEAST_EXPECT(product.mantissa() == (std::numeric_limits::max() /10) + 1); + BEAST_EXPECT(product.exponent() == 19); + + Number::setround(origRound); + Number::setMantissaScale(origScale); + } + + void + run() override + { + testUpwardRoundsDown(); + } +}; + +BEAST_DEFINE_TESTSUITE(NumberUpwardWrongDirection, basics, ripple); + +} // namespace xrpl \ No newline at end of file