Compare commits

..

1 Commits

Author SHA1 Message Date
Ed Hennis
dc88533112 [WIP] Checkpoint 2026-04-28 22:28:14 -04:00
6 changed files with 154 additions and 574 deletions

View File

@@ -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<internalrep>(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();

View File

@@ -452,489 +452,6 @@ doWithdraw(
return accountSend(view, sourceAcct, dstAcct, amount, j, WaiveTransferFee::Yes);
}
// Direct send w/o fees:
// - Redeeming IOUs and/or sending sender's own IOUs.
// - Create trust line if needed.
// --> bCheckIssuer : normally require issuer to be involved.
static TER
rippleCreditIOU(
ApplyView& view,
AccountID const& uSenderID,
AccountID const& uReceiverID,
STAmount const& saAmount,
bool bCheckIssuer,
beast::Journal j)
{
AccountID const& issuer = saAmount.getIssuer();
Currency const& currency = saAmount.getCurrency();
// Make sure issuer is involved.
XRPL_ASSERT(
!bCheckIssuer || uSenderID == issuer || uReceiverID == issuer,
"xrpl::rippleCreditIOU : matching issuer or don't care");
(void)issuer;
// Disallow sending to self.
XRPL_ASSERT(uSenderID != uReceiverID, "xrpl::rippleCreditIOU : sender is not receiver");
bool const bSenderHigh = uSenderID > uReceiverID;
auto const index = keylet::line(uSenderID, uReceiverID, currency);
XRPL_ASSERT(
!isXRP(uSenderID) && uSenderID != noAccount(), "xrpl::rippleCreditIOU : sender is not XRP");
XRPL_ASSERT(
!isXRP(uReceiverID) && uReceiverID != noAccount(),
"xrpl::rippleCreditIOU : receiver is not XRP");
// If the line exists, modify it accordingly.
if (auto const sleRippleState = view.peek(index))
{
STAmount saBalance = sleRippleState->getFieldAmount(sfBalance);
if (bSenderHigh)
saBalance.negate(); // Put balance in sender terms.
view.creditHook(uSenderID, uReceiverID, saAmount, saBalance);
STAmount const saBefore = saBalance;
saBalance -= saAmount;
JLOG(j.trace()) << "rippleCreditIOU: " << to_string(uSenderID) << " -> "
<< to_string(uReceiverID) << " : before=" << saBefore.getFullText()
<< " amount=" << saAmount.getFullText()
<< " after=" << saBalance.getFullText();
std::uint32_t const uFlags(sleRippleState->getFieldU32(sfFlags));
bool bDelete = false;
// FIXME This NEEDS to be cleaned up and simplified. It's impossible
// for anyone to understand.
if (saBefore > beast::zero
// Sender balance was positive.
&& saBalance <= beast::zero
// Sender is zero or negative.
&& (uFlags & (!bSenderHigh ? lsfLowReserve : lsfHighReserve))
// Sender reserve is set.
&& static_cast<bool>(uFlags & (!bSenderHigh ? lsfLowNoRipple : lsfHighNoRipple)) !=
static_cast<bool>(
view.read(keylet::account(uSenderID))->getFlags() & lsfDefaultRipple) &&
!(uFlags & (!bSenderHigh ? lsfLowFreeze : lsfHighFreeze)) &&
!sleRippleState->getFieldAmount(!bSenderHigh ? sfLowLimit : sfHighLimit)
// Sender trust limit is 0.
&& !sleRippleState->getFieldU32(!bSenderHigh ? sfLowQualityIn : sfHighQualityIn)
// Sender quality in is 0.
&& !sleRippleState->getFieldU32(!bSenderHigh ? sfLowQualityOut : sfHighQualityOut))
// Sender quality out is 0.
{
// Clear the reserve of the sender, possibly delete the line!
adjustOwnerCount(view, view.peek(keylet::account(uSenderID)), -1, j);
// Clear reserve flag.
sleRippleState->setFieldU32(
sfFlags, uFlags & (!bSenderHigh ? ~lsfLowReserve : ~lsfHighReserve));
// Balance is zero, receiver reserve is clear.
bDelete = !saBalance // Balance is zero.
&& !(uFlags & (bSenderHigh ? lsfLowReserve : lsfHighReserve));
// Receiver reserve is clear.
}
if (bSenderHigh)
saBalance.negate();
// Want to reflect balance to zero even if we are deleting line.
sleRippleState->setFieldAmount(sfBalance, saBalance);
// ONLY: Adjust ripple balance.
if (bDelete)
{
return trustDelete(
view,
sleRippleState,
bSenderHigh ? uReceiverID : uSenderID,
!bSenderHigh ? uReceiverID : uSenderID,
j);
}
view.update(sleRippleState);
return tesSUCCESS;
}
STAmount const saReceiverLimit(Issue{currency, uReceiverID});
STAmount saBalance{saAmount};
saBalance.setIssuer(noAccount());
JLOG(j.debug()) << "rippleCreditIOU: "
"create line: "
<< to_string(uSenderID) << " -> " << to_string(uReceiverID) << " : "
<< saAmount.getFullText();
auto const sleAccount = view.peek(keylet::account(uReceiverID));
if (!sleAccount)
return tefINTERNAL; // LCOV_EXCL_LINE
bool const noRipple = (sleAccount->getFlags() & lsfDefaultRipple) == 0;
return trustCreate(
view,
bSenderHigh,
uSenderID,
uReceiverID,
index.key,
sleAccount,
false,
noRipple,
false,
false,
saBalance,
saReceiverLimit,
0,
0,
j);
}
// Send regardless of limits.
// --> saAmount: Amount/currency/issuer to deliver to receiver.
// <-- saActual: Amount actually cost. Sender pays fees.
static TER
rippleSendIOU(
ApplyView& view,
AccountID const& uSenderID,
AccountID const& uReceiverID,
STAmount const& saAmount,
STAmount& saActual,
beast::Journal j,
WaiveTransferFee waiveFee)
{
auto const& issuer = saAmount.getIssuer();
XRPL_ASSERT(
!isXRP(uSenderID) && !isXRP(uReceiverID),
"xrpl::rippleSendIOU : neither sender nor receiver is XRP");
XRPL_ASSERT(uSenderID != uReceiverID, "xrpl::rippleSendIOU : sender is not receiver");
if (uSenderID == issuer || uReceiverID == issuer || issuer == noAccount())
{
// Direct send: redeeming IOUs and/or sending own IOUs.
auto const ter = rippleCreditIOU(view, uSenderID, uReceiverID, saAmount, false, j);
if (ter != tesSUCCESS)
return ter;
saActual = saAmount;
return tesSUCCESS;
}
// Sending 3rd party IOUs: transit.
// Calculate the amount to transfer accounting
// for any transfer fees if the fee is not waived:
saActual = (waiveFee == WaiveTransferFee::Yes) ? saAmount
: multiply(saAmount, transferRate(view, issuer));
JLOG(j.debug()) << "rippleSendIOU> " << to_string(uSenderID) << " - > "
<< to_string(uReceiverID) << " : deliver=" << saAmount.getFullText()
<< " cost=" << saActual.getFullText();
TER terResult = rippleCreditIOU(view, issuer, uReceiverID, saAmount, true, j);
if (tesSUCCESS == terResult)
terResult = rippleCreditIOU(view, uSenderID, issuer, saActual, true, j);
return terResult;
}
template <class TAsset>
static TER
doSendMulti(
std::string const& name,
ApplyView& view,
AccountID const& senderID,
TAsset const& issue,
MultiplePaymentDestinations const& receivers,
STAmount& actual,
beast::Journal j,
WaiveTransferFee waiveFee,
// Don't pass back parameters that the caller already has
std::function<
TER(AccountID const& senderID,
AccountID const& receiverID,
STAmount const& amount,
bool checkIssuer)> doCredit,
std::function<
TER(AccountID const& issuer, STAmount const& takeFromSender, STAmount const& amount)>
preMint = {})
{
// Use the same pattern for all the SendMulti functions to help avoid
// divergence and copy/paste errors.
auto const& issuer = issue.getIssuer();
// These values may not stay in sync
STAmount takeFromSender{issue};
actual = takeFromSender;
// Failures return immediately.
for (auto const& r : receivers)
{
auto const& receiverID = r.first;
STAmount amount{issue, r.second};
if (amount < beast::zero)
{
return tecINTERNAL; // LCOV_EXCL_LINE
}
/* If we aren't sending anything or if the sender is the same as the
* receiver then we don't need to do anything.
*/
if (!amount || (senderID == receiverID))
continue;
using namespace std::string_literals;
XRPL_ASSERT(!isXRP(receiverID), ("xrpl::"s + name + " : receiver is not XRP").c_str());
if (senderID == issuer || receiverID == issuer || issuer == noAccount())
{
if (preMint)
{
if (auto const ter = preMint(issuer, takeFromSender, amount))
return ter;
}
// Direct send: redeeming IOUs and/or sending own IOUs.
if (auto const ter = doCredit(senderID, receiverID, amount, false))
return ter;
actual += amount;
// Do not add amount to takeFromSender, because doCredit took
// it.
continue;
}
// Sending 3rd party: transit.
// Calculate the amount to transfer accounting
// for any transfer fees if the fee is not waived:
STAmount actualSend = (waiveFee == WaiveTransferFee::Yes || issue.native())
? amount
: multiply(amount, transferRate(view, amount));
actual += actualSend;
takeFromSender += actualSend;
JLOG(j.debug()) << name << "> " << to_string(senderID) << " - > " << to_string(receiverID)
<< " : deliver=" << amount.getFullText()
<< " cost=" << actualSend.getFullText();
if (TER const terResult = doCredit(issuer, receiverID, amount, true))
return terResult;
}
if (senderID != issuer && takeFromSender)
{
if (TER const terResult = doCredit(senderID, issuer, takeFromSender, true))
return terResult;
}
return tesSUCCESS;
}
// Send regardless of limits.
// --> receivers: Amount/currency/issuer to deliver to receivers.
// <-- saActual: Amount actually cost to sender. Sender pays fees.
static TER
rippleSendMultiIOU(
ApplyView& view,
AccountID const& senderID,
Issue const& issue,
MultiplePaymentDestinations const& receivers,
STAmount& actual,
beast::Journal j,
WaiveTransferFee waiveFee)
{
XRPL_ASSERT(!isXRP(senderID), "xrpl::rippleSendMultiIOU : sender is not XRP");
auto doCredit = [&view, j](
AccountID const& senderID,
AccountID const& receiverID,
STAmount const& amount,
bool checkIssuer) {
return rippleCreditIOU(view, senderID, receiverID, amount, checkIssuer, j);
};
return doSendMulti(
"rippleSendMultiIOU", view, senderID, issue, receivers, actual, j, waiveFee, doCredit);
}
static TER
rippleCreditMPT(
ApplyView& view,
AccountID const& uSenderID,
AccountID const& uReceiverID,
STAmount const& saAmount,
beast::Journal j)
{
// Do not check MPT authorization here - it must have been checked earlier
auto const mptID = keylet::mptIssuance(saAmount.get<MPTIssue>().getMptID());
auto const& issuer = saAmount.getIssuer();
auto sleIssuance = view.peek(mptID);
if (!sleIssuance)
return tecOBJECT_NOT_FOUND;
if (uSenderID == issuer)
{
(*sleIssuance)[sfOutstandingAmount] += saAmount.mpt().value();
view.update(sleIssuance);
}
else
{
auto const mptokenID = keylet::mptoken(mptID.key, uSenderID);
if (auto sle = view.peek(mptokenID))
{
auto const amt = sle->getFieldU64(sfMPTAmount);
auto const pay = saAmount.mpt().value();
if (amt < pay)
return tecINSUFFICIENT_FUNDS;
(*sle)[sfMPTAmount] = amt - pay;
view.update(sle);
}
else
return tecNO_AUTH;
}
if (uReceiverID == issuer)
{
auto const outstanding = sleIssuance->getFieldU64(sfOutstandingAmount);
auto const redeem = saAmount.mpt().value();
if (outstanding >= redeem)
{
sleIssuance->setFieldU64(sfOutstandingAmount, outstanding - redeem);
view.update(sleIssuance);
}
else
return tecINTERNAL; // LCOV_EXCL_LINE
}
else
{
auto const mptokenID = keylet::mptoken(mptID.key, uReceiverID);
if (auto sle = view.peek(mptokenID))
{
(*sle)[sfMPTAmount] += saAmount.mpt().value();
view.update(sle);
}
else
return tecNO_AUTH;
}
return tesSUCCESS;
}
static TER
rippleSendMPT(
ApplyView& view,
AccountID const& uSenderID,
AccountID const& uReceiverID,
STAmount const& saAmount,
STAmount& saActual,
beast::Journal j,
WaiveTransferFee waiveFee)
{
XRPL_ASSERT(uSenderID != uReceiverID, "xrpl::rippleSendMPT : sender is not receiver");
// Safe to get MPT since rippleSendMPT is only called by accountSendMPT
auto const& issuer = saAmount.getIssuer();
auto const sle = view.read(keylet::mptIssuance(saAmount.get<MPTIssue>().getMptID()));
if (!sle)
return tecOBJECT_NOT_FOUND;
if (uSenderID == issuer || uReceiverID == issuer)
{
// if sender is issuer, check that the new OutstandingAmount will not
// exceed MaximumAmount
if (uSenderID == issuer)
{
auto const sendAmount = saAmount.mpt().value();
auto const maximumAmount = sle->at(~sfMaximumAmount).value_or(maxMPTokenAmount);
if (sendAmount > maximumAmount ||
sle->getFieldU64(sfOutstandingAmount) > maximumAmount - sendAmount)
return tecPATH_DRY;
}
// Direct send: redeeming MPTs and/or sending own MPTs.
auto const ter = rippleCreditMPT(view, uSenderID, uReceiverID, saAmount, j);
if (ter != tesSUCCESS)
return ter;
saActual = saAmount;
return tesSUCCESS;
}
// Sending 3rd party MPTs: transit.
saActual = (waiveFee == WaiveTransferFee::Yes)
? saAmount
: multiply(saAmount, transferRate(view, saAmount.get<MPTIssue>().getMptID()));
JLOG(j.debug()) << "rippleSendMPT> " << to_string(uSenderID) << " - > "
<< to_string(uReceiverID) << " : deliver=" << saAmount.getFullText()
<< " cost=" << saActual.getFullText();
if (auto const terResult = rippleCreditMPT(view, issuer, uReceiverID, saAmount, j);
terResult != tesSUCCESS)
return terResult;
return rippleCreditMPT(view, uSenderID, issuer, saActual, j);
}
static TER
rippleSendMultiMPT(
ApplyView& view,
AccountID const& senderID,
MPTIssue const& mptIssue,
MultiplePaymentDestinations const& receivers,
STAmount& actual,
beast::Journal j,
WaiveTransferFee waiveFee)
{
auto const sle = view.read(keylet::mptIssuance(mptIssue.getMptID()));
if (!sle)
return tecOBJECT_NOT_FOUND;
auto preMint = [&](AccountID const& issuer,
STAmount const& takeFromSender,
STAmount const& amount) -> TER {
// if sender is issuer, check that the new OutstandingAmount will
// not exceed MaximumAmount
if (senderID == issuer)
{
XRPL_ASSERT_PARTS(
takeFromSender == beast::zero,
"rippler::rippleSendMultiMPT",
"sender == issuer, takeFromSender == zero");
auto const sendAmount = amount.mpt().value();
auto const maximumAmount = sle->at(~sfMaximumAmount).value_or(maxMPTokenAmount);
if (sendAmount > maximumAmount ||
sle->getFieldU64(sfOutstandingAmount) > maximumAmount - sendAmount)
return tecPATH_DRY;
}
return tesSUCCESS;
};
auto doCredit =
[&view, j](
AccountID const& senderID, AccountID const& receiverID, STAmount const& amount, bool) {
return rippleCreditMPT(view, senderID, receiverID, amount, j);
};
return doSendMulti(
"rippleSendMultiMPT",
view,
senderID,
mptIssue,
receivers,
actual,
j,
waiveFee,
doCredit,
preMint);
}
TER
cleanupOnAccountDelete(
ApplyView& view,

View File

@@ -21,7 +21,7 @@ offerDelete(ApplyView& view, std::shared_ptr<SLE> const& sle, beast::Journal j)
if (!sle)
return tesSUCCESS;
auto offerIndex = sle->key();
auto const owner = sle->getAccountID(sfAccount);
auto owner = sle->getAccountID(sfAccount);
// Detect legacy directories.
uint256 const uDirectory = sle->getFieldH256(sfBookDirectory);

View File

@@ -579,7 +579,7 @@ requireAuth(ReadView const& view, Issue const& issue, AccountID const& account,
return tesSUCCESS;
}
[[nodiscard]] TER
TER
canTransfer(ReadView const& view, Issue const& issue, AccountID const& from, AccountID const& to)
{
if (issue.native())
@@ -617,7 +617,7 @@ canTransfer(ReadView const& view, Issue const& issue, AccountID const& from, Acc
//
//------------------------------------------------------------------------------
[[nodiscard]] TER
TER
addEmptyHolding(
ApplyView& view,
AccountID const& accountID,
@@ -671,7 +671,7 @@ addEmptyHolding(
journal);
}
[[nodiscard]] TER
TER
removeEmptyHolding(
ApplyView& view,
AccountID const& accountID,

View File

@@ -938,85 +938,6 @@ accountSendMultiIOU(
<< receivers.size() << " receivers.";
}
auto doCredit = [&view, &sender, &receivers, j](
AccountID const& senderID,
AccountID const& receiverID,
STAmount const& amount,
bool /*checkIssuer*/) -> TER {
if (!senderID)
{
SLE::pointer receiver =
receiverID != beast::zero ? view.peek(keylet::account(receiverID)) : SLE::pointer();
if (auto stream = j.trace())
{
std::string receiver_bal("-");
if (receiver)
receiver_bal = receiver->getFieldAmount(sfBalance).getFullText();
stream << "accountSendMultiIOU> " << to_string(senderID) << " -> "
<< to_string(receiverID) << " (" << receiver_bal
<< ") : " << amount.getFullText();
}
if (receiver)
{
// Increment XRP balance.
auto const rcvBal = receiver->getFieldAmount(sfBalance);
receiver->setFieldAmount(sfBalance, rcvBal + amount);
view.creditHook(xrpAccount(), receiverID, amount, -rcvBal);
view.update(receiver);
}
if (auto stream = j.trace())
{
std::string receiver_bal("-");
if (receiver)
receiver_bal = receiver->getFieldAmount(sfBalance).getFullText();
stream << "accountSendMultiIOU< " << to_string(senderID) << " -> "
<< to_string(receiverID) << " (" << receiver_bal
<< ") : " << amount.getFullText();
}
return tesSUCCESS;
}
// Sender
if (sender)
{
if (sender->getFieldAmount(sfBalance) < amount)
{
return TER{tecFAILED_PROCESSING};
}
else
{
auto const sndBal = sender->getFieldAmount(sfBalance);
view.creditHook(senderID, xrpAccount(), amount, sndBal);
// Decrement XRP balance.
sender->setFieldAmount(sfBalance, sndBal - amount);
view.update(sender);
}
}
if (auto stream = j.trace())
{
std::string sender_bal("-");
if (sender)
sender_bal = sender->getFieldAmount(sfBalance).getFullText();
stream << "accountSendMultiIOU< " << to_string(senderID) << " (" << sender_bal
<< ") -> " << receivers.size() << " receivers.";
}
return tesSUCCESS;
};
return doSendMulti(
"accountSendMultiIOU", view, senderID, issue, receivers, actual, j, waiveFee, doCredit);
// Failures return immediately.
STAmount takeFromSender{issue};
for (auto const& r : receivers)

View File

@@ -1584,3 +1584,101 @@ public:
BEAST_DEFINE_TESTSUITE(Number, basics, xrpl);
} // namespace xrpl
#include <xrpl/basics/Number.h>
#include <xrpl/beast/unit_test.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/MPTIssue.h>
#include <xrpl/protocol/STAmount.h>
#include <boost/multiprecision/cpp_int.hpp>
#include <cstdint>
#include <limits>
#include <sstream>
#include <string>
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<std::int64_t>::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