diff --git a/Builds/VisualStudio2013/RippleD.vcxproj b/Builds/VisualStudio2013/RippleD.vcxproj
index 0cd80d13aa..51c4243aac 100644
--- a/Builds/VisualStudio2013/RippleD.vcxproj
+++ b/Builds/VisualStudio2013/RippleD.vcxproj
@@ -2598,7 +2598,7 @@
-
+
True
diff --git a/Builds/VisualStudio2013/RippleD.vcxproj.filters b/Builds/VisualStudio2013/RippleD.vcxproj.filters
index d338e23361..e51d83de8f 100644
--- a/Builds/VisualStudio2013/RippleD.vcxproj.filters
+++ b/Builds/VisualStudio2013/RippleD.vcxproj.filters
@@ -3738,7 +3738,7 @@
ripple\module\data\protocol
-
+
ripple\module\data\protocol
diff --git a/src/ripple/module/app/book/Quality.h b/src/ripple/module/app/book/Quality.h
index ae053c43ca..97bf2a2381 100644
--- a/src/ripple/module/app/book/Quality.h
+++ b/src/ripple/module/app/book/Quality.h
@@ -80,7 +80,7 @@ public:
Amount
rate () const
{
- return Amount::setRate (m_value);
+ return amountFromQuality (m_value);
}
/** Returns the scaled amount with in capped.
diff --git a/src/ripple/module/app/book/impl/Quality.cpp b/src/ripple/module/app/book/impl/Quality.cpp
index 5ec2e68c22..334361a740 100644
--- a/src/ripple/module/app/book/impl/Quality.cpp
+++ b/src/ripple/module/app/book/impl/Quality.cpp
@@ -31,7 +31,7 @@ Quality::Quality (std::uint64_t value)
}
Quality::Quality (Amounts const& amount)
- : m_value (Amount::getRate (amount.out, amount.in))
+ : m_value (getRate (amount.out, amount.in))
{
}
@@ -72,7 +72,7 @@ Quality::ceil_in (Amounts const& amount, Amount const& limit) const
{
if (amount.in > limit)
{
- Amounts result (limit, Amount::divRound (
+ Amounts result (limit, divRound (
limit, rate(), amount.out, true));
// Clamp out
if (result.out > amount.out)
@@ -89,7 +89,7 @@ Quality::ceil_out (Amounts const& amount, Amount const& limit) const
{
if (amount.out > limit)
{
- Amounts result (Amount::mulRound (
+ Amounts result (mulRound (
limit, rate(), amount.in, true), limit);
// Clamp in
if (result.in > amount.in)
@@ -110,10 +110,10 @@ composed_quality (Quality const& lhs, Quality const& rhs)
Amount const rhs_rate (rhs.rate ());
assert (rhs_rate != zero);
- Amount const rate (Amount::mulRound (lhs_rate, rhs_rate, true));
+ Amount const rate (mulRound (lhs_rate, rhs_rate, true));
- std::uint64_t const stored_exponent (rate.getExponent () + 100);
- std::uint64_t const stored_mantissa (rate.getMantissa ());
+ std::uint64_t const stored_exponent (rate.exponent () + 100);
+ std::uint64_t const stored_mantissa (rate.mantissa());
assert ((stored_exponent > 0) && (stored_exponent <= 255));
diff --git a/src/ripple/module/app/book/impl/Taker.cpp b/src/ripple/module/app/book/impl/Taker.cpp
index ca7452866c..65023864f9 100644
--- a/src/ripple/module/app/book/impl/Taker.cpp
+++ b/src/ripple/module/app/book/impl/Taker.cpp
@@ -57,14 +57,14 @@ Taker::remaining_offer () const
assert (m_remain.in > zero);
// We scale the output based on the remaining input:
- return Amounts (m_remain.in, Amount::divRound (
+ return Amounts (m_remain.in, divRound (
m_remain.in, m_quality.rate (), m_remain.out, true));
}
assert (m_remain.out > zero);
// We scale the input based on the remaining output:
- return Amounts (Amount::mulRound (
+ return Amounts (mulRound (
m_remain.out, m_quality.rate (), m_remain.in, true), m_remain.out);
}
@@ -94,7 +94,7 @@ Taker::flow (Amounts amount, Offer const& offer, Account const& taker)
{
Amount const taker_charge (Amount::saFromRate (taker_charge_rate));
amount = offer.quality ().ceil_in (amount,
- Amount::divide (taker_funds, taker_charge));
+ divide (taker_funds, taker_charge));
}
// Best flow the owner can get.
@@ -118,7 +118,7 @@ Taker::flow (Amounts amount, Offer const& offer, Account const& taker)
{
Amount const owner_charge (Amount::saFromRate (owner_charge_rate));
owner_amount = offer.quality ().ceil_out (owner_amount,
- Amount::divide (owner_funds, owner_charge));
+ divide (owner_funds, owner_charge));
}
// Calculate the amount that will flow through the offer
diff --git a/src/ripple/module/app/ledger/LedgerEntrySet.cpp b/src/ripple/module/app/ledger/LedgerEntrySet.cpp
index 0394a6be55..c8ed632c16 100644
--- a/src/ripple/module/app/ledger/LedgerEntrySet.cpp
+++ b/src/ripple/module/app/ledger/LedgerEntrySet.cpp
@@ -1278,7 +1278,7 @@ STAmount LedgerEntrySet::rippleTransferFee (
STAmount saTransitRate (
noIssue(), static_cast (uTransitRate), -9);
- STAmount saTransferTotal = STAmount::multiply (
+ STAmount saTransferTotal = multiply (
saAmount, saTransitRate, saAmount.issue ());
STAmount saTransferFee = saTransferTotal - saAmount;
diff --git a/src/ripple/module/app/ledger/OrderBookIterator.h b/src/ripple/module/app/ledger/OrderBookIterator.h
index b9a03019e1..11e400252c 100644
--- a/src/ripple/module/app/ledger/OrderBookIterator.h
+++ b/src/ripple/module/app/ledger/OrderBookIterator.h
@@ -60,7 +60,7 @@ public:
*/
STAmount getCurrentRate () const
{
- return STAmount::setRate (getCurrentQuality());
+ return amountFromQuality (getCurrentQuality());
}
/** Get the current quality
diff --git a/src/ripple/module/app/misc/NetworkOPs.cpp b/src/ripple/module/app/misc/NetworkOPs.cpp
index 056c49b6e9..7a313dba4e 100644
--- a/src/ripple/module/app/misc/NetworkOPs.cpp
+++ b/src/ripple/module/app/misc/NetworkOPs.cpp
@@ -3140,7 +3140,7 @@ void NetworkOPsImp::getBookPage (
else
{
uTipIndex = sleOfferDir->getIndex ();
- saDirRate = STAmount::setRate (Ledger::getQuality (uTipIndex));
+ saDirRate = amountFromQuality (Ledger::getQuality (uTipIndex));
lesActive.dirFirst (
uTipIndex, sleOfferDir, uBookEntry, offerIndex);
@@ -3220,7 +3220,7 @@ void NetworkOPsImp::getBookPage (
{
// Need to charge a transfer fee to offer owner.
uOfferRate = uTransferRate;
- saOwnerFundsLimit = STAmount::divide (
+ saOwnerFundsLimit = divide (
saOwnerFunds, STAmount (noIssue(), uOfferRate, -9));
// TODO(tom): why -9?
}
@@ -3243,7 +3243,7 @@ void NetworkOPsImp::getBookPage (
saTakerGetsFunded.setJson (jvOffer[jss::taker_gets_funded]);
std::min (
- saTakerPays, STAmount::multiply (
+ saTakerPays, multiply (
saTakerGetsFunded, saDirRate, saTakerPays)).setJson
(jvOffer[jss::taker_pays_funded]);
}
@@ -3252,7 +3252,7 @@ void NetworkOPsImp::getBookPage (
? saTakerGetsFunded
: std::min (
saOwnerFunds,
- STAmount::multiply (
+ multiply (
saTakerGetsFunded,
STAmount (noIssue(),
uOfferRate, -9)));
@@ -3388,7 +3388,7 @@ void NetworkOPsImp::getBookPage (
uOfferRate = uTransferRate;
// TODO(tom): where does -9 come from?!
STAmount amount (noIssue(), uOfferRate, -9);
- saOwnerFundsLimit = STAmount::divide (saOwnerFunds, amount);
+ saOwnerFundsLimit = divide (saOwnerFunds, amount);
}
else
{
@@ -3410,7 +3410,7 @@ void NetworkOPsImp::getBookPage (
// TOOD(tom): The result of this expression is not used - what's
// going on here?
- std::min (saTakerPays, STAmount::multiply (
+ std::min (saTakerPays, multiply (
saTakerGetsFunded, saDirRate, saTakerPays)).setJson (
jvOffer[jss::taker_pays_funded]);
}
@@ -3418,7 +3418,7 @@ void NetworkOPsImp::getBookPage (
STAmount saOwnerPays = (uOfferRate == QUALITY_ONE)
? saTakerGetsFunded
: std::min (saOwnerFunds,
- STAmount::multiply (
+ multiply (
saTakerGetsFunded, STAmount (
noIssue(), uOfferRate,
-9)));
diff --git a/src/ripple/module/app/paths/PathRequest.cpp b/src/ripple/module/app/paths/PathRequest.cpp
index f4cac1cac2..015db503a1 100644
--- a/src/ripple/module/app/paths/PathRequest.cpp
+++ b/src/ripple/module/app/paths/PathRequest.cpp
@@ -268,7 +268,7 @@ int PathRequest::parseJson (Json::Value const& jvParams, bool complete)
if (jvParams.isMember ("destination_amount"))
{
- if (!saDstAmount.bSetJson (jvParams["destination_amount"]) ||
+ if (! amountFromJsonNoThrow (saDstAmount, jvParams["destination_amount"]) ||
(saDstAmount.getCurrency ().isZero () && saDstAmount.getIssuer ().isNonZero ()) ||
(saDstAmount.getCurrency () == badCurrency()) ||
saDstAmount <= zero)
diff --git a/src/ripple/module/app/paths/Pathfinder.cpp b/src/ripple/module/app/paths/Pathfinder.cpp
index 52cf96e07e..54ef722d85 100644
--- a/src/ripple/module/app/paths/Pathfinder.cpp
+++ b/src/ripple/module/app/paths/Pathfinder.cpp
@@ -302,7 +302,7 @@ STPathSet Pathfinder::filterPaths(int iMaxPaths, STPath& extraPath)
std::vector vMap;
// Ignore paths that move only very small amounts
- auto saMinDstAmount = STAmount::divide(
+ auto saMinDstAmount = divide(
mDstAmount, STAmount(iMaxPaths + 2), mDstAmount);
// Build map of quality to entry.
@@ -348,7 +348,7 @@ STPathSet Pathfinder::filterPaths(int iMaxPaths, STPath& extraPath)
else
{
std::uint64_t uQuality (
- STAmount::getRate (rc.actualAmountOut, rc.actualAmountIn));
+ getRate (rc.actualAmountOut, rc.actualAmountIn));
WriteLog (lsDEBUG, Pathfinder) <<
"findPaths: quality: " << uQuality <<
diff --git a/src/ripple/module/app/paths/RippleCalc.cpp b/src/ripple/module/app/paths/RippleCalc.cpp
index ce91cf2c03..d947cdb7b2 100644
--- a/src/ripple/module/app/paths/RippleCalc.cpp
+++ b/src/ripple/module/app/paths/RippleCalc.cpp
@@ -193,7 +193,7 @@ TER RippleCalc::rippleCalculate ()
// When processing, we don't want to complicate directory walking with
// deletion.
const std::uint64_t uQualityLimit = inputFlags.limitQuality ?
- STAmount::getRate (saDstAmountReq_, saMaxAmountReq_) : 0;
+ getRate (saDstAmountReq_, saMaxAmountReq_) : 0;
// Offers that became unfunded.
OfferSet unfundedOffersFromBestPaths;
diff --git a/src/ripple/module/app/paths/cursor/AdvanceNode.cpp b/src/ripple/module/app/paths/cursor/AdvanceNode.cpp
index f7290e70fa..6a946073f3 100644
--- a/src/ripple/module/app/paths/cursor/AdvanceNode.cpp
+++ b/src/ripple/module/app/paths/cursor/AdvanceNode.cpp
@@ -103,7 +103,7 @@ TER PathCursor::advanceNode (bool const bReverse) const
{
// Our quality changed since last iteration.
// Use the rate from the directory.
- node().saOfrRate = STAmount::setRate (
+ node().saOfrRate = amountFromQuality (
Ledger::getQuality (node().directory.current));
// For correct ratio
node().uEntry = 0;
diff --git a/src/ripple/module/app/paths/cursor/DeliverNodeForward.cpp b/src/ripple/module/app/paths/cursor/DeliverNodeForward.cpp
index 62d6292376..e9f70a78b9 100644
--- a/src/ripple/module/app/paths/cursor/DeliverNodeForward.cpp
+++ b/src/ripple/module/app/paths/cursor/DeliverNodeForward.cpp
@@ -97,14 +97,14 @@ TER PathCursor::deliverNodeForward (
node().saRevDeliver - node().saFwdDeliver);
// Offer maximum in - Limited by by payout.
- auto saInFunded = STAmount::mulRound (
+ auto saInFunded = mulRound (
saOutPassFunded,
node().saOfrRate,
node().saTakerPays,
true);
// Offer maximum in with fees.
- auto saInTotal = STAmount::mulRound (saInFunded, saInFeeRate, true);
+ auto saInTotal = mulRound (saInFunded, saInFeeRate, true);
auto saInRemaining = saInReq - saInAct - saInFees;
if (saInRemaining < zero)
@@ -115,11 +115,11 @@ TER PathCursor::deliverNodeForward (
// In without fees.
auto saInPassAct = std::min (
- node().saTakerPays, STAmount::divRound (
+ node().saTakerPays, divRound (
saInSum, saInFeeRate, true));
// Out limited by in remaining.
- auto outPass = STAmount::divRound (
+ auto outPass = divRound (
saInPassAct, node().saOfrRate, node().saTakerGets, true);
STAmount saOutPassMax = std::min (saOutPassFunded, outPass);
@@ -235,10 +235,10 @@ TER PathCursor::deliverNodeForward (
// previous.
assert (saOutPassAct < saOutPassMax);
- auto inPassAct = STAmount::mulRound (
+ auto inPassAct = mulRound (
saOutPassAct, node().saOfrRate, saInReq, true);
saInPassAct = std::min (node().saTakerPays, inPassAct);
- auto inPassFees = STAmount::mulRound (
+ auto inPassFees = mulRound (
saInPassAct, saInFeeRate, true);
saInPassFees = std::min (saInPassFeesMax, inPassFees);
}
diff --git a/src/ripple/module/app/paths/cursor/DeliverNodeReverse.cpp b/src/ripple/module/app/paths/cursor/DeliverNodeReverse.cpp
index 1885166f63..c494445f83 100644
--- a/src/ripple/module/app/paths/cursor/DeliverNodeReverse.cpp
+++ b/src/ripple/module/app/paths/cursor/DeliverNodeReverse.cpp
@@ -151,7 +151,7 @@ TER PathCursor::deliverNodeReverse (
// as a cost to taker.
//
// Round down: prefer liquidity rather than microscopic fees.
- STAmount saOutPlusFees = STAmount::mulRound (
+ STAmount saOutPlusFees = mulRound (
saOutPassAct, saOutFeeRate, false);
// Offer out with fees.
@@ -172,7 +172,7 @@ TER PathCursor::deliverNodeReverse (
// Round up: prefer liquidity rather than microscopic fees. But,
// limit by requested.
- auto fee = STAmount::divRound (saOutPlusFees, saOutFeeRate, true);
+ auto fee = divRound (saOutPlusFees, saOutFeeRate, true);
saOutPassAct = std::min (saOutPassReq, fee);
WriteLog (lsTRACE, RippleCalc)
@@ -183,7 +183,7 @@ TER PathCursor::deliverNodeReverse (
}
// Compute portion of input needed to cover actual output.
- auto outputFee = STAmount::mulRound (
+ auto outputFee = mulRound (
saOutPassAct, node().saOfrRate, node().saTakerPays, true);
STAmount saInPassReq = std::min (node().saTakerPays, outputFee);
STAmount saInPassAct;
@@ -248,10 +248,10 @@ TER PathCursor::deliverNodeReverse (
if (saInPassAct < saInPassReq)
{
// Adjust output to conform to limited input.
- auto outputRequirements = STAmount::divRound (
+ auto outputRequirements = divRound (
saInPassAct, node().saOfrRate, node().saTakerGets, true);
saOutPassAct = std::min (saOutPassReq, outputRequirements);
- auto outputFees = STAmount::mulRound (
+ auto outputFees = mulRound (
saOutPassAct, saOutFeeRate, true);
saOutPlusFees = std::min (node().saOfferFunds, outputFees);
diff --git a/src/ripple/module/app/paths/cursor/ForwardLiquidityForAccount.cpp b/src/ripple/module/app/paths/cursor/ForwardLiquidityForAccount.cpp
index 715a257600..3bc3bd9230 100644
--- a/src/ripple/module/app/paths/cursor/ForwardLiquidityForAccount.cpp
+++ b/src/ripple/module/app/paths/cursor/ForwardLiquidityForAccount.cpp
@@ -152,7 +152,7 @@ TER PathCursor::forwardLiquidityForAccount () const
auto& saCurReceive = pathState_.outPass();
STAmount saIssueCrd = uQualityIn >= QUALITY_ONE
? previousNode().saFwdIssue // No fee.
- : STAmount::mulRound (
+ : mulRound (
previousNode().saFwdIssue,
STAmount (noIssue(), uQualityIn, -9),
true); // Amount to credit.
diff --git a/src/ripple/module/app/paths/cursor/NextIncrement.cpp b/src/ripple/module/app/paths/cursor/NextIncrement.cpp
index 053bb6b83e..9808a442a4 100644
--- a/src/ripple/module/app/paths/cursor/NextIncrement.cpp
+++ b/src/ripple/module/app/paths/cursor/NextIncrement.cpp
@@ -54,7 +54,7 @@ void PathCursor::nextIncrement (LedgerEntrySet const& lesCheckpoint) const
throw std::runtime_error ("Made no progress.");
// Calculate relative quality.
- pathState_.setQuality(STAmount::getRate (
+ pathState_.setQuality(getRate (
pathState_.outPass(), pathState_.inPass()));
WriteLog (lsTRACE, RippleCalc)
diff --git a/src/ripple/module/app/paths/cursor/RippleLiquidity.cpp b/src/ripple/module/app/paths/cursor/RippleLiquidity.cpp
index 187807a8c3..611042046c 100644
--- a/src/ripple/module/app/paths/cursor/RippleLiquidity.cpp
+++ b/src/ripple/module/app/paths/cursor/RippleLiquidity.cpp
@@ -126,7 +126,7 @@ void rippleLiquidity (
// If the quality is worse than the previous
WriteLog (lsTRACE, RippleCalc) << "rippleLiquidity: Fee";
- std::uint64_t uRate = STAmount::getRate (
+ std::uint64_t uRate = getRate (
STAmount (uQualityOut), STAmount (uQualityIn));
// If the next rate is at least as good as the current rate, process.
@@ -136,11 +136,11 @@ void rippleLiquidity (
auto uCurIssuerID = saCur.getIssuer ();
// current actual = current request * (quality out / quality in).
- auto numerator = STAmount::mulRound (
+ auto numerator = mulRound (
saCur, uQualityOut, {currency, uCurIssuerID}, true);
// True means "round up" to get best flow.
- STAmount saCurIn = STAmount::divRound (
+ STAmount saCurIn = divRound (
numerator, uQualityIn, {currency, uCurIssuerID}, true);
WriteLog (lsTRACE, RippleCalc)
@@ -169,11 +169,11 @@ void rippleLiquidity (
// going the other way
Issue issue{currency, uCurIssuerID};
- auto numerator = STAmount::mulRound (
+ auto numerator = mulRound (
saPrv, uQualityIn, issue, true);
// A part of current. All of previous. (Cur is the driver
// variable.)
- STAmount saCurOut = STAmount::divRound (
+ STAmount saCurOut = divRound (
numerator, uQualityOut, issue, true);
WriteLog (lsTRACE, RippleCalc)
diff --git a/src/ripple/module/app/transactors/CreateOffer.cpp b/src/ripple/module/app/transactors/CreateOffer.cpp
index aa22348de2..e735b34252 100644
--- a/src/ripple/module/app/transactors/CreateOffer.cpp
+++ b/src/ripple/module/app/transactors/CreateOffer.cpp
@@ -153,7 +153,7 @@ public:
STAmount saTakerPays = mTxn.getFieldAmount (sfTakerPays);
STAmount saTakerGets = mTxn.getFieldAmount (sfTakerGets);
- if (!saTakerPays.isLegalNet () || !saTakerGets.isLegalNet ())
+ if (!isLegalNet (saTakerPays) || !isLegalNet (saTakerGets))
return temBAD_AMOUNT;
auto const& uPaysIssuerID = saTakerPays.getIssuer ();
@@ -192,7 +192,7 @@ public:
// This is the original rate of this offer, and is the rate at which it will
// be placed, even if crossing offers change the amounts.
- std::uint64_t const uRate = STAmount::getRate (saTakerGets, saTakerPays);
+ std::uint64_t const uRate = getRate (saTakerGets, saTakerPays);
TER terResult (tesSUCCESS);
diff --git a/src/ripple/module/app/transactors/Payment.cpp b/src/ripple/module/app/transactors/Payment.cpp
index a40b5aeef6..56fdf70bf6 100644
--- a/src/ripple/module/app/transactors/Payment.cpp
+++ b/src/ripple/module/app/transactors/Payment.cpp
@@ -63,7 +63,7 @@ public:
else
maxSourceAmount = STAmount (
{saDstAmount.getCurrency (), mTxnAccountID},
- saDstAmount.getMantissa (), saDstAmount.getExponent (),
+ saDstAmount.mantissa(), saDstAmount.exponent (),
saDstAmount < zero);
auto const& uSrcCurrency = maxSourceAmount.getCurrency ();
auto const& uDstCurrency = saDstAmount.getCurrency ();
@@ -75,7 +75,7 @@ public:
"maxSourceAmount=" << maxSourceAmount.getFullText () <<
" saDstAmount=" << saDstAmount.getFullText ();
- if (!saDstAmount.isLegalNet () || !maxSourceAmount.isLegalNet ())
+ if (!isLegalNet (saDstAmount) || !isLegalNet (maxSourceAmount))
return temBAD_AMOUNT;
if (uTxFlags & tfPaymentMask)
diff --git a/src/ripple/module/app/transactors/SetTrust.cpp b/src/ripple/module/app/transactors/SetTrust.cpp
index 6f8a16596f..b5b03f63d9 100644
--- a/src/ripple/module/app/transactors/SetTrust.cpp
+++ b/src/ripple/module/app/transactors/SetTrust.cpp
@@ -70,7 +70,7 @@ public:
std::uint32_t uQualityIn (bQualityIn ? mTxn.getFieldU32 (sfQualityIn) : 0);
std::uint32_t uQualityOut (bQualityOut ? mTxn.getFieldU32 (sfQualityOut) : 0);
- if (!saLimitAmount.isLegalNet ())
+ if (!isLegalNet (saLimitAmount))
return temBAD_AMOUNT;
if (bQualityOut && QUALITY_ONE == uQualityOut)
diff --git a/src/ripple/module/app/transactors/Transactor.cpp b/src/ripple/module/app/transactors/Transactor.cpp
index 05d779d128..a126fced33 100644
--- a/src/ripple/module/app/transactors/Transactor.cpp
+++ b/src/ripple/module/app/transactors/Transactor.cpp
@@ -106,7 +106,7 @@ TER Transactor::payFee ()
{
STAmount saPaid = mTxn.getTransactionFee ();
- if (!saPaid.isLegalNet ())
+ if (!isLegalNet (saPaid))
return temBAD_AMOUNT;
// Only check fee is sufficient when the ledger is open.
diff --git a/src/ripple/module/data/protocol/STAmount.cpp b/src/ripple/module/data/protocol/STAmount.cpp
index 4a421c8a31..1738c67247 100644
--- a/src/ripple/module/data/protocol/STAmount.cpp
+++ b/src/ripple/module/data/protocol/STAmount.cpp
@@ -17,148 +17,366 @@
*/
//==============================================================================
-#include
-
#include
+#include
+#include
namespace ripple {
+static const std::uint64_t tenTo14 = 100000000000000ull;
+static const std::uint64_t tenTo14m1 = tenTo14 - 1;
+static const std::uint64_t tenTo17 = tenTo14 * 1000;
+
+//------------------------------------------------------------------------------
+
+STAmount::STAmount (SField::ref name, Issue const& issue,
+ mantissa_type mantissa, exponent_type exponent,
+ bool native, bool negative)
+ : SerializedType (name)
+ , mIssue (issue)
+ , mValue (mantissa)
+ , mOffset (exponent)
+ , mIsNative (native)
+ , mIsNegative (negative)
+{
+ canonicalize();
+}
+
+STAmount::STAmount (SField::ref name, Issue const& issue,
+ mantissa_type mantissa, exponent_type exponent,
+ bool native, bool negative, unchecked)
+ : SerializedType (name)
+ , mIssue (issue)
+ , mValue (mantissa)
+ , mOffset (exponent)
+ , mIsNative (native)
+ , mIsNegative (negative)
+{
+}
+
+STAmount::STAmount (SField::ref name, std::int64_t mantissa)
+ : SerializedType (name)
+ , mOffset (0)
+ , mIsNative (true)
+{
+ set (mantissa);
+}
+
+STAmount::STAmount (SField::ref name,
+ std::uint64_t mantissa, bool negative)
+ : SerializedType (name)
+ , mValue (mantissa)
+ , mOffset (0)
+ , mIsNative (true)
+ , mIsNegative (negative)
+{
+}
+
+STAmount::STAmount (SField::ref name, Issue const& issue,
+ std::uint64_t mantissa, int exponent, bool negative)
+ : SerializedType (name)
+ , mIssue (issue)
+ , mValue (mantissa)
+ , mOffset (exponent)
+ , mIsNegative (negative)
+{
+ canonicalize ();
+}
+
+//------------------------------------------------------------------------------
+
+STAmount::STAmount (std::uint64_t mantissa, bool negative)
+ : mValue (mantissa)
+ , mOffset (0)
+ , mIsNative (true)
+ , mIsNegative (mantissa != 0 && negative)
+{
+}
+
+STAmount::STAmount (Issue const& issue,
+ std::uint64_t mantissa, int exponent, bool negative)
+ : mIssue (issue)
+ , mValue (mantissa)
+ , mOffset (exponent)
+ , mIsNegative (negative)
+{
+ canonicalize ();
+}
+
+STAmount::STAmount (Issue const& issue,
+ std::int64_t mantissa, int exponent)
+ : mIssue (issue)
+ , mOffset (exponent)
+{
+ set (mantissa);
+ canonicalize ();
+}
+
+STAmount::STAmount (Issue const& issue,
+ std::uint32_t mantissa, int exponent, bool negative)
+ : STAmount (issue, static_cast(mantissa), exponent, negative)
+{
+}
+
+STAmount::STAmount (Issue const& issue,
+ int mantissa, int exponent)
+ : STAmount (issue, static_cast(mantissa), exponent)
+{
+}
+
+std::unique_ptr
+STAmount::construct (SerializerIterator& sit, SField::ref name)
+{
+ std::uint64_t value = sit.get64 ();
+
+ // native
+ if ((value & cNotNative) == 0)
+ {
+ // positive
+ if ((value & cPosNative) != 0)
+ return std::make_unique (name, value & ~cPosNative, false);
+
+ // negative
+ if (value == 0)
+ throw std::runtime_error ("negative zero is not canonical");
+
+ return std::make_unique (name, value, true);
+ }
+
+ Issue issue;
+ issue.currency.copyFrom (sit.get160 ());
+
+ if (isXRP (issue.currency))
+ throw std::runtime_error ("invalid native currency");
+
+ issue.account.copyFrom (sit.get160 ());
+
+ if (isXRP (issue.account))
+ throw std::runtime_error ("invalid native account");
+
+ // 10 bits for the offset, sign and "not native" flag
+ int offset = static_cast (value >> (64 - 10));
+
+ value &= ~ (1023ull << (64 - 10));
+
+ if (value)
+ {
+ bool isNegative = (offset & 256) == 0;
+ offset = (offset & 255) - 97; // center the range
+
+ if (value < cMinValue ||
+ value > cMaxValue ||
+ offset < cMinOffset ||
+ offset > cMaxOffset)
+ {
+ throw std::runtime_error ("invalid currency value");
+ }
+
+ return std::make_unique (name, issue, value, offset, isNegative);
+ }
+
+ if (offset != 512)
+ throw std::runtime_error ("invalid currency value");
+
+ return std::make_unique (name, issue);
+}
+
+STAmount
+STAmount::createFromInt64 (SField::ref name, std::int64_t value)
+{
+ return value >= 0
+ ? STAmount (name, static_cast (value), false)
+ : STAmount (name, static_cast (-value), true);
+}
+
+STAmount STAmount::deserialize (SerializerIterator& it)
+{
+ auto s = construct (it, sfGeneric);
+
+ if (!s)
+ throw std::runtime_error("Deserialization error");
+
+ return STAmount (*s);
+}
+
+//------------------------------------------------------------------------------
+//
+// Operators
+//
+//------------------------------------------------------------------------------
+
+bool STAmount::isComparable (STAmount const& t) const
+{
+ // are these two STAmount instances in the same currency
+ if (mIsNative) return t.mIsNative;
+
+ if (t.mIsNative) return false;
+
+ return mIssue.currency == t.mIssue.currency;
+}
+
+void STAmount::throwComparable (STAmount const& t) const
+{
+ // throw an exception if these two STAmount instances are incomparable
+ if (!isComparable (t))
+ throw std::runtime_error ("amounts are not comparable");
+}
+
+STAmount& STAmount::operator+= (STAmount const& a)
+{
+ *this = *this + a;
+ return *this;
+}
+
+STAmount& STAmount::operator-= (STAmount const& a)
+{
+ *this = *this - a;
+ return *this;
+}
+
+STAmount& STAmount::operator+= (std::uint64_t v)
+{
+ assert (mIsNative);
+ if (!mIsNative)
+ throw std::runtime_error ("not native");
+ // VFALCO TODO The cast looks dangerous, is it needed?
+ setSNValue (getSNValue () + static_cast (v));
+ return *this;
+}
+
+STAmount& STAmount::operator-= (std::uint64_t v)
+{
+ assert (mIsNative);
+
+ if (!mIsNative)
+ throw std::runtime_error ("not native");
+
+ // VFALCO TODO The cast looks dangerous, is it needed?
+ setSNValue (getSNValue () - static_cast (v));
+ return *this;
+}
+
+STAmount& STAmount::operator= (std::uint64_t v)
+{
+ // Does not copy name, does not change currency type.
+ mOffset = 0;
+ mValue = v;
+ mIsNegative = false;
+ if (!mIsNative)
+ canonicalize ();
+ return *this;
+}
+
+
+
+STAmount operator+ (STAmount const& v1, STAmount const& v2)
+{
+ v1.throwComparable (v2);
+
+ if (v2 == zero)
+ return v1;
+
+ if (v1 == zero)
+ {
+ // Result must be in terms of v1 currency and issuer.
+ return STAmount (v1.getFName (), v1.mIssue,
+ v2.mValue, v2.mOffset, v2.mIsNegative);
+ }
+
+ if (v1.mIsNative)
+ return STAmount (v1.getFName (), v1.getSNValue () + v2.getSNValue ());
+
+ int ov1 = v1.mOffset, ov2 = v2.mOffset;
+ std::int64_t vv1 = static_cast (v1.mValue);
+ std::int64_t vv2 = static_cast (v2.mValue);
+
+ if (v1.mIsNegative)
+ vv1 = -vv1;
+
+ if (v2.mIsNegative)
+ vv2 = -vv2;
+
+ while (ov1 < ov2)
+ {
+ vv1 /= 10;
+ ++ov1;
+ }
+
+ while (ov2 < ov1)
+ {
+ vv2 /= 10;
+ ++ov2;
+ }
+
+ // This addition cannot overflow an std::int64_t. It can overflow an
+ // STAmount and the constructor will throw.
+
+ std::int64_t fv = vv1 + vv2;
+
+ if ((fv >= -10) && (fv <= 10))
+ return STAmount (v1.getFName (), v1.mIssue);
+ if (fv >= 0)
+ return STAmount (v1.getFName (), v1.mIssue, fv, ov1, false);
+ return STAmount (v1.getFName (), v1.mIssue, -fv, ov1, true);
+}
+
+STAmount operator- (STAmount const& v1, STAmount const& v2)
+{
+ v1.throwComparable (v2);
+
+ if (v2 == zero)
+ return v1;
+
+ if (v2.mIsNative)
+ {
+ // XXX This could be better, check for overflow and that maximum range
+ // is covered.
+ return STAmount::createFromInt64 (
+ v1.getFName (), v1.getSNValue () - v2.getSNValue ());
+ }
+
+ int ov1 = v1.mOffset, ov2 = v2.mOffset;
+ auto vv1 = static_cast (v1.mValue);
+ auto vv2 = static_cast (v2.mValue);
+
+ if (v1.mIsNegative)
+ vv1 = -vv1;
+
+ if (v2.mIsNegative)
+ vv2 = -vv2;
+
+ while (ov1 < ov2)
+ {
+ vv1 /= 10;
+ ++ov1;
+ }
+
+ while (ov2 < ov1)
+ {
+ vv2 /= 10;
+ ++ov2;
+ }
+
+ // this subtraction cannot overflow an std::int64_t, it can overflow an STAmount and the constructor will throw
+
+ std::int64_t fv = vv1 - vv2;
+
+ if ((fv >= -10) && (fv <= 10))
+ return STAmount (v1.getFName (), v1.mIssue);
+ if (fv >= 0)
+ return STAmount (v1.getFName (), v1.mIssue, fv, ov1, false);
+ return STAmount (v1.getFName (), v1.mIssue, -fv, ov1, true);
+}
+
+//------------------------------------------------------------------------------
+
std::uint64_t STAmount::uRateOne =
- STAmount::getRate (STAmount (1), STAmount (1));
+ getRate (STAmount (1), STAmount (1));
-std::string STAmount::getHumanCurrency () const
+// Note: mIsNative and mIssue.currency must be set already!
+bool
+STAmount::setValue (std::string const& sAmount)
{
- return to_string (mIssue.currency);
-}
-
-bool STAmount::bSetJson (Json::Value const& jvSource)
-{
- try
- {
- STAmount saParsed (sfGeneric, jvSource);
-
- *this = saParsed;
-
- return true;
- }
- catch (const std::exception& e)
- {
- WriteLog (lsINFO, STAmount) << "bSetJson(): caught: " << e.what ();
- return false;
- }
-}
-
-STAmount::STAmount (SField::ref n, Json::Value const& v)
- : SerializedType (n), mValue (0), mOffset (0), mIsNegative (false)
-{
- Json::Value value, currency, issuer;
-
- if (v.isObject ())
- {
- WriteLog (lsTRACE, STAmount) <<
- "value='" << v["value"].asString () <<
- "', currency='" << v["currency"].asString () <<
- "', issuer='" << v["issuer"].asString () <<
- "')";
-
- value = v[jss::value];
- currency = v[jss::currency];
- issuer = v[jss::issuer];
- }
- else if (v.isArray ())
- {
- value = v.get (Json::UInt (0), 0);
- currency = v.get (Json::UInt (1), Json::nullValue);
- issuer = v.get (Json::UInt (2), Json::nullValue);
- }
- else if (v.isString ())
- {
- std::string val = v.asString ();
- std::vector elements;
- boost::split (elements, val, boost::is_any_of ("\t\n\r ,/"));
-
- if (elements.size () > 3)
- throw std::runtime_error ("invalid amount string");
-
- value = elements[0];
-
- if (elements.size () > 1)
- currency = elements[1];
-
- if (elements.size () > 2)
- issuer = elements[2];
- }
- else
- value = v;
-
- mIsNative = !currency.isString () || currency.asString ().empty () || (currency.asString () == systemCurrencyCode());
-
- if (mIsNative)
- {
- if (v.isObject ())
- throw std::runtime_error ("XRP may not be specified as an object");
- }
- else
- {
- // non-XRP
- if (!to_currency (mIssue.currency, currency.asString ()))
- throw std::runtime_error ("invalid currency");
-
- if (!issuer.isString ()
- || !to_issuer (mIssue.account, issuer.asString ()))
- throw std::runtime_error ("invalid issuer");
-
- if (isXRP (*this))
- throw std::runtime_error ("invalid issuer");
- }
-
- if (value.isInt ())
- {
- if (value.asInt () >= 0)
- mValue = value.asInt ();
- else
- {
- mValue = -value.asInt ();
- mIsNegative = true;
- }
-
- canonicalize ();
- }
- else if (value.isUInt ())
- {
- mValue = v.asUInt ();
-
- canonicalize ();
- }
- else if (value.isString ())
- {
- if (mIsNative)
- {
- std::int64_t val = beast::lexicalCastThrow (value.asString ());
-
- if (val >= 0)
- mValue = val;
- else
- {
- mValue = -val;
- mIsNegative = true;
- }
-
- canonicalize ();
- }
- else
- {
- setValue (value.asString ());
- }
- }
- else
- throw std::runtime_error ("invalid amount type");
-}
-
-bool STAmount::setValue (std::string const& sAmount)
-{
- // Note: mIsNative and mIssue.currency must be set already!
-
static boost::regex reNumber (
"\\`([+-]?)(\\d*)(\\.(\\d*))?([eE]([+-]?)(\\d+))?\\'");
boost::smatch smMatch;
@@ -288,214 +506,21 @@ bool STAmount::setFullValue (std::string const& sAmount, std::string const& sCur
return setValue (sAmount);
}
-// amount = value * [10 ^ offset]
-// Representation range is 10^80 - 10^(-80).
-// On the wire, high 8 bits are (offset+142), low 56 bits are value.
-//
-// Value is zero if amount is zero, otherwise value is 10^15 to (10^16 - 1)
-// inclusive.
-
-void STAmount::canonicalize ()
-{
- if (isXRP (*this))
- {
- // native currency amounts should always have an offset of zero
- mIsNative = true;
-
- if (mValue == 0)
- {
- mOffset = 0;
- mIsNegative = false;
- return;
- }
-
- while (mOffset < 0)
- {
- mValue /= 10;
- ++mOffset;
- }
-
- while (mOffset > 0)
- {
- mValue *= 10;
- --mOffset;
- }
-
- if (mValue > cMaxNative)
- throw std::runtime_error ("Native currency amount out of range");
-
- return;
- }
-
- mIsNative = false;
-
- if (mValue == 0)
- {
- mOffset = -100;
- mIsNegative = false;
- return;
- }
-
- while ((mValue < cMinValue) && (mOffset > cMinOffset))
- {
- mValue *= 10;
- --mOffset;
- }
-
- while (mValue > cMaxValue)
- {
- if (mOffset >= cMaxOffset)
- throw std::runtime_error ("value overflow");
-
- mValue /= 10;
- ++mOffset;
- }
-
- if ((mOffset < cMinOffset) || (mValue < cMinValue))
- {
- mValue = 0;
- mOffset = 0;
- mIsNegative = false;
- }
-
- if (mOffset > cMaxOffset)
- throw std::runtime_error ("value overflow");
-
- assert ((mValue == 0) || ((mValue >= cMinValue) && (mValue <= cMaxValue)));
- assert ((mValue == 0) || ((mOffset >= cMinOffset) && (mOffset <= cMaxOffset)));
- assert ((mValue != 0) || (mOffset != -100));
-}
-
-void STAmount::add (Serializer& s) const
-{
- if (mIsNative)
- {
- assert (mOffset == 0);
-
- if (!mIsNegative)
- s.add64 (mValue | cPosNative);
- else
- s.add64 (mValue);
- }
- else
- {
- if (*this == zero)
- s.add64 (cNotNative);
- else if (mIsNegative) // 512 = not native
- s.add64 (mValue | (static_cast (mOffset + 512 + 97) << (64 - 10)));
- else // 256 = positive
- s.add64 (mValue | (static_cast (mOffset + 512 + 256 + 97) << (64 - 10)));
-
- s.add160 (mIssue.currency);
- s.add160 (mIssue.account);
- }
-}
-
-STAmount STAmount::createFromInt64 (SField::ref name, std::int64_t value)
-{
- return value >= 0
- ? STAmount (name, static_cast (value), false)
- : STAmount (name, static_cast (-value), true);
-}
-
-void STAmount::setValue (STAmount const& a)
-{
- mIssue = a.mIssue;
- mValue = a.mValue;
- mOffset = a.mOffset;
- mIsNative = a.mIsNative;
- mIsNegative = a.mIsNegative;
-}
-
void STAmount::setIssue (Issue const& issue) {
mIssue = std::move(issue);
mIsNative = isXRP (*this);
}
-int STAmount::compare (STAmount const& a) const
+std::uint64_t
+STAmount::getNValue () const
{
- // Compares the value of a to the value of this STAmount, amounts must be comparable
- if (mIsNegative != a.mIsNegative)
- return mIsNegative ? -1 : 1;
-
- if (!mValue)
- {
- if (a.mIsNegative) return 1;
-
- return a.mValue ? -1 : 0;
- }
-
- if (!a.mValue) return 1;
-
- if (mOffset > a.mOffset) return mIsNegative ? -1 : 1;
-
- if (mOffset < a.mOffset) return mIsNegative ? 1 : -1;
-
- if (mValue > a.mValue) return mIsNegative ? -1 : 1;
-
- if (mValue < a.mValue) return mIsNegative ? 1 : -1;
-
- return 0;
+ if (!mIsNative)
+ throw std::runtime_error ("not native");
+ return mValue;
}
-std::unique_ptr
-STAmount::construct (SerializerIterator& sit, SField::ref name)
-{
- std::uint64_t value = sit.get64 ();
-
- // native
- if ((value & cNotNative) == 0)
- {
- // positive
- if ((value & cPosNative) != 0)
- return std::make_unique (name, value & ~cPosNative, false);
-
- // negative
- if (value == 0)
- throw std::runtime_error ("negative zero is not canonical");
-
- return std::make_unique (name, value, true);
- }
-
- Issue issue;
- issue.currency.copyFrom (sit.get160 ());
-
- if (isXRP (issue.currency))
- throw std::runtime_error ("invalid native currency");
-
- issue.account.copyFrom (sit.get160 ());
-
- if (isXRP (issue.account))
- throw std::runtime_error ("invalid native account");
-
- // 10 bits for the offset, sign and "not native" flag
- int offset = static_cast (value >> (64 - 10));
-
- value &= ~ (1023ull << (64 - 10));
-
- if (value)
- {
- bool isNegative = (offset & 256) == 0;
- offset = (offset & 255) - 97; // center the range
-
- if (value < cMinValue ||
- value > cMaxValue ||
- offset < cMinOffset ||
- offset > cMaxOffset)
- {
- throw std::runtime_error ("invalid currency value");
- }
-
- return std::make_unique (name, issue, value, offset, isNegative);
- }
-
- if (offset != 512)
- throw std::runtime_error ("invalid currency value");
-
- return std::make_unique (name, issue);
-}
-
-std::int64_t STAmount::getSNValue () const
+std::int64_t
+STAmount::getSNValue () const
{
// signed native value
if (!mIsNative)
@@ -507,7 +532,21 @@ std::int64_t STAmount::getSNValue () const
return static_cast (mValue);
}
-void STAmount::setSNValue (std::int64_t v)
+std::string STAmount::getHumanCurrency () const
+{
+ return to_string (mIssue.currency);
+}
+
+void
+STAmount::setNValue (std::uint64_t v)
+{
+ if (!mIsNative)
+ throw std::runtime_error ("not native");
+ mValue = v;
+}
+
+void
+STAmount::setSNValue (std::int64_t v)
{
if (!mIsNative) throw std::runtime_error ("not native");
@@ -523,7 +562,110 @@ void STAmount::setSNValue (std::int64_t v)
}
}
-std::string STAmount::getText () const
+// Convert an offer into an index amount so they sort by rate.
+// A taker will take the best, lowest, rate first.
+// (e.g. a taker will prefer pay 1 get 3 over pay 1 get 2.
+// --> offerOut: takerGets: How much the offerer is selling to the taker.
+// --> offerIn: takerPays: How much the offerer is receiving from the taker.
+// <-- uRate: normalize(offerIn/offerOut)
+// A lower rate is better for the person taking the order.
+// The taker gets more for less with a lower rate.
+// Zero is returned if the offer is worthless.
+std::uint64_t
+getRate (STAmount const& offerOut, STAmount const& offerIn)
+{
+ if (offerOut == zero)
+ return 0;
+ try
+ {
+ STAmount r = divide (offerIn, offerOut, noIssue());
+ if (r == zero) // offer is too good
+ return 0;
+ assert ((r.exponent() >= -100) && (r.exponent() <= 155));
+ std::uint64_t ret = r.exponent() + 100;
+ return (ret << (64 - 8)) | r.mantissa();
+ }
+ catch (...)
+ {
+ }
+
+ // overflow -- very bad offer
+ return 0;
+}
+
+void STAmount::setJson (Json::Value& elem) const
+{
+ elem = Json::objectValue;
+
+ if (!mIsNative)
+ {
+ // It is an error for currency or issuer not to be specified for valid
+ // json.
+ elem[jss::value] = getText ();
+ elem[jss::currency] = getHumanCurrency ();
+ elem[jss::issuer] = to_string (mIssue.account);
+ }
+ else
+ {
+ elem = getText ();
+ }
+}
+
+// VFALCO What does this perplexing function do?
+void STAmount::roundSelf ()
+{
+ if (mIsNative)
+ return;
+
+ std::uint64_t valueDigits = mValue % 1000000000ull;
+
+ if (valueDigits == 1)
+ {
+ mValue -= 1;
+
+ if (mValue < cMinValue)
+ canonicalize ();
+ }
+ else if (valueDigits == 999999999ull)
+ {
+ mValue += 1;
+
+ if (mValue > cMaxValue)
+ canonicalize ();
+ }
+}
+
+//------------------------------------------------------------------------------
+//
+// SerializedType
+//
+//------------------------------------------------------------------------------
+
+std::string
+STAmount::getFullText () const
+{
+ std::string ret;
+
+ ret.reserve(64);
+ ret = getText () + "/" + getHumanCurrency ();
+
+ if (!mIsNative)
+ {
+ ret += "/";
+
+ if (isXRP (*this))
+ ret += "0";
+ else if (mIssue.account == noAccount())
+ ret += "1";
+ else
+ ret += to_string (mIssue.account);
+ }
+
+ return ret;
+}
+
+std::string
+STAmount::getText () const
{
// keep full internal accuracy, but make more human friendly if posible
if (*this == zero)
@@ -612,272 +754,435 @@ std::string STAmount::getText () const
return ret;
}
-bool STAmount::isComparable (STAmount const& t) const
+Json::Value
+STAmount::getJson (int) const
{
- // are these two STAmount instances in the same currency
- if (mIsNative) return t.mIsNative;
-
- if (t.mIsNative) return false;
-
- return mIssue.currency == t.mIssue.currency;
+ Json::Value elem;
+ setJson (elem);
+ return elem;
}
-bool STAmount::isEquivalent (const SerializedType& t) const
+void
+STAmount::add (Serializer& s) const
+{
+ if (mIsNative)
+ {
+ assert (mOffset == 0);
+
+ if (!mIsNegative)
+ s.add64 (mValue | cPosNative);
+ else
+ s.add64 (mValue);
+ }
+ else
+ {
+ if (*this == zero)
+ s.add64 (cNotNative);
+ else if (mIsNegative) // 512 = not native
+ s.add64 (mValue | (static_cast (mOffset + 512 + 97) << (64 - 10)));
+ else // 256 = positive
+ s.add64 (mValue | (static_cast (mOffset + 512 + 256 + 97) << (64 - 10)));
+
+ s.add160 (mIssue.currency);
+ s.add160 (mIssue.account);
+ }
+}
+
+bool
+STAmount::isEquivalent (const SerializedType& t) const
{
const STAmount* v = dynamic_cast (&t);
return v && (*v == *this);
}
-void STAmount::throwComparable (STAmount const& t) const
+STAmount*
+STAmount::duplicate () const
{
- // throw an exception if these two STAmount instances are incomparable
- if (!isComparable (t))
- throw std::runtime_error ("amounts are not comparable");
+ return new STAmount (*this);
}
-bool STAmount::operator== (STAmount const& a) const
+//------------------------------------------------------------------------------
+
+// amount = value * [10 ^ offset]
+// Representation range is 10^80 - 10^(-80).
+// On the wire, high 8 bits are (offset+142), low 56 bits are value.
+//
+// Value is zero if amount is zero, otherwise value is 10^15 to (10^16 - 1)
+// inclusive.
+void STAmount::canonicalize ()
{
- return isComparable (a) &&
- mIsNegative == a.mIsNegative &&
- mOffset == a.mOffset &&
- mValue == a.mValue;
-}
-
-bool STAmount::operator!= (STAmount const& a) const
-{
- return mOffset != a.mOffset ||
- mValue != a.mValue ||
- mIsNegative != a.mIsNegative ||
- !isComparable (a);
-}
-
-bool STAmount::operator< (STAmount const& a) const
-{
- throwComparable (a);
- return compare (a) < 0;
-}
-
-bool STAmount::operator> (STAmount const& a) const
-{
- throwComparable (a);
- return compare (a) > 0;
-}
-
-bool STAmount::operator<= (STAmount const& a) const
-{
- throwComparable (a);
- return compare (a) <= 0;
-}
-
-bool STAmount::operator>= (STAmount const& a) const
-{
- throwComparable (a);
- return compare (a) >= 0;
-}
-
-STAmount& STAmount::operator+= (STAmount const& a)
-{
- *this = *this + a;
- return *this;
-}
-
-STAmount& STAmount::operator-= (STAmount const& a)
-{
- *this = *this - a;
- return *this;
-}
-
-STAmount STAmount::operator- (void) const
-{
- if (mValue == 0) return *this;
-
- return STAmount (
- getFName (), mIssue, mValue, mOffset, mIsNative, !mIsNegative);
-}
-
-STAmount& STAmount::operator= (std::uint64_t v)
-{
- // Does not copy name, does not change currency type.
- mOffset = 0;
- mValue = v;
- mIsNegative = false;
-
- if (!mIsNative)
- canonicalize ();
-
- return *this;
-}
-
-STAmount& STAmount::operator+= (std::uint64_t v)
-{
- assert (mIsNative);
-
- if (!mIsNative)
- throw std::runtime_error ("not native");
-
- setSNValue (getSNValue () + static_cast (v));
- return *this;
-}
-
-STAmount& STAmount::operator-= (std::uint64_t v)
-{
- assert (mIsNative);
-
- if (!mIsNative)
- throw std::runtime_error ("not native");
-
- setSNValue (getSNValue () - static_cast (v));
- return *this;
-}
-
-bool STAmount::operator< (std::uint64_t v) const
-{
- return getSNValue () < static_cast (v);
-}
-
-bool STAmount::operator> (std::uint64_t v) const
-{
- return getSNValue () > static_cast (v);
-}
-
-bool STAmount::operator<= (std::uint64_t v) const
-{
- return getSNValue () <= static_cast (v);
-}
-
-bool STAmount::operator>= (std::uint64_t v) const
-{
- return getSNValue () >= static_cast (v);
-}
-
-STAmount STAmount::operator+ (std::uint64_t v) const
-{
- return STAmount (
- getFName (), getSNValue () + static_cast (v));
-}
-
-STAmount STAmount::operator- (std::uint64_t v) const
-{
- return STAmount (
- getFName (), getSNValue () - static_cast (v));
-}
-
-STAmount::operator double () const
-{
- // Does not keep the precise value. Not recommended
- if (!mValue)
- return 0.0;
-
- if (mIsNegative)
- return -1.0 * static_cast (mValue) * pow (10.0, mOffset);
-
- return static_cast (mValue) * pow (10.0, mOffset);
-}
-
-STAmount operator+ (STAmount const& v1, STAmount const& v2)
-{
- v1.throwComparable (v2);
-
- if (v2 == zero)
- return v1;
-
- if (v1 == zero)
+ if (isXRP (*this))
{
- // Result must be in terms of v1 currency and issuer.
- return STAmount (v1.getFName (), v1.mIssue,
- v2.mValue, v2.mOffset, v2.mIsNegative);
+ // native currency amounts should always have an offset of zero
+ mIsNative = true;
+
+ if (mValue == 0)
+ {
+ mOffset = 0;
+ mIsNegative = false;
+ return;
+ }
+
+ while (mOffset < 0)
+ {
+ mValue /= 10;
+ ++mOffset;
+ }
+
+ while (mOffset > 0)
+ {
+ mValue *= 10;
+ --mOffset;
+ }
+
+ if (mValue > cMaxNative)
+ throw std::runtime_error ("Native currency amount out of range");
+
+ return;
}
- if (v1.mIsNative)
- return STAmount (v1.getFName (), v1.getSNValue () + v2.getSNValue ());
+ mIsNative = false;
- int ov1 = v1.mOffset, ov2 = v2.mOffset;
- std::int64_t vv1 = static_cast (v1.mValue);
- std::int64_t vv2 = static_cast (v2.mValue);
-
- if (v1.mIsNegative)
- vv1 = -vv1;
-
- if (v2.mIsNegative)
- vv2 = -vv2;
-
- while (ov1 < ov2)
+ if (mValue == 0)
{
- vv1 /= 10;
- ++ov1;
+ mOffset = -100;
+ mIsNegative = false;
+ return;
}
- while (ov2 < ov1)
+ while ((mValue < cMinValue) && (mOffset > cMinOffset))
{
- vv2 /= 10;
- ++ov2;
+ mValue *= 10;
+ --mOffset;
}
- // This addition cannot overflow an std::int64_t. It can overflow an
- // STAmount and the constructor will throw.
+ while (mValue > cMaxValue)
+ {
+ if (mOffset >= cMaxOffset)
+ throw std::runtime_error ("value overflow");
- std::int64_t fv = vv1 + vv2;
+ mValue /= 10;
+ ++mOffset;
+ }
- if ((fv >= -10) && (fv <= 10))
- return STAmount (v1.getFName (), v1.mIssue);
- if (fv >= 0)
- return STAmount (v1.getFName (), v1.mIssue, fv, ov1, false);
+ if ((mOffset < cMinOffset) || (mValue < cMinValue))
+ {
+ mValue = 0;
+ mOffset = 0;
+ mIsNegative = false;
+ }
+
+ if (mOffset > cMaxOffset)
+ throw std::runtime_error ("value overflow");
+
+ assert ((mValue == 0) || ((mValue >= cMinValue) && (mValue <= cMaxValue)));
+ assert ((mValue == 0) || ((mOffset >= cMinOffset) && (mOffset <= cMaxOffset)));
+ assert ((mValue != 0) || (mOffset != -100));
+}
+
+void STAmount::set (std::int64_t v)
+{
+ if (v < 0)
+ {
+ mIsNegative = true;
+ mValue = static_cast (-v);
+ }
else
- return STAmount (v1.getFName (), v1.mIssue, -fv, ov1, true);
+ {
+ mIsNegative = false;
+ mValue = static_cast (v);
+ }
}
-STAmount operator- (STAmount const& v1, STAmount const& v2)
+//------------------------------------------------------------------------------
+
+STAmount
+amountFromQuality (std::uint64_t rate)
{
- v1.throwComparable (v2);
+ if (rate == 0)
+ return STAmount (noIssue());
- if (v2 == zero)
- return v1;
+ std::uint64_t mantissa = rate & ~ (255ull << (64 - 8));
+ int exponent = static_cast (rate >> (64 - 8)) - 100;
- if (v2.mIsNative)
- {
- // XXX This could be better, check for overflow and that maximum range
- // is covered.
- return STAmount::createFromInt64 (
- v1.getFName (), v1.getSNValue () - v2.getSNValue ());
- }
-
- int ov1 = v1.mOffset, ov2 = v2.mOffset;
- auto vv1 = static_cast (v1.mValue);
- auto vv2 = static_cast (v2.mValue);
-
- if (v1.mIsNegative)
- vv1 = -vv1;
-
- if (v2.mIsNegative)
- vv2 = -vv2;
-
- while (ov1 < ov2)
- {
- vv1 /= 10;
- ++ov1;
- }
-
- while (ov2 < ov1)
- {
- vv2 /= 10;
- ++ov2;
- }
-
- // this subtraction cannot overflow an std::int64_t, it can overflow an STAmount and the constructor will throw
-
- std::int64_t fv = vv1 - vv2;
-
- if ((fv >= -10) && (fv <= 10))
- return STAmount (v1.getFName (), v1.mIssue);
- if (fv >= 0)
- return STAmount (v1.getFName (), v1.mIssue, fv, ov1, false);
- else
- return STAmount (v1.getFName (), v1.mIssue, -fv, ov1, true);
+ return STAmount (noIssue(), mantissa, exponent);
}
+STAmount
+amountFromJson (SField::ref name, Json::Value const& v)
+{
+ STAmount::mantissa_type mantissa = 0;
+ STAmount::exponent_type exponent = 0;
+ bool negative = false;
+ Issue issue;
+
+ Json::Value value;
+ Json::Value currency;
+ Json::Value issuer;
+
+ if (v.isObject ())
+ {
+ WriteLog (lsTRACE, STAmount) <<
+ "value='" << v["value"].asString () <<
+ "', currency='" << v["currency"].asString () <<
+ "', issuer='" << v["issuer"].asString () <<
+ "')";
+
+ value = v[jss::value];
+ currency = v[jss::currency];
+ issuer = v[jss::issuer];
+ }
+ else if (v.isArray ())
+ {
+ value = v.get (Json::UInt (0), 0);
+ currency = v.get (Json::UInt (1), Json::nullValue);
+ issuer = v.get (Json::UInt (2), Json::nullValue);
+ }
+ else if (v.isString ())
+ {
+ std::string val = v.asString ();
+ std::vector elements;
+ boost::split (elements, val, boost::is_any_of ("\t\n\r ,/"));
+
+ if (elements.size () > 3)
+ throw std::runtime_error ("invalid amount string");
+
+ value = elements[0];
+
+ if (elements.size () > 1)
+ currency = elements[1];
+
+ if (elements.size () > 2)
+ issuer = elements[2];
+ }
+ else
+ {
+ value = v;
+ }
+
+ bool const native = ! currency.isString () ||
+ currency.asString ().empty () ||
+ (currency.asString () == systemCurrencyCode());
+
+ if (native)
+ {
+ if (v.isObject ())
+ throw std::runtime_error ("XRP may not be specified as an object");
+ }
+ else
+ {
+ // non-XRP
+ if (! to_currency (issue.currency, currency.asString ()))
+ throw std::runtime_error ("invalid currency");
+
+ if (! issuer.isString ()
+ || !to_issuer (issue.account, issuer.asString ()))
+ throw std::runtime_error ("invalid issuer");
+
+ if (isXRP (issue.currency))
+ throw std::runtime_error ("invalid issuer");
+ }
+
+ if (value.isInt ())
+ {
+ if (value.asInt () >= 0)
+ {
+ mantissa = value.asInt ();
+ }
+ else
+ {
+ mantissa = -value.asInt ();
+ negative = true;
+ }
+ }
+ else if (value.isUInt ())
+ {
+ mantissa = v.asUInt ();
+ }
+ else if (value.isString ())
+ {
+ if (native)
+ {
+ std::int64_t val = beast::lexicalCastThrow (
+ value.asString ());
+
+ if (val >= 0)
+ {
+ mantissa = val;
+ }
+ else
+ {
+ mantissa = -val;
+ negative = true;
+ }
+ }
+ else
+ {
+ STAmount amount (name, issue, mantissa, exponent,
+ native, negative, STAmount::unchecked{});
+ amount.setValue (value.asString ());
+ return amount;
+ }
+ }
+ else
+ {
+ throw std::runtime_error ("invalid amount type");
+ }
+
+ return STAmount (name, issue, mantissa, exponent, native, negative);
+}
+
+bool
+amountFromJsonNoThrow (STAmount& result, Json::Value const& jvSource)
+{
+ try
+ {
+ result = amountFromJson (sfGeneric, jvSource);
+ return true;
+ }
+ catch (const std::exception& e)
+ {
+ WriteLog (lsINFO, STAmount) <<
+ "amountFromJsonNoThrow: caught: " << e.what ();
+ }
+ return false;
+}
+
+//------------------------------------------------------------------------------
+//
+// Operators
+//
+//------------------------------------------------------------------------------
+
+static
+int
+compare (STAmount const& lhs, STAmount const& rhs)
+{
+ // Compares the value of a to the value of this STAmount, amounts must be comparable
+ if (lhs.negative() != rhs.negative())
+ return lhs.negative() ? -1 : 1;
+ if (lhs.mantissa() == 0)
+ {
+ if (rhs.negative())
+ return 1;
+ return (rhs.mantissa() != 0) ? -1 : 0;
+ }
+ if (rhs.mantissa() == 0) return 1;
+ if (lhs.exponent() > rhs.exponent()) return lhs.negative() ? -1 : 1;
+ if (lhs.exponent() < rhs.exponent()) return lhs.negative() ? 1 : -1;
+ if (lhs.mantissa() > rhs.mantissa()) return lhs.negative() ? -1 : 1;
+ if (lhs.mantissa() < rhs.mantissa()) return lhs.negative() ? 1 : -1;
+ return 0;
+}
+
+bool
+operator== (STAmount const& lhs, STAmount const& rhs)
+{
+ return lhs.isComparable (rhs) && lhs.negative() == rhs.negative() &&
+ lhs.exponent() == rhs.exponent() && lhs.mantissa() == rhs.mantissa();
+}
+
+bool
+operator!= (STAmount const& lhs, STAmount const& rhs)
+{
+ return lhs.exponent() != rhs.exponent() ||
+ lhs.mantissa() != rhs.mantissa() ||
+ lhs.negative() != rhs.negative() || ! lhs.isComparable (rhs);
+}
+
+bool
+operator< (STAmount const& lhs, STAmount const& rhs)
+{
+ lhs.throwComparable (rhs);
+ return compare (lhs, rhs) < 0;
+}
+
+bool
+operator> (STAmount const& lhs, STAmount const& rhs)
+{
+ lhs.throwComparable (rhs);
+ return compare (lhs, rhs) > 0;
+}
+
+bool
+operator<= (STAmount const& lhs, STAmount const& rhs)
+{
+ lhs.throwComparable (rhs);
+ return compare (lhs, rhs) <= 0;
+}
+
+bool
+operator>= (STAmount const& lhs, STAmount const& rhs)
+{
+ lhs.throwComparable (rhs);
+ return compare (lhs, rhs) >= 0;
+}
+
+// native currency only
+
+bool
+operator< (STAmount const& lhs, std::uint64_t rhs)
+{
+ // VFALCO Why the cast?
+ return lhs.getSNValue() < static_cast (rhs);
+}
+
+bool
+operator> (STAmount const& lhs, std::uint64_t rhs)
+{
+ // VFALCO Why the cast?
+ return lhs.getSNValue() > static_cast (rhs);
+}
+
+bool
+operator<= (STAmount const& lhs, std::uint64_t rhs)
+{
+ // VFALCO TODO The cast looks dangerous, is it needed?
+ return lhs.getSNValue () <= static_cast (rhs);
+}
+
+bool
+operator>= (STAmount const& lhs, std::uint64_t rhs)
+{
+ // VFALCO TODO The cast looks dangerous, is it needed?
+ return lhs.getSNValue() >= static_cast (rhs);
+}
+
+STAmount
+operator+ (STAmount const& lhs, std::uint64_t rhs)
+{
+ // VFALCO TODO The cast looks dangerous, is it needed?
+ return STAmount (lhs.getFName (),
+ lhs.getSNValue () + static_cast (rhs));
+}
+
+STAmount
+operator- (STAmount const& lhs, std::uint64_t rhs)
+{
+ // VFALCO TODO The cast looks dangerous, is it needed?
+ return STAmount (lhs.getFName (),
+ lhs.getSNValue () - static_cast (rhs));
+}
+
+STAmount
+operator- (STAmount const& value)
+{
+ if (value.mantissa() == 0)
+ return value;
+ return STAmount (value.getFName (),
+ value.issue(), value.mantissa(), value.exponent(),
+ value.native(), ! value.negative(), STAmount::unchecked{});
+}
+
+//------------------------------------------------------------------------------
+//
+// Arithmetic
+//
+//------------------------------------------------------------------------------
+
// NIKB TODO Make Amount::divide skip math if den == QUALITY_ONE
-STAmount STAmount::divide (
- STAmount const& num, STAmount const& den, Issue const& issue)
+STAmount
+divide (STAmount const& num, STAmount const& den, Issue const& issue)
{
if (den == zero)
throw std::runtime_error ("division by zero");
@@ -885,23 +1190,29 @@ STAmount STAmount::divide (
if (num == zero)
return {issue};
- std::uint64_t numVal = num.mValue, denVal = den.mValue;
- int numOffset = num.mOffset, denOffset = den.mOffset;
+ std::uint64_t numVal = num.mantissa();
+ std::uint64_t denVal = den.mantissa();
+ int numOffset = num.exponent();
+ int denOffset = den.exponent();
- if (num.mIsNative)
+ if (num.native())
+ {
while (numVal < STAmount::cMinValue)
{
// Need to bring into range
numVal *= 10;
--numOffset;
}
+ }
- if (den.mIsNative)
+ if (den.native())
+ {
while (denVal < STAmount::cMinValue)
{
denVal *= 10;
--denOffset;
}
+ }
// Compute (numerator * 10^17) / denominator
CBigNum v;
@@ -919,20 +1230,20 @@ STAmount STAmount::divide (
// TODO(tom): where do 5 and 17 come from?
return STAmount (issue, v.getuint64 () + 5,
numOffset - denOffset - 17,
- num.mIsNegative != den.mIsNegative);
+ num.negative() != den.negative());
}
-STAmount STAmount::multiply (
- STAmount const& v1, STAmount const& v2, Issue const& issue)
+STAmount
+multiply (STAmount const& v1, STAmount const& v2, Issue const& issue)
{
if (v1 == zero || v2 == zero)
return STAmount (issue);
- if (v1.mIsNative && v2.mIsNative && isXRP (issue) )
+ if (v1.native() && v2.native() && isXRP (issue))
{
- std::uint64_t minV = v1.getSNValue () < v2.getSNValue ()
+ std::uint64_t const minV = v1.getSNValue () < v2.getSNValue ()
? v1.getSNValue () : v2.getSNValue ();
- std::uint64_t maxV = v1.getSNValue () < v2.getSNValue ()
+ std::uint64_t const maxV = v1.getSNValue () < v2.getSNValue ()
? v2.getSNValue () : v1.getSNValue ();
if (minV > 3000000000ull) // sqrt(cMaxNative)
@@ -944,10 +1255,12 @@ STAmount STAmount::multiply (
return STAmount (v1.getFName (), minV * maxV);
}
- std::uint64_t value1 = v1.mValue, value2 = v2.mValue;
- int offset1 = v1.mOffset, offset2 = v2.mOffset;
+ std::uint64_t value1 = v1.mantissa();
+ std::uint64_t value2 = v2.mantissa();
+ int offset1 = v1.exponent();
+ int offset2 = v2.exponent();
- if (v1.mIsNative)
+ if (v1.native())
{
while (value1 < STAmount::cMinValue)
{
@@ -956,7 +1269,7 @@ STAmount STAmount::multiply (
}
}
- if (v2.mIsNative)
+ if (v2.native())
{
while (value2 < STAmount::cMinValue)
{
@@ -981,810 +1294,318 @@ STAmount STAmount::multiply (
// TODO(tom): where do 7 and 14 come from?
return STAmount (issue, v.getuint64 () + 7,
- offset1 + offset2 + 14, v1.mIsNegative != v2.mIsNegative);
+ offset1 + offset2 + 14, v1.negative() != v2.negative());
}
-// Convert an offer into an index amount so they sort by rate.
-// A taker will take the best, lowest, rate first.
-// (e.g. a taker will prefer pay 1 get 3 over pay 1 get 2.
-// --> offerOut: takerGets: How much the offerer is selling to the taker.
-// --> offerIn: takerPays: How much the offerer is receiving from the taker.
-// <-- uRate: normalize(offerIn/offerOut)
-// A lower rate is better for the person taking the order.
-// The taker gets more for less with a lower rate.
-// Zero is returned if the offer is worthless.
-std::uint64_t STAmount::getRate (STAmount const& offerOut, STAmount const& offerIn)
+void
+canonicalizeRound (bool isNative, std::uint64_t& value,
+ int& offset, bool roundUp)
{
- if (offerOut == zero)
- return 0;
-
- try
- {
- STAmount r = divide (offerIn, offerOut, noIssue());
-
- if (r == zero) // offer is too good
- return 0;
-
- assert ((r.getExponent () >= -100) && (r.getExponent () <= 155));
-
- std::uint64_t ret = r.getExponent () + 100;
-
- return (ret << (64 - 8)) | r.getMantissa ();
- }
- catch (...)
- {
- // overflow -- very bad offer
- return 0;
- }
-}
-
-STAmount STAmount::setRate (std::uint64_t rate)
-{
- if (rate == 0)
- return STAmount (noIssue());
-
- std::uint64_t mantissa = rate & ~ (255ull << (64 - 8));
- int exponent = static_cast (rate >> (64 - 8)) - 100;
-
- return STAmount (noIssue(), mantissa, exponent);
-}
-
-STAmount STAmount::getPay (
- STAmount const& offerOut, STAmount const& offerIn, STAmount const& needed)
-{
- // Someone wants to get (needed) out of the offer, how much should they pay
- // in?
- if (offerOut == zero)
- return STAmount (offerIn.issue ());
-
- if (needed >= offerOut)
- // They need more than offered, pay full amount.
- return needed;
-
-
- STAmount ret = divide (multiply (needed, offerIn, noIssue()),
- offerOut, offerIn.issue());
-
- return (ret > offerIn) ? offerIn : ret;
-}
-
-STAmount STAmount::deserialize (SerializerIterator& it)
-{
- auto s = construct (it, sfGeneric);
-
- if (!s)
- throw std::runtime_error("Deserialization error");
-
- return STAmount (*s);
-}
-
-std::string STAmount::getFullText () const
-{
- std::string ret;
-
- ret.reserve(64);
- ret = getText () + "/" + getHumanCurrency ();
-
- if (!mIsNative)
- {
- ret += "/";
-
- if (isXRP (*this))
- ret += "0";
- else if (mIssue.account == noAccount())
- ret += "1";
- else
- ret += to_string (mIssue.account);
- }
-
- return ret;
-}
-
-STAmount STAmount::getRound () const
-{
- if (mIsNative)
- return *this;
-
- std::uint64_t valueDigits = mValue % 1000000000ull;
-
- if (valueDigits == 1)
- return STAmount (mIssue, mValue - 1, mOffset, mIsNegative);
- else if (valueDigits == 999999999ull)
- return STAmount (mIssue, mValue + 1, mOffset, mIsNegative);
-
- return *this;
-}
-
-void STAmount::roundSelf ()
-{
- if (mIsNative)
+ if (!roundUp) // canonicalize already rounds down
return;
- std::uint64_t valueDigits = mValue % 1000000000ull;
+ WriteLog (lsTRACE, STAmount)
+ << "canonicalizeRound< " << value << ":" << offset;
- if (valueDigits == 1)
+ if (isNative)
{
- mValue -= 1;
+ if (offset < 0)
+ {
+ int loops = 0;
- if (mValue < cMinValue)
- canonicalize ();
+ while (offset < -1)
+ {
+ value /= 10;
+ ++offset;
+ ++loops;
+ }
+
+ value += (loops >= 2) ? 9 : 10; // add before last divide
+ value /= 10;
+ ++offset;
+ }
}
- else if (valueDigits == 999999999ull)
+ else if (value > STAmount::cMaxValue)
{
- mValue += 1;
+ while (value > (10 * STAmount::cMaxValue))
+ {
+ value /= 10;
+ ++offset;
+ }
- if (mValue > cMaxValue)
- canonicalize ();
+ value += 9; // add before last divide
+ value /= 10;
+ ++offset;
}
+
+ WriteLog (lsTRACE, STAmount)
+ << "canonicalizeRound> " << value << ":" << offset;
}
-void STAmount::setJson (Json::Value& elem) const
+STAmount
+addRound (STAmount const& v1, STAmount const& v2, bool roundUp)
{
- elem = Json::objectValue;
+ v1.throwComparable (v2);
- if (!mIsNative)
+ if (v2.mantissa() == 0)
+ return v1;
+
+ if (v1.mantissa() == 0)
+ return STAmount (v1.getFName (), v1.issue(), v2.mantissa(),
+ v2.exponent(), v2.negative());
+
+ if (v1.native())
+ return STAmount (v1.getFName (), v1.getSNValue () + v2.getSNValue ());
+
+ int ov1 = v1.exponent(), ov2 = v2.exponent();
+ auto vv1 = static_cast (v1.mantissa());
+ auto vv2 = static_cast (v2.mantissa());
+
+ if (v1.negative())
+ vv1 = -vv1;
+
+ if (v2.negative())
+ vv2 = -vv2;
+
+ if (ov1 < ov2)
{
- // It is an error for currency or issuer not to be specified for valid
- // json.
- elem[jss::value] = getText ();
- elem[jss::currency] = getHumanCurrency ();
- elem[jss::issuer] = to_string (mIssue.account);
+ while (ov1 < (ov2 - 1))
+ {
+ vv1 /= 10;
+ ++ov1;
+ }
+
+ if (roundUp)
+ vv1 += 9;
+
+ vv1 /= 10;
+ ++ov1;
+ }
+
+ if (ov2 < ov1)
+ {
+ while (ov2 < (ov1 - 1))
+ {
+ vv2 /= 10;
+ ++ov2;
+ }
+
+ if (roundUp)
+ vv2 += 9;
+
+ vv2 /= 10;
+ ++ov2;
+ }
+
+ std::int64_t fv = vv1 + vv2;
+
+ if ((fv >= -10) && (fv <= 10))
+ return STAmount (v1.getFName (), v1.issue());
+ else if (fv >= 0)
+ {
+ std::uint64_t v = static_cast (fv);
+ canonicalizeRound (false, v, ov1, roundUp);
+ return STAmount (v1.getFName (), v1.issue(), v, ov1, false);
}
else
{
- elem = getText ();
+ std::uint64_t v = static_cast (-fv);
+ canonicalizeRound (false, v, ov1, !roundUp);
+ return STAmount (v1.getFName (), v1.issue(), v, ov1, true);
}
}
-Json::Value STAmount::getJson (int) const
+STAmount
+subRound (STAmount const& v1, STAmount const& v2, bool roundUp)
{
- Json::Value elem;
- setJson (elem);
- return elem;
+ v1.throwComparable (v2);
+
+ if (v2.mantissa() == 0)
+ return v1;
+
+ if (v1.mantissa() == 0)
+ return STAmount (v1.getFName (), v1.issue(), v2.mantissa(),
+ v2.exponent(), !v2.negative());
+
+ if (v1.native())
+ return STAmount (v1.getFName (), v1.getSNValue () - v2.getSNValue ());
+
+ int ov1 = v1.exponent(), ov2 = v2.exponent();
+ auto vv1 = static_cast (v1.mantissa());
+ auto vv2 = static_cast (v2.mantissa());
+
+ if (v1.negative())
+ vv1 = -vv1;
+
+ if (!v2.negative())
+ vv2 = -vv2;
+
+ if (ov1 < ov2)
+ {
+ while (ov1 < (ov2 - 1))
+ {
+ vv1 /= 10;
+ ++ov1;
+ }
+
+ if (roundUp)
+ vv1 += 9;
+
+ vv1 /= 10;
+ ++ov1;
+ }
+
+ if (ov2 < ov1)
+ {
+ while (ov2 < (ov1 - 1))
+ {
+ vv2 /= 10;
+ ++ov2;
+ }
+
+ if (roundUp)
+ vv2 += 9;
+
+ vv2 /= 10;
+ ++ov2;
+ }
+
+ std::int64_t fv = vv1 + vv2;
+
+ if ((fv >= -10) && (fv <= 10))
+ return STAmount (v1.getFName (), v1.issue());
+
+ if (fv >= 0)
+ {
+ std::uint64_t v = static_cast (fv);
+ canonicalizeRound (false, v, ov1, roundUp);
+ return STAmount (v1.getFName (), v1.issue(), v, ov1, false);
+ }
+ else
+ {
+ std::uint64_t v = static_cast (-fv);
+ canonicalizeRound (false, v, ov1, !roundUp);
+ return STAmount (v1.getFName (), v1.issue(), v, ov1, true);
+ }
}
-//------------------------------------------------------------------------------
-
-class STAmount_test : public beast::unit_test::suite
+STAmount
+mulRound (STAmount const& v1, STAmount const& v2,
+ Issue const& issue, bool roundUp)
{
-public:
- static STAmount serializeAndDeserialize (STAmount const& s)
- {
- Serializer ser;
- s.add (ser);
+ if (v1 == zero || v2 == zero)
+ return {issue};
- SerializerIterator sit (ser);
- return STAmount::deserialize (sit);
+ if (v1.native() && v2.native() && isXRP (issue))
+ {
+ std::uint64_t minV = (v1.getSNValue () < v2.getSNValue ()) ?
+ v1.getSNValue () : v2.getSNValue ();
+ std::uint64_t maxV = (v1.getSNValue () < v2.getSNValue ()) ?
+ v2.getSNValue () : v1.getSNValue ();
+
+ if (minV > 3000000000ull) // sqrt(cMaxNative)
+ throw std::runtime_error ("Native value overflow");
+
+ if (((maxV >> 32) * minV) > 2095475792ull) // cMaxNative / 2^32
+ throw std::runtime_error ("Native value overflow");
+
+ return STAmount (v1.getFName (), minV * maxV);
}
- //--------------------------------------------------------------------------
+ std::uint64_t value1 = v1.mantissa(), value2 = v2.mantissa();
+ int offset1 = v1.exponent(), offset2 = v2.exponent();
- bool roundTest (int n, int d, int m)
+ if (v1.native())
{
- // check STAmount rounding
- STAmount num (noIssue(), n);
- STAmount den (noIssue(), d);
- STAmount mul (noIssue(), m);
- STAmount quot = STAmount::divide (n, d, noIssue());
- STAmount res = STAmount::multiply (quot, mul, noIssue());
-
- expect (! res.isNative (), "Product should not be native");
-
- res.roundSelf ();
-
- STAmount cmp (noIssue(), (n * m) / d);
-
- expect (! cmp.isNative (), "Comparison amount should not be native");
-
- if (res != cmp)
+ while (value1 < STAmount::cMinValue)
{
- cmp.throwComparable (res);
-
- WriteLog (lsWARNING, STAmount) << "(" << num.getText () << "/" << den.getText () << ") X " << mul.getText () << " = "
- << res.getText () << " not " << cmp.getText ();
-
- fail ("Rounding");
-
- return false;
- }
- else
- {
- pass ();
- }
-
- return true;
- }
-
- void mulTest (int a, int b)
- {
- STAmount aa (noIssue(), a);
- STAmount bb (noIssue(), b);
- STAmount prod1 (STAmount::multiply (aa, bb, noIssue()));
-
- expect (! prod1.isNative ());
-
- STAmount prod2 (noIssue(), static_cast (a) * static_cast (b));
-
- if (prod1 != prod2)
- {
- WriteLog (lsWARNING, STAmount) << "nn(" << aa.getFullText () << " * " << bb.getFullText () << ") = " << prod1.getFullText ()
- << " not " << prod2.getFullText ();
-
- fail ("Multiplication result is not exact");
- }
- else
- {
- pass ();
- }
-
- aa = a;
- prod1 = STAmount::multiply (aa, bb, noIssue());
-
- if (prod1 != prod2)
- {
- WriteLog (lsWARNING, STAmount) << "n(" << aa.getFullText () << " * " << bb.getFullText () << ") = " << prod1.getFullText ()
- << " not " << prod2.getFullText ();
- fail ("Multiplication result is not exact");
- }
- else
- {
- pass ();
+ value1 *= 10;
+ --offset1;
}
}
- //--------------------------------------------------------------------------
-
- void testSetValue ()
+ if (v2.native())
{
- testcase ("set value");
-
- STAmount saTmp;
-
- #if 0
- // Check native floats
- saTmp.setFullValue ("1^0");
- BOOST_CHECK_MESSAGE (SYSTEM_CURRENCY_PARTS == saTmp.getNValue (), "float integer failed");
- saTmp.setFullValue ("0^1");
- BOOST_CHECK_MESSAGE (SYSTEM_CURRENCY_PARTS / 10 == saTmp.getNValue (), "float fraction failed");
- saTmp.setFullValue ("0^12");
- BOOST_CHECK_MESSAGE (12 * SYSTEM_CURRENCY_PARTS / 100 == saTmp.getNValue (), "float fraction failed");
- saTmp.setFullValue ("1^2");
- BOOST_CHECK_MESSAGE (SYSTEM_CURRENCY_PARTS + (2 * SYSTEM_CURRENCY_PARTS / 10) == saTmp.getNValue (), "float combined failed");
- #endif
-
- // Check native integer
- saTmp.setFullValue ("1");
- expect (1 == saTmp.getNValue (), "should be equal");
- }
-
- //--------------------------------------------------------------------------
-
- void testNativeCurrency ()
- {
- testcase ("native currency");
-
- STAmount zeroSt, one (1), hundred (100);
-
- unexpected (serializeAndDeserialize (zeroSt) != zeroSt, "STAmount fail");
-
- unexpected (serializeAndDeserialize (one) != one, "STAmount fail");
-
- unexpected (serializeAndDeserialize (hundred) != hundred, "STAmount fail");
-
- unexpected (!zeroSt.isNative (), "STAmount fail");
-
- unexpected (!hundred.isNative (), "STAmount fail");
-
- unexpected (zeroSt != zero, "STAmount fail");
-
- unexpected (one == zero, "STAmount fail");
-
- unexpected (hundred == zero, "STAmount fail");
-
- unexpected ((zeroSt < zeroSt), "STAmount fail");
-
- unexpected (! (zeroSt < one), "STAmount fail");
-
- unexpected (! (zeroSt < hundred), "STAmount fail");
-
- unexpected ((one < zeroSt), "STAmount fail");
-
- unexpected ((one < one), "STAmount fail");
-
- unexpected (! (one < hundred), "STAmount fail");
-
- unexpected ((hundred < zeroSt), "STAmount fail");
-
- unexpected ((hundred < one), "STAmount fail");
-
- unexpected ((hundred < hundred), "STAmount fail");
-
- unexpected ((zeroSt > zeroSt), "STAmount fail");
-
- unexpected ((zeroSt > one), "STAmount fail");
-
- unexpected ((zeroSt > hundred), "STAmount fail");
-
- unexpected (! (one > zeroSt), "STAmount fail");
-
- unexpected ((one > one), "STAmount fail");
-
- unexpected ((one > hundred), "STAmount fail");
-
- unexpected (! (hundred > zeroSt), "STAmount fail");
-
- unexpected (! (hundred > one), "STAmount fail");
-
- unexpected ((hundred > hundred), "STAmount fail");
-
- unexpected (! (zeroSt <= zeroSt), "STAmount fail");
-
- unexpected (! (zeroSt <= one), "STAmount fail");
-
- unexpected (! (zeroSt <= hundred), "STAmount fail");
-
- unexpected ((one <= zeroSt), "STAmount fail");
-
- unexpected (! (one <= one), "STAmount fail");
-
- unexpected (! (one <= hundred), "STAmount fail");
-
- unexpected ((hundred <= zeroSt), "STAmount fail");
-
- unexpected ((hundred <= one), "STAmount fail");
-
- unexpected (! (hundred <= hundred), "STAmount fail");
-
- unexpected (! (zeroSt >= zeroSt), "STAmount fail");
-
- unexpected ((zeroSt >= one), "STAmount fail");
-
- unexpected ((zeroSt >= hundred), "STAmount fail");
-
- unexpected (! (one >= zeroSt), "STAmount fail");
-
- unexpected (! (one >= one), "STAmount fail");
-
- unexpected ((one >= hundred), "STAmount fail");
-
- unexpected (! (hundred >= zeroSt), "STAmount fail");
-
- unexpected (! (hundred >= one), "STAmount fail");
-
- unexpected (! (hundred >= hundred), "STAmount fail");
-
- unexpected (! (zeroSt == zeroSt), "STAmount fail");
-
- unexpected ((zeroSt == one), "STAmount fail");
-
- unexpected ((zeroSt == hundred), "STAmount fail");
-
- unexpected ((one == zeroSt), "STAmount fail");
-
- unexpected (! (one == one), "STAmount fail");
-
- unexpected ((one == hundred), "STAmount fail");
-
- unexpected ((hundred == zeroSt), "STAmount fail");
-
- unexpected ((hundred == one), "STAmount fail");
-
- unexpected (! (hundred == hundred), "STAmount fail");
-
- unexpected ((zeroSt != zeroSt), "STAmount fail");
-
- unexpected (! (zeroSt != one), "STAmount fail");
-
- unexpected (! (zeroSt != hundred), "STAmount fail");
-
- unexpected (! (one != zeroSt), "STAmount fail");
-
- unexpected ((one != one), "STAmount fail");
-
- unexpected (! (one != hundred), "STAmount fail");
-
- unexpected (! (hundred != zeroSt), "STAmount fail");
-
- unexpected (! (hundred != one), "STAmount fail");
-
- unexpected ((hundred != hundred), "STAmount fail");
-
- unexpected (STAmount ().getText () != "0", "STAmount fail");
-
- unexpected (STAmount (31).getText () != "31", "STAmount fail");
-
- unexpected (STAmount (310).getText () != "310", "STAmount fail");
-
- unexpected (to_string (Currency ()) != "XRP", "cHC(XRP)");
-
- Currency c;
- unexpected (!to_currency (c, "USD"), "create USD currency");
- unexpected (to_string (c) != "USD", "check USD currency");
-
- const std::string cur = "015841551A748AD2C1F76FF6ECB0CCCD00000000";
- unexpected (!to_currency (c, cur), "create custom currency");
- unexpected (to_string (c) != cur, "check custom currency");
- unexpected (c != Currency (cur), "check custom currency");
- }
-
- //--------------------------------------------------------------------------
-
- void testCustomCurrency ()
- {
- testcase ("custom currency");
-
- STAmount zeroSt (noIssue()), one (noIssue(), 1), hundred (noIssue(), 100);
-
- unexpected (serializeAndDeserialize (zeroSt) != zeroSt, "STAmount fail");
-
- unexpected (serializeAndDeserialize (one) != one, "STAmount fail");
-
- unexpected (serializeAndDeserialize (hundred) != hundred, "STAmount fail");
-
- unexpected (zeroSt.isNative (), "STAmount fail");
-
- unexpected (hundred.isNative (), "STAmount fail");
-
- unexpected (zeroSt != zero, "STAmount fail");
-
- unexpected (one == zero, "STAmount fail");
-
- unexpected (hundred == zero, "STAmount fail");
-
- unexpected ((zeroSt < zeroSt), "STAmount fail");
-
- unexpected (! (zeroSt < one), "STAmount fail");
-
- unexpected (! (zeroSt < hundred), "STAmount fail");
-
- unexpected ((one < zeroSt), "STAmount fail");
-
- unexpected ((one < one), "STAmount fail");
-
- unexpected (! (one < hundred), "STAmount fail");
-
- unexpected ((hundred < zeroSt), "STAmount fail");
-
- unexpected ((hundred < one), "STAmount fail");
-
- unexpected ((hundred < hundred), "STAmount fail");
-
- unexpected ((zeroSt > zeroSt), "STAmount fail");
-
- unexpected ((zeroSt > one), "STAmount fail");
-
- unexpected ((zeroSt > hundred), "STAmount fail");
-
- unexpected (! (one > zeroSt), "STAmount fail");
-
- unexpected ((one > one), "STAmount fail");
-
- unexpected ((one > hundred), "STAmount fail");
-
- unexpected (! (hundred > zeroSt), "STAmount fail");
-
- unexpected (! (hundred > one), "STAmount fail");
-
- unexpected ((hundred > hundred), "STAmount fail");
-
- unexpected (! (zeroSt <= zeroSt), "STAmount fail");
-
- unexpected (! (zeroSt <= one), "STAmount fail");
-
- unexpected (! (zeroSt <= hundred), "STAmount fail");
-
- unexpected ((one <= zeroSt), "STAmount fail");
-
- unexpected (! (one <= one), "STAmount fail");
-
- unexpected (! (one <= hundred), "STAmount fail");
-
- unexpected ((hundred <= zeroSt), "STAmount fail");
-
- unexpected ((hundred <= one), "STAmount fail");
-
- unexpected (! (hundred <= hundred), "STAmount fail");
-
- unexpected (! (zeroSt >= zeroSt), "STAmount fail");
-
- unexpected ((zeroSt >= one), "STAmount fail");
-
- unexpected ((zeroSt >= hundred), "STAmount fail");
-
- unexpected (! (one >= zeroSt), "STAmount fail");
-
- unexpected (! (one >= one), "STAmount fail");
-
- unexpected ((one >= hundred), "STAmount fail");
-
- unexpected (! (hundred >= zeroSt), "STAmount fail");
-
- unexpected (! (hundred >= one), "STAmount fail");
-
- unexpected (! (hundred >= hundred), "STAmount fail");
-
- unexpected (! (zeroSt == zeroSt), "STAmount fail");
-
- unexpected ((zeroSt == one), "STAmount fail");
-
- unexpected ((zeroSt == hundred), "STAmount fail");
-
- unexpected ((one == zeroSt), "STAmount fail");
-
- unexpected (! (one == one), "STAmount fail");
-
- unexpected ((one == hundred), "STAmount fail");
-
- unexpected ((hundred == zeroSt), "STAmount fail");
-
- unexpected ((hundred == one), "STAmount fail");
-
- unexpected (! (hundred == hundred), "STAmount fail");
-
- unexpected ((zeroSt != zeroSt), "STAmount fail");
-
- unexpected (! (zeroSt != one), "STAmount fail");
-
- unexpected (! (zeroSt != hundred), "STAmount fail");
-
- unexpected (! (one != zeroSt), "STAmount fail");
-
- unexpected ((one != one), "STAmount fail");
-
- unexpected (! (one != hundred), "STAmount fail");
-
- unexpected (! (hundred != zeroSt), "STAmount fail");
-
- unexpected (! (hundred != one), "STAmount fail");
-
- unexpected ((hundred != hundred), "STAmount fail");
-
- unexpected (STAmount (noIssue()).getText () != "0", "STAmount fail");
-
- unexpected (STAmount (noIssue(), 31).getText () != "31", "STAmount fail");
-
- unexpected (STAmount (noIssue(), 31, 1).getText () != "310", "STAmount fail");
-
- unexpected (STAmount (noIssue(), 31, -1).getText () != "3.1", "STAmount fail");
-
- unexpected (STAmount (noIssue(), 31, -2).getText () != "0.31", "STAmount fail");
-
- unexpected (STAmount::multiply (STAmount (noIssue(), 20), STAmount (3), noIssue()).getText () != "60",
- "STAmount multiply fail 1");
-
- unexpected (STAmount::multiply (STAmount (noIssue(), 20), STAmount (3), xrpIssue ()).getText () != "60",
- "STAmount multiply fail 2");
-
- unexpected (STAmount::multiply (STAmount (20), STAmount (3), noIssue()).getText () != "60",
- "STAmount multiply fail 3");
-
- unexpected (STAmount::multiply (STAmount (20), STAmount (3), xrpIssue ()).getText () != "60",
- "STAmount multiply fail 4");
-
- if (STAmount::divide (STAmount (noIssue(), 60), STAmount (3), noIssue()).getText () != "20")
+ while (value2 < STAmount::cMinValue)
{
- WriteLog (lsFATAL, STAmount) << "60/3 = " <<
- STAmount::divide (STAmount (noIssue(), 60),
- STAmount (3), noIssue()).getText ();
- fail ("STAmount divide fail");
+ value2 *= 10;
+ --offset2;
}
- else
+ }
+
+ bool resultNegative = v1.negative() != v2.negative();
+ // Compute (numerator * denominator) / 10^14 with rounding
+ // 10^16 <= result <= 10^18
+ CBigNum v;
+
+ if ((BN_add_word64 (&v, value1) != 1) || (BN_mul_word64 (&v, value2) != 1))
+ throw std::runtime_error ("internal bn error");
+
+ if (resultNegative != roundUp) // rounding down is automatic when we divide
+ BN_add_word64 (&v, tenTo14m1);
+
+ if (BN_div_word64 (&v, tenTo14) == ((std::uint64_t) - 1))
+ throw std::runtime_error ("internal bn error");
+
+ // 10^16 <= product <= 10^18
+ assert (BN_num_bytes (&v) <= 64);
+
+ std::uint64_t amount = v.getuint64 ();
+ int offset = offset1 + offset2 + 14;
+ canonicalizeRound (
+ isXRP (issue), amount, offset, resultNegative != roundUp);
+ return STAmount (issue, amount, offset, resultNegative);
+}
+
+STAmount
+divRound (STAmount const& num, STAmount const& den,
+ Issue const& issue, bool roundUp)
+{
+ if (den == zero)
+ throw std::runtime_error ("division by zero");
+
+ if (num == zero)
+ return {issue};
+
+ std::uint64_t numVal = num.mantissa(), denVal = den.mantissa();
+ int numOffset = num.exponent(), denOffset = den.exponent();
+
+ if (num.native())
+ while (numVal < STAmount::cMinValue)
{
- pass ();
+ // Need to bring into range
+ numVal *= 10;
+ --numOffset;
}
- unexpected (STAmount::divide (STAmount (noIssue(), 60), STAmount (3), xrpIssue ()).getText () != "20",
- "STAmount divide fail");
-
- unexpected (STAmount::divide (STAmount (noIssue(), 60), STAmount (noIssue(), 3), noIssue()).getText () != "20",
- "STAmount divide fail");
-
- unexpected (STAmount::divide (STAmount (noIssue(), 60), STAmount (noIssue(), 3), xrpIssue ()).getText () != "20",
- "STAmount divide fail");
-
- STAmount a1 (noIssue(), 60), a2 (noIssue(), 10, -1);
-
- unexpected (STAmount::divide (a2, a1, noIssue()) != STAmount::setRate (STAmount::getRate (a1, a2)),
- "STAmount setRate(getRate) fail");
-
- unexpected (STAmount::divide (a1, a2, noIssue()) != STAmount::setRate (STAmount::getRate (a2, a1)),
- "STAmount setRate(getRate) fail");
- }
-
- //--------------------------------------------------------------------------
-
- void testArithmetic ()
- {
- testcase ("arithmetic");
-
- CBigNum b;
-
- for (int i = 0; i < 16; ++i)
+ if (den.native())
+ while (denVal < STAmount::cMinValue)
{
- std::uint64_t r = rand ();
- r <<= 32;
- r |= rand ();
- b.setuint64 (r);
-
- if (b.getuint64 () != r)
- {
- WriteLog (lsFATAL, STAmount) << r << " != " << b.getuint64 () << " " << b.ToString (16);
- fail ("setull64/getull64 failure");
- }
- else
- {
- pass ();
- }
+ denVal *= 10;
+ --denOffset;
}
- // Test currency multiplication and division operations such as
- // convertToDisplayAmount, convertToInternalAmount, getRate, getClaimed, and getNeeded
+ bool resultNegative = num.negative() != den.negative();
+ // Compute (numerator * 10^17) / denominator
+ CBigNum v;
- unexpected (STAmount::getRate (STAmount (1), STAmount (10)) != (((100ull - 14) << (64 - 8)) | 1000000000000000ull),
- "STAmount getRate fail 1");
+ if ((BN_add_word64 (&v, numVal) != 1) || (BN_mul_word64 (&v, tenTo17) != 1))
+ throw std::runtime_error ("internal bn error");
- unexpected (STAmount::getRate (STAmount (10), STAmount (1)) != (((100ull - 16) << (64 - 8)) | 1000000000000000ull),
- "STAmount getRate fail 2");
+ if (resultNegative != roundUp) // Rounding down is automatic when we divide
+ BN_add_word64 (&v, denVal - 1);
- unexpected (STAmount::getRate (STAmount (noIssue(), 1), STAmount (noIssue(), 10)) != (((100ull - 14) << (64 - 8)) | 1000000000000000ull),
- "STAmount getRate fail 3");
+ if (BN_div_word64 (&v, denVal) == ((std::uint64_t) - 1))
+ throw std::runtime_error ("internal bn error");
- unexpected (STAmount::getRate (STAmount (noIssue(), 10), STAmount (noIssue(), 1)) != (((100ull - 16) << (64 - 8)) | 1000000000000000ull),
- "STAmount getRate fail 4");
+ // 10^16 <= quotient <= 10^18
+ assert (BN_num_bytes (&v) <= 64);
- unexpected (STAmount::getRate (STAmount (noIssue(), 1), STAmount (10)) != (((100ull - 14) << (64 - 8)) | 1000000000000000ull),
- "STAmount getRate fail 5");
-
- unexpected (STAmount::getRate (STAmount (noIssue(), 10), STAmount (1)) != (((100ull - 16) << (64 - 8)) | 1000000000000000ull),
- "STAmount getRate fail 6");
-
- unexpected (STAmount::getRate (STAmount (1), STAmount (noIssue(), 10)) != (((100ull - 14) << (64 - 8)) | 1000000000000000ull),
- "STAmount getRate fail 7");
-
- unexpected (STAmount::getRate (STAmount (10), STAmount (noIssue(), 1)) != (((100ull - 16) << (64 - 8)) | 1000000000000000ull),
- "STAmount getRate fail 8");
-
- roundTest (1, 3, 3);
- roundTest (2, 3, 9);
- roundTest (1, 7, 21);
- roundTest (1, 2, 4);
- roundTest (3, 9, 18);
- roundTest (7, 11, 44);
-
- for (int i = 0; i <= 100000; ++i)
- mulTest (rand () % 10000000, rand () % 10000000);
- }
-
- //--------------------------------------------------------------------------
-
- template
- bool
- expect (Cond cond, beast::String const& s)
- {
- return suite::expect (cond, s.toStdString());
- }
-
- template
- bool
- expect (Cond cond)
- {
- return suite::expect (cond);
- }
-
- void testUnderflow ()
- {
- testcase ("underflow");
-
- STAmount bigNative (STAmount::cMaxNative / 2);
- STAmount bigValue (noIssue(),
- (STAmount::cMinValue + STAmount::cMaxValue) / 2, STAmount::cMaxOffset - 1);
- STAmount smallValue (noIssue(),
- (STAmount::cMinValue + STAmount::cMaxValue) / 2, STAmount::cMinOffset + 1);
- STAmount zeroSt (noIssue(), 0);
-
- STAmount smallXsmall = STAmount::multiply (smallValue, smallValue, noIssue());
-
- expect (smallXsmall == zero, "smallXsmall != 0");
-
- STAmount bigDsmall = STAmount::divide (smallValue, bigValue, noIssue());
-
- expect (bigDsmall == zero, beast::String ("small/big != 0: ") + bigDsmall.getText ());
-
-#if 0
- // TODO(tom): this test makes no sense - we should have no way to have
- // the currency not be XRP while the account is XRP.
- bigDsmall = STAmount::divide (smallValue, bigNative, noCurrency(), xrpAccount ());
-#endif
-
- expect (bigDsmall == zero, beast::String ("small/bigNative != 0: ") + bigDsmall.getText ());
-
- bigDsmall = STAmount::divide (smallValue, bigValue, xrpIssue ());
-
- expect (bigDsmall == zero, beast::String ("(small/big)->N != 0: ") + bigDsmall.getText ());
-
- bigDsmall = STAmount::divide (smallValue, bigNative, xrpIssue ());
-
- expect (bigDsmall == zero, beast::String ("(small/bigNative)->N != 0: ") + bigDsmall.getText ());
-
- // very bad offer
- std::uint64_t r = STAmount::getRate (smallValue, bigValue);
-
- expect (r == 0, "getRate(smallOut/bigIn) != 0");
-
- // very good offer
- r = STAmount::getRate (bigValue, smallValue);
-
- expect (r == 0, "getRate(smallIn/bigOUt) != 0");
- }
-
- //--------------------------------------------------------------------------
-
- void testRounding ()
- {
- // VFALCO TODO There are no actual tests here, just printed output?
- // Change this to actually do something.
-
-#if 0
- beginTestCase ("rounding ");
-
- std::uint64_t value = 25000000000000000ull;
- int offset = -14;
- STAmount::canonicalizeRound (false, value, offset, true);
-
- STAmount one (noIssue(), 1);
- STAmount two (noIssue(), 2);
- STAmount three (noIssue(), 3);
-
- STAmount oneThird1 = STAmount::divRound (one, three, noIssue(), false);
- STAmount oneThird2 = STAmount::divide (one, three, noIssue());
- STAmount oneThird3 = STAmount::divRound (one, three, noIssue(), true);
- WriteLog (lsINFO, STAmount) << oneThird1;
- WriteLog (lsINFO, STAmount) << oneThird2;
- WriteLog (lsINFO, STAmount) << oneThird3;
-
- STAmount twoThird1 = STAmount::divRound (two, three, noIssue(), false);
- STAmount twoThird2 = STAmount::divide (two, three, noIssue());
- STAmount twoThird3 = STAmount::divRound (two, three, noIssue(), true);
- WriteLog (lsINFO, STAmount) << twoThird1;
- WriteLog (lsINFO, STAmount) << twoThird2;
- WriteLog (lsINFO, STAmount) << twoThird3;
-
- STAmount oneA = STAmount::mulRound (oneThird1, three, noIssue(), false);
- STAmount oneB = STAmount::multiply (oneThird2, three, noIssue());
- STAmount oneC = STAmount::mulRound (oneThird3, three, noIssue(), true);
- WriteLog (lsINFO, STAmount) << oneA;
- WriteLog (lsINFO, STAmount) << oneB;
- WriteLog (lsINFO, STAmount) << oneC;
-
- STAmount fourThirdsA = STAmount::addRound (twoThird2, twoThird2, false);
- STAmount fourThirdsB = twoThird2 + twoThird2;
- STAmount fourThirdsC = STAmount::addRound (twoThird2, twoThird2, true);
- WriteLog (lsINFO, STAmount) << fourThirdsA;
- WriteLog (lsINFO, STAmount) << fourThirdsB;
- WriteLog (lsINFO, STAmount) << fourThirdsC;
-
- STAmount dripTest1 = STAmount::mulRound (twoThird2, two, xrpIssue (), false);
- STAmount dripTest2 = STAmount::multiply (twoThird2, two, xrpIssue ());
- STAmount dripTest3 = STAmount::mulRound (twoThird2, two, xrpIssue (), true);
- WriteLog (lsINFO, STAmount) << dripTest1;
- WriteLog (lsINFO, STAmount) << dripTest2;
- WriteLog (lsINFO, STAmount) << dripTest3;
-#endif
- }
-
- //--------------------------------------------------------------------------
-
- void run ()
- {
- testSetValue ();
- testNativeCurrency ();
- testCustomCurrency ();
- testArithmetic ();
- testUnderflow ();
- testRounding ();
- }
-};
-
-BEAST_DEFINE_TESTSUITE(STAmount,ripple_data,ripple);
+ std::uint64_t amount = v.getuint64 ();
+ int offset = numOffset - denOffset - 17;
+ canonicalizeRound (
+ isXRP (issue), amount, offset, resultNegative != roundUp);
+ return STAmount (issue, amount, offset, resultNegative);
+}
} // ripple
diff --git a/src/ripple/module/data/protocol/STAmount.h b/src/ripple/module/data/protocol/STAmount.h
index 0efc99da11..032191fed0 100644
--- a/src/ripple/module/data/protocol/STAmount.h
+++ b/src/ripple/module/data/protocol/STAmount.h
@@ -40,6 +40,18 @@ namespace ripple {
// Low 56 bits are value, legal range is 10^15 to (10^16 - 1) inclusive
class STAmount : public SerializedType
{
+public:
+ typedef std::uint64_t mantissa_type;
+ typedef int exponent_type;
+ typedef std::pair rep;
+
+private:
+ Issue mIssue;
+ mantissa_type mValue;
+ exponent_type mOffset;
+ bool mIsNative; // A shorthand for isXRP(mIssue).
+ bool mIsNegative;
+
public:
static const int cMinOffset = -96;
static const int cMaxOffset = 80;
@@ -55,164 +67,168 @@ public:
static std::uint64_t uRateOne;
- STAmount (std::uint64_t v = 0, bool negative = false)
- : mValue (v), mOffset (0), mIsNative (true), mIsNegative (negative)
- {
- if (v == 0) mIsNegative = false;
- }
+ //--------------------------------------------------------------------------
- STAmount (SField::ref n, std::uint64_t v = 0, bool negative = false)
- : SerializedType (n), mValue (v), mOffset (0), mIsNative (true),
- mIsNegative (negative)
- {
- }
+ struct unchecked { };
- STAmount (SField::ref n, std::int64_t v)
- : SerializedType (n), mOffset (0), mIsNative (true)
- {
- set (v);
- }
+ // Calls canonicalize
+ STAmount (SField::ref name, Issue const& issue,
+ mantissa_type mantissa, exponent_type exponent,
+ bool native, bool negative);
+
+ // Does not call canonicalize
+ STAmount (SField::ref name, Issue const& issue,
+ mantissa_type mantissa, exponent_type exponent,
+ bool native, bool negative, unchecked);
+
+ STAmount (SField::ref name, std::int64_t mantissa);
+
+ STAmount (SField::ref name,
+ std::uint64_t mantissa = 0, bool negative = false);
+
+ STAmount (SField::ref name, Issue const& issue,
+ std::uint64_t mantissa = 0, int exponent = 0, bool negative = false);
+
+ STAmount (std::uint64_t mantissa = 0, bool negative = false);
STAmount (Issue const& issue,
- std::uint64_t uV = 0, int iOff = 0, bool negative = false)
- : mIssue(issue), mValue (uV), mOffset (iOff), mIsNegative (negative)
- {
- canonicalize ();
- }
+ std::uint64_t mantissa = 0, int exponent = 0, bool negative = false);
+ // VFALCO Is this needed when we have the previous signature?
STAmount (Issue const& issue,
- std::uint32_t uV, int iOff = 0, bool negative = false)
- : mIssue(issue), mValue (uV), mOffset (iOff), mIsNegative (negative)
- {
- canonicalize ();
- }
+ std::uint32_t mantissa, int exponent = 0, bool negative = false);
- STAmount (SField::ref n, Issue const& issue,
- std::uint64_t v = 0, int off = 0, bool negative = false) :
- SerializedType (n), mIssue(issue), mValue (v), mOffset (off),
- mIsNegative (negative)
- {
- canonicalize ();
- }
+ STAmount (Issue const& issue, std::int64_t mantissa, int exponent = 0);
- STAmount (Issue const& issue, std::int64_t v, int iOff = 0)
- : mIssue(issue), mOffset (iOff)
- {
- set (v);
- canonicalize ();
- }
+ STAmount (Issue const& issue, int mantissa, int exponent = 0);
- STAmount (SField::ref n, Issue const& issue, std::int64_t v, int off = 0)
- : SerializedType (n), mIssue(issue), mOffset (off)
- {
- set (v);
- canonicalize ();
- }
+ //--------------------------------------------------------------------------
- STAmount (Issue const& issue, int v, int iOff = 0)
- : mIssue(issue), mOffset (iOff)
- {
- set (v);
- canonicalize ();
- }
+private:
+ static
+ std::unique_ptr
+ construct (SerializerIterator&, SField::ref name);
- STAmount (SField::ref n, Issue const& issue, int v, int off = 0)
- : SerializedType (n), mIssue(issue), mOffset (off)
- {
- set (v);
- canonicalize ();
- }
+public:
+ static
+ STAmount
+ createFromInt64 (SField::ref n, std::int64_t v);
- STAmount (SField::ref, Json::Value const&);
-
- static STAmount createFromInt64 (SField::ref n, std::int64_t v);
-
- static std::unique_ptr deserialize (
+ static
+ std::unique_ptr
+ deserialize (
SerializerIterator& sit, SField::ref name)
{
return construct (sit, name);
}
- bool bSetJson (Json::Value const& jvSource);
-
- static STAmount saFromRate (std::uint64_t uRate = 0)
+ static
+ STAmount
+ saFromRate (std::uint64_t uRate = 0)
{
return STAmount (noIssue(), uRate, -9, false);
}
- SerializedTypeID getSType () const
- {
- return STI_AMOUNT;
- }
- std::string getText () const;
- std::string getFullText () const;
- void add (Serializer& s) const;
+ static
+ STAmount
+ deserialize (SerializerIterator&);
- int getExponent () const
- {
- return mOffset;
- }
- std::uint64_t getMantissa () const
- {
- return mValue;
- }
+ //--------------------------------------------------------------------------
+ //
+ // Observers
+ //
+ //--------------------------------------------------------------------------
- int signum () const
+ int exponent() const noexcept { return mOffset; }
+ bool native() const noexcept { return mIsNative; }
+ bool negative() const noexcept { return mIsNegative; }
+ std::uint64_t mantissa() const noexcept { return mValue; }
+ Issue const& issue() const { return mIssue; }
+
+ // These three are deprecated
+ Currency const& getCurrency() const { return mIssue.currency; }
+ Account const& getIssuer() const { return mIssue.account; }
+ bool isNative() const { return mIsNative; }
+
+ int
+ signum() const noexcept
{
return mValue ? (mIsNegative ? -1 : 1) : 0;
}
-
- // When the currency is XRP, the value in raw units. S=signed
- std::uint64_t getNValue () const
- {
- if (!mIsNative)
- throw std::runtime_error ("not native");
-
- return mValue;
- }
- void setNValue (std::uint64_t v)
- {
- if (!mIsNative)
- throw std::runtime_error ("not native");
-
- mValue = v;
- }
- std::int64_t getSNValue () const;
- void setSNValue (std::int64_t);
-
- std::string getHumanCurrency () const;
-
- bool isNative () const
- {
- return mIsNative;
- }
- bool isLegalNet () const
- {
- return !mIsNative || (mValue <= cMaxNativeN);
- }
-
- explicit
- operator bool () const noexcept
- {
- return *this != zero;
- }
-
- void negate ()
- {
- if (*this != zero)
- mIsNegative = !mIsNegative;
- }
-
- /** @return a copy of amount with the same Issuer and Currency but zero
- value. */
- STAmount zeroed() const
+
+ /** Returns a zero value with the same issuer and currency. */
+ STAmount
+ zeroed() const
{
// TODO(tom): what does this next comment mean here?
// See https://ripplelabs.atlassian.net/browse/WC-1847?jql=
return STAmount (mIssue);
}
- void clear ()
+ // When the currency is XRP, the value in raw unsigned units.
+ std::uint64_t
+ getNValue() const;
+
+ // When the currency is XRP, the value in raw signed units.
+ std::int64_t
+ getSNValue() const;
+
+ // VFALCO TODO This can be a free function or just call the
+ // member on the issue.
+ std::string
+ getHumanCurrency() const;
+
+ void
+ setJson (Json::Value&) const;
+
+ //--------------------------------------------------------------------------
+ //
+ // Operators
+ //
+ //--------------------------------------------------------------------------
+
+ explicit operator bool() const noexcept
+ {
+ return *this != zero;
+ }
+
+ bool isComparable (STAmount const&) const;
+ void throwComparable (STAmount const&) const;
+
+ STAmount& operator+= (STAmount const&);
+ STAmount& operator-= (STAmount const&);
+ STAmount& operator+= (std::uint64_t);
+ STAmount& operator-= (std::uint64_t);
+
+ STAmount& operator= (std::uint64_t);
+ STAmount& operator= (beast::Zero)
+ {
+ clear();
+ return *this;
+ }
+
+ friend STAmount operator+ (STAmount const& v1, STAmount const& v2);
+ friend STAmount operator- (STAmount const& v1, STAmount const& v2);
+
+ //--------------------------------------------------------------------------
+ //
+ // Modification
+ //
+ //--------------------------------------------------------------------------
+
+ // VFALCO TODO Remove this, it is only called from the unit test
+ void roundSelf();
+
+ void setNValue (std::uint64_t v);
+ void setSNValue (std::int64_t);
+
+ void negate()
+ {
+ if (*this != zero)
+ mIsNegative = !mIsNegative;
+ }
+
+ void clear()
{
// VFALCO: Why -100?
mOffset = mIsNative ? 0 : -100;
@@ -223,26 +239,13 @@ public:
// Zero while copying currency and issuer.
void clear (STAmount const& saTmpl)
{
- clear(saTmpl.mIssue);
+ clear (saTmpl.mIssue);
}
void clear (Issue const& issue)
{
setIssue(issue);
- clear ();
- }
-
- STAmount& operator=(beast::Zero)
- {
- clear ();
- return *this;
- }
-
- int compare (STAmount const&) const;
-
- Account const& getIssuer () const
- {
- return mIssue.account;
+ clear();
}
void setIssuer (Account const& uIssuer)
@@ -254,201 +257,208 @@ public:
/** Set the Issue for this amount and update mIsNative. */
void setIssue (Issue const& issue);
- Currency const& getCurrency () const
- {
- return mIssue.currency;
- }
-
- Issue const& issue () const
- {
- return mIssue;
- }
-
+ // VFALCO TODO Rename to setValueOnly (it only sets mantissa and exponent)
+ // Make this private
bool setValue (std::string const& sAmount);
- bool setFullValue (
- std::string const& sAmount, std::string const& sCurrency = "",
- std::string const& sIssuer = "");
- void setValue (STAmount const&);
- virtual bool isEquivalent (const SerializedType& t) const;
- virtual bool isDefault () const
+ bool setFullValue (std::string const& sAmount,
+ std::string const& sCurrency = "", std::string const& sIssuer = "");
+
+ //--------------------------------------------------------------------------
+ //
+ // SerializedType
+ //
+ //--------------------------------------------------------------------------
+
+ SerializedTypeID
+ getSType() const override
+ {
+ return STI_AMOUNT;
+ }
+
+ std::string
+ getFullText() const override;
+
+ std::string
+ getText() const override;
+
+ Json::Value
+ getJson (int) const override;
+
+ void
+ add (Serializer& s) const override;
+
+ bool
+ isEquivalent (const SerializedType& t) const override;
+
+ bool
+ isDefault() const override
{
return (mValue == 0) && mIsNative;
}
- bool operator== (STAmount const&) const;
- bool operator!= (STAmount const&) const;
- bool operator< (STAmount const&) const;
- bool operator> (STAmount const&) const;
- bool operator<= (STAmount const&) const;
- bool operator>= (STAmount const&) const;
- bool isComparable (STAmount const&) const;
- void throwComparable (STAmount const&) const;
-
- // native currency only
- bool operator< (std::uint64_t) const;
- bool operator> (std::uint64_t) const;
- bool operator<= (std::uint64_t) const;
- bool operator>= (std::uint64_t) const;
- STAmount operator+ (std::uint64_t) const;
- STAmount operator- (std::uint64_t) const;
- STAmount operator- (void) const;
-
- STAmount& operator+= (STAmount const&);
- STAmount& operator-= (STAmount const&);
- STAmount& operator+= (std::uint64_t);
- STAmount& operator-= (std::uint64_t);
- STAmount& operator= (std::uint64_t);
-
- operator double () const;
-
- friend STAmount operator+ (STAmount const& v1, STAmount const& v2);
- friend STAmount operator- (STAmount const& v1, STAmount const& v2);
-
- static STAmount divide (
- STAmount const& v1, STAmount const& v2, Issue const& issue);
-
- static STAmount divide (
- STAmount const& v1, STAmount const& v2, STAmount const& saUnit)
- {
- return divide (v1, v2, saUnit.issue ());
- }
- static STAmount divide (STAmount const& v1, STAmount const& v2)
- {
- return divide (v1, v2, v1);
- }
-
- static STAmount multiply (
- STAmount const& v1, STAmount const& v2, Issue const& issue);
-
- static STAmount multiply (
- STAmount const& v1, STAmount const& v2, STAmount const& saUnit)
- {
- return multiply (v1, v2, saUnit.issue());
- }
- static STAmount multiply (STAmount const& v1, STAmount const& v2)
- {
- return multiply (v1, v2, v1);
- }
-
- /* addRound, subRound can end up rounding if the amount subtracted is too small
- to make a change. Consder (X-d) where d is very small relative to X.
- If you ask to round down, then (X-d) should not be X unless d is zero.
- If you ask to round up, (X+d) should never be X unless d is zero. (Assuming X and d are positive).
- */
- // Add, subtract, multiply, or divide rounding result in specified direction
- static STAmount addRound (
- STAmount const& v1, STAmount const& v2, bool roundUp);
- static STAmount subRound (
- STAmount const& v1, STAmount const& v2, bool roundUp);
- static STAmount mulRound (
- STAmount const& v1, STAmount const& v2, Issue const& issue,
- bool roundUp);
- static STAmount divRound (
- STAmount const& v1, STAmount const& v2, Issue const& issue,
- bool roundUp);
-
- static STAmount mulRound (
- STAmount const& v1, STAmount const& v2, STAmount const& saUnit,
- bool roundUp)
- {
- return mulRound (v1, v2, saUnit.issue (), roundUp);
- }
- static STAmount mulRound (
- STAmount const& v1, STAmount const& v2, bool roundUp)
- {
- return mulRound (v1, v2, v1.issue (), roundUp);
- }
- static STAmount divRound (
- STAmount const& v1, STAmount const& v2, STAmount const& saUnit,
- bool roundUp)
- {
- return divRound (v1, v2, saUnit.issue (), roundUp);
- }
- static STAmount divRound (
- STAmount const& v1, STAmount const& v2, bool roundUp)
- {
- return divRound (v1, v2, v1.issue (), roundUp);
- }
-
- // Someone is offering X for Y, what is the rate?
- // Rate: smaller is better, the taker wants the most out: in/out
- static std::uint64_t getRate (
- STAmount const& offerOut, STAmount const& offerIn);
- static STAmount setRate (std::uint64_t rate);
-
- // Someone is offering X for Y, I need Z, how much do I pay
-
- // WARNING: most methods in rippled have parameters ordered "in, out" - this
- // one is ordered "out, in".
- static STAmount getPay (
- STAmount const& out, STAmount const& in, STAmount const& needed);
-
- static STAmount deserialize (SerializerIterator&);
-
- Json::Value getJson (int) const;
- void setJson (Json::Value&) const;
-
- STAmount getRound () const;
- void roundSelf ();
-
- static void canonicalizeRound (
- bool isNative, std::uint64_t& value, int& offset, bool roundUp);
-
private:
- Issue mIssue;
+ STAmount*
+ duplicate() const override;
- std::uint64_t mValue;
- int mOffset;
- bool mIsNative; // A shorthand for isXRP(mIssue).
- bool mIsNegative;
-
- void canonicalize ();
- STAmount* duplicate () const
- {
- return new STAmount (*this);
- }
-
- static
- std::unique_ptr
- construct (SerializerIterator&, SField::ref name);
-
- STAmount (SField::ref name, Issue const& issue,
- std::uint64_t val, int off, bool isNat, bool negative)
- : SerializedType (name), mIssue(issue), mValue (val),
- mOffset (off), mIsNative (isNat), mIsNegative (negative)
- {
- }
-
- void set (std::int64_t v)
- {
- if (v < 0)
- {
- mIsNegative = true;
- mValue = static_cast (-v);
- }
- else
- {
- mIsNegative = false;
- mValue = static_cast (v);
- }
- }
-
- void set (int v)
- {
- if (v < 0)
- {
- mIsNegative = true;
- mValue = static_cast (-v);
- }
- else
- {
- mIsNegative = false;
- mValue = static_cast (v);
- }
- }
+ void canonicalize();
+ void set (std::int64_t v);
};
+//------------------------------------------------------------------------------
+//
+// Creation
+//
+//------------------------------------------------------------------------------
+
+// VFALCO TODO The parameter type should be Quality not uint64_t
+STAmount
+amountFromQuality (std::uint64_t rate);
+
+STAmount
+amountFromJson (SField::ref name, Json::Value const& v);
+
+bool
+amountFromJsonNoThrow (STAmount& result, Json::Value const& jvSource);
+
+//------------------------------------------------------------------------------
+//
+// Observers
+//
+//------------------------------------------------------------------------------
+
+inline
+bool
+isLegalNet (STAmount const& value)
+{
+ return ! value.native() || (value.mantissa() <= STAmount::cMaxNativeN);
+}
+
+//------------------------------------------------------------------------------
+//
+// Operators
+//
+//------------------------------------------------------------------------------
+
+bool operator== (STAmount const& lhs, STAmount const& rhs);
+bool operator!= (STAmount const& lhs, STAmount const& rhs);
+bool operator< (STAmount const& lhs, STAmount const& rhs);
+bool operator> (STAmount const& lhs, STAmount const& rhs);
+bool operator<= (STAmount const& lhs, STAmount const& rhs);
+bool operator>= (STAmount const& lhs, STAmount const& rhs);
+
+// native currency only
+bool operator< (STAmount const& lhs, std::uint64_t rhs);
+bool operator> (STAmount const& lhs, std::uint64_t rhs);
+bool operator<= (STAmount const& lhs, std::uint64_t rhs);
+bool operator>= (STAmount const& lhs, std::uint64_t rhs);
+
+STAmount operator+ (STAmount const& lhs, std::uint64_t rhs);
+STAmount operator- (STAmount const& lhs, std::uint64_t rhs);
+STAmount operator- (STAmount const& value);
+
+//------------------------------------------------------------------------------
+//
+// Arithmetic
+//
+//------------------------------------------------------------------------------
+
+STAmount
+divide (STAmount const& v1, STAmount const& v2, Issue const& issue);
+
+inline
+STAmount
+divide (STAmount const& v1, STAmount const& v2, STAmount const& saUnit)
+{
+ return divide (v1, v2, saUnit.issue());
+}
+
+inline
+STAmount
+divide (STAmount const& v1, STAmount const& v2)
+{
+ return divide (v1, v2, v1);
+}
+
+STAmount
+multiply (STAmount const& v1, STAmount const& v2, Issue const& issue);
+
+inline
+STAmount
+multiply (STAmount const& v1, STAmount const& v2, STAmount const& saUnit)
+{
+ return multiply (v1, v2, saUnit.issue());
+}
+
+inline
+STAmount
+multiply (STAmount const& v1, STAmount const& v2)
+{
+ return multiply (v1, v2, v1);
+}
+
+void
+canonicalizeRound (bool native, std::uint64_t& mantissa,
+ int& exponent, bool roundUp);
+
+/* addRound, subRound can end up rounding if the amount subtracted is too small
+ to make a change. Consder (X-d) where d is very small relative to X.
+ If you ask to round down, then (X-d) should not be X unless d is zero.
+ If you ask to round up, (X+d) should never be X unless d is zero. (Assuming X and d are positive).
+*/
+// Add, subtract, multiply, or divide rounding result in specified direction
+STAmount
+addRound (STAmount const& v1, STAmount const& v2, bool roundUp);
+
+STAmount
+subRound (STAmount const& v1, STAmount const& v2, bool roundUp);
+
+STAmount
+mulRound (STAmount const& v1, STAmount const& v2,
+ Issue const& issue, bool roundUp);
+
+inline
+STAmount
+mulRound (STAmount const& v1, STAmount const& v2,
+ STAmount const& saUnit, bool roundUp)
+{
+ return mulRound (v1, v2, saUnit.issue(), roundUp);
+}
+
+inline
+STAmount
+mulRound (STAmount const& v1, STAmount const& v2, bool roundUp)
+{
+ return mulRound (v1, v2, v1.issue(), roundUp);
+}
+
+STAmount
+divRound (STAmount const& v1, STAmount const& v2,
+ Issue const& issue, bool roundUp);
+
+inline
+STAmount
+divRound (STAmount const& v1, STAmount const& v2,
+ STAmount const& saUnit, bool roundUp)
+{
+ return divRound (v1, v2, saUnit.issue(), roundUp);
+}
+
+inline
+STAmount
+divRound (STAmount const& v1, STAmount const& v2, bool roundUp)
+{
+ return divRound (v1, v2, v1.issue(), roundUp);
+}
+
+// Someone is offering X for Y, what is the rate?
+// Rate: smaller is better, the taker wants the most out: in/out
+// VFALCO TODO Return a Quality object
+std::uint64_t
+getRate (STAmount const& offerOut, STAmount const& offerIn);
+
+//------------------------------------------------------------------------------
+
inline bool isXRP(STAmount const& amount)
{
return isXRP (amount.issue().currency);
diff --git a/src/ripple/module/data/protocol/STAmount.test.cpp b/src/ripple/module/data/protocol/STAmount.test.cpp
new file mode 100644
index 0000000000..2af9e53a2b
--- /dev/null
+++ b/src/ripple/module/data/protocol/STAmount.test.cpp
@@ -0,0 +1,528 @@
+//------------------------------------------------------------------------------
+/*
+ This file is part of rippled: https://github.com/ripple/rippled
+ Copyright (c) 2012, 2013 Ripple Labs Inc.
+
+ Permission to use, copy, modify, and/or distribute this software for any
+ purpose with or without fee is hereby granted, provided that the above
+ copyright notice and this permission notice appear in all copies.
+
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+//==============================================================================
+
+#include
+#include
+
+namespace ripple {
+
+class STAmount_test : public beast::unit_test::suite
+{
+public:
+ static STAmount serializeAndDeserialize (STAmount const& s)
+ {
+ Serializer ser;
+ s.add (ser);
+
+ SerializerIterator sit (ser);
+ return STAmount::deserialize (sit);
+ }
+
+ //--------------------------------------------------------------------------
+
+ bool roundTest (int n, int d, int m)
+ {
+ // check STAmount rounding
+ STAmount num (noIssue(), n);
+ STAmount den (noIssue(), d);
+ STAmount mul (noIssue(), m);
+ STAmount quot = divide (n, d, noIssue());
+ STAmount res = multiply (quot, mul, noIssue());
+
+ expect (! res.isNative (), "Product should not be native");
+
+ res.roundSelf ();
+
+ STAmount cmp (noIssue(), (n * m) / d);
+
+ expect (! cmp.isNative (), "Comparison amount should not be native");
+
+ if (res != cmp)
+ {
+ cmp.throwComparable (res);
+
+ WriteLog (lsWARNING, STAmount) << "(" << num.getText () << "/" << den.getText () << ") X " << mul.getText () << " = "
+ << res.getText () << " not " << cmp.getText ();
+
+ fail ("Rounding");
+
+ return false;
+ }
+ else
+ {
+ pass ();
+ }
+
+ return true;
+ }
+
+ void mulTest (int a, int b)
+ {
+ STAmount aa (noIssue(), a);
+ STAmount bb (noIssue(), b);
+ STAmount prod1 (multiply (aa, bb, noIssue()));
+
+ expect (! prod1.isNative ());
+
+ STAmount prod2 (noIssue(), static_cast (a) * static_cast (b));
+
+ if (prod1 != prod2)
+ {
+ WriteLog (lsWARNING, STAmount) << "nn(" << aa.getFullText () << " * " << bb.getFullText () << ") = " << prod1.getFullText ()
+ << " not " << prod2.getFullText ();
+
+ fail ("Multiplication result is not exact");
+ }
+ else
+ {
+ pass ();
+ }
+
+ aa = a;
+ prod1 = multiply (aa, bb, noIssue());
+
+ if (prod1 != prod2)
+ {
+ WriteLog (lsWARNING, STAmount) << "n(" << aa.getFullText () << " * " << bb.getFullText () << ") = " << prod1.getFullText ()
+ << " not " << prod2.getFullText ();
+ fail ("Multiplication result is not exact");
+ }
+ else
+ {
+ pass ();
+ }
+ }
+
+ //--------------------------------------------------------------------------
+
+ void testSetValue ()
+ {
+ testcase ("set value");
+
+ STAmount saTmp;
+
+ #if 0
+ // Check native floats
+ saTmp.setFullValue ("1^0");
+ BOOST_CHECK_MESSAGE (SYSTEM_CURRENCY_PARTS == saTmp.getNValue (), "float integer failed");
+ saTmp.setFullValue ("0^1");
+ BOOST_CHECK_MESSAGE (SYSTEM_CURRENCY_PARTS / 10 == saTmp.getNValue (), "float fraction failed");
+ saTmp.setFullValue ("0^12");
+ BOOST_CHECK_MESSAGE (12 * SYSTEM_CURRENCY_PARTS / 100 == saTmp.getNValue (), "float fraction failed");
+ saTmp.setFullValue ("1^2");
+ BOOST_CHECK_MESSAGE (SYSTEM_CURRENCY_PARTS + (2 * SYSTEM_CURRENCY_PARTS / 10) == saTmp.getNValue (), "float combined failed");
+ #endif
+
+ // Check native integer
+ saTmp.setFullValue ("1");
+ expect (1 == saTmp.getNValue (), "should be equal");
+ }
+
+ //--------------------------------------------------------------------------
+
+ void testNativeCurrency ()
+ {
+ testcase ("native currency");
+ STAmount zeroSt, one (1), hundred (100);
+ // VFALCO NOTE Why repeat "STAmount fail" so many times??
+ unexpected (serializeAndDeserialize (zeroSt) != zeroSt, "STAmount fail");
+ unexpected (serializeAndDeserialize (one) != one, "STAmount fail");
+ unexpected (serializeAndDeserialize (hundred) != hundred, "STAmount fail");
+ unexpected (!zeroSt.isNative (), "STAmount fail");
+ unexpected (!hundred.isNative (), "STAmount fail");
+ unexpected (zeroSt != zero, "STAmount fail");
+ unexpected (one == zero, "STAmount fail");
+ unexpected (hundred == zero, "STAmount fail");
+ unexpected ((zeroSt < zeroSt), "STAmount fail");
+ unexpected (! (zeroSt < one), "STAmount fail");
+ unexpected (! (zeroSt < hundred), "STAmount fail");
+ unexpected ((one < zeroSt), "STAmount fail");
+ unexpected ((one < one), "STAmount fail");
+ unexpected (! (one < hundred), "STAmount fail");
+ unexpected ((hundred < zeroSt), "STAmount fail");
+ unexpected ((hundred < one), "STAmount fail");
+ unexpected ((hundred < hundred), "STAmount fail");
+ unexpected ((zeroSt > zeroSt), "STAmount fail");
+ unexpected ((zeroSt > one), "STAmount fail");
+ unexpected ((zeroSt > hundred), "STAmount fail");
+ unexpected (! (one > zeroSt), "STAmount fail");
+ unexpected ((one > one), "STAmount fail");
+ unexpected ((one > hundred), "STAmount fail");
+ unexpected (! (hundred > zeroSt), "STAmount fail");
+ unexpected (! (hundred > one), "STAmount fail");
+ unexpected ((hundred > hundred), "STAmount fail");
+ unexpected (! (zeroSt <= zeroSt), "STAmount fail");
+ unexpected (! (zeroSt <= one), "STAmount fail");
+ unexpected (! (zeroSt <= hundred), "STAmount fail");
+ unexpected ((one <= zeroSt), "STAmount fail");
+ unexpected (! (one <= one), "STAmount fail");
+ unexpected (! (one <= hundred), "STAmount fail");
+ unexpected ((hundred <= zeroSt), "STAmount fail");
+ unexpected ((hundred <= one), "STAmount fail");
+ unexpected (! (hundred <= hundred), "STAmount fail");
+ unexpected (! (zeroSt >= zeroSt), "STAmount fail");
+ unexpected ((zeroSt >= one), "STAmount fail");
+ unexpected ((zeroSt >= hundred), "STAmount fail");
+ unexpected (! (one >= zeroSt), "STAmount fail");
+ unexpected (! (one >= one), "STAmount fail");
+ unexpected ((one >= hundred), "STAmount fail");
+ unexpected (! (hundred >= zeroSt), "STAmount fail");
+ unexpected (! (hundred >= one), "STAmount fail");
+ unexpected (! (hundred >= hundred), "STAmount fail");
+ unexpected (! (zeroSt == zeroSt), "STAmount fail");
+ unexpected ((zeroSt == one), "STAmount fail");
+ unexpected ((zeroSt == hundred), "STAmount fail");
+ unexpected ((one == zeroSt), "STAmount fail");
+ unexpected (! (one == one), "STAmount fail");
+ unexpected ((one == hundred), "STAmount fail");
+ unexpected ((hundred == zeroSt), "STAmount fail");
+ unexpected ((hundred == one), "STAmount fail");
+ unexpected (! (hundred == hundred), "STAmount fail");
+ unexpected ((zeroSt != zeroSt), "STAmount fail");
+ unexpected (! (zeroSt != one), "STAmount fail");
+ unexpected (! (zeroSt != hundred), "STAmount fail");
+ unexpected (! (one != zeroSt), "STAmount fail");
+ unexpected ((one != one), "STAmount fail");
+ unexpected (! (one != hundred), "STAmount fail");
+ unexpected (! (hundred != zeroSt), "STAmount fail");
+ unexpected (! (hundred != one), "STAmount fail");
+ unexpected ((hundred != hundred), "STAmount fail");
+ unexpected (STAmount ().getText () != "0", "STAmount fail");
+ unexpected (STAmount (31).getText () != "31", "STAmount fail");
+ unexpected (STAmount (310).getText () != "310", "STAmount fail");
+ unexpected (to_string (Currency ()) != "XRP", "cHC(XRP)");
+ Currency c;
+ unexpected (!to_currency (c, "USD"), "create USD currency");
+ unexpected (to_string (c) != "USD", "check USD currency");
+
+ const std::string cur = "015841551A748AD2C1F76FF6ECB0CCCD00000000";
+ unexpected (!to_currency (c, cur), "create custom currency");
+ unexpected (to_string (c) != cur, "check custom currency");
+ unexpected (c != Currency (cur), "check custom currency");
+ }
+
+ //--------------------------------------------------------------------------
+
+ void testCustomCurrency ()
+ {
+ testcase ("custom currency");
+ STAmount zeroSt (noIssue()), one (noIssue(), 1), hundred (noIssue(), 100);
+ unexpected (serializeAndDeserialize (zeroSt) != zeroSt, "STAmount fail");
+ unexpected (serializeAndDeserialize (one) != one, "STAmount fail");
+ unexpected (serializeAndDeserialize (hundred) != hundred, "STAmount fail");
+ unexpected (zeroSt.isNative (), "STAmount fail");
+ unexpected (hundred.isNative (), "STAmount fail");
+ unexpected (zeroSt != zero, "STAmount fail");
+ unexpected (one == zero, "STAmount fail");
+ unexpected (hundred == zero, "STAmount fail");
+ unexpected ((zeroSt < zeroSt), "STAmount fail");
+ unexpected (! (zeroSt < one), "STAmount fail");
+ unexpected (! (zeroSt < hundred), "STAmount fail");
+ unexpected ((one < zeroSt), "STAmount fail");
+ unexpected ((one < one), "STAmount fail");
+ unexpected (! (one < hundred), "STAmount fail");
+ unexpected ((hundred < zeroSt), "STAmount fail");
+ unexpected ((hundred < one), "STAmount fail");
+ unexpected ((hundred < hundred), "STAmount fail");
+ unexpected ((zeroSt > zeroSt), "STAmount fail");
+ unexpected ((zeroSt > one), "STAmount fail");
+ unexpected ((zeroSt > hundred), "STAmount fail");
+ unexpected (! (one > zeroSt), "STAmount fail");
+ unexpected ((one > one), "STAmount fail");
+ unexpected ((one > hundred), "STAmount fail");
+ unexpected (! (hundred > zeroSt), "STAmount fail");
+ unexpected (! (hundred > one), "STAmount fail");
+ unexpected ((hundred > hundred), "STAmount fail");
+ unexpected (! (zeroSt <= zeroSt), "STAmount fail");
+ unexpected (! (zeroSt <= one), "STAmount fail");
+ unexpected (! (zeroSt <= hundred), "STAmount fail");
+ unexpected ((one <= zeroSt), "STAmount fail");
+ unexpected (! (one <= one), "STAmount fail");
+ unexpected (! (one <= hundred), "STAmount fail");
+ unexpected ((hundred <= zeroSt), "STAmount fail");
+ unexpected ((hundred <= one), "STAmount fail");
+ unexpected (! (hundred <= hundred), "STAmount fail");
+ unexpected (! (zeroSt >= zeroSt), "STAmount fail");
+ unexpected ((zeroSt >= one), "STAmount fail");
+ unexpected ((zeroSt >= hundred), "STAmount fail");
+ unexpected (! (one >= zeroSt), "STAmount fail");
+ unexpected (! (one >= one), "STAmount fail");
+ unexpected ((one >= hundred), "STAmount fail");
+ unexpected (! (hundred >= zeroSt), "STAmount fail");
+ unexpected (! (hundred >= one), "STAmount fail");
+ unexpected (! (hundred >= hundred), "STAmount fail");
+ unexpected (! (zeroSt == zeroSt), "STAmount fail");
+ unexpected ((zeroSt == one), "STAmount fail");
+ unexpected ((zeroSt == hundred), "STAmount fail");
+ unexpected ((one == zeroSt), "STAmount fail");
+ unexpected (! (one == one), "STAmount fail");
+ unexpected ((one == hundred), "STAmount fail");
+ unexpected ((hundred == zeroSt), "STAmount fail");
+ unexpected ((hundred == one), "STAmount fail");
+ unexpected (! (hundred == hundred), "STAmount fail");
+ unexpected ((zeroSt != zeroSt), "STAmount fail");
+ unexpected (! (zeroSt != one), "STAmount fail");
+ unexpected (! (zeroSt != hundred), "STAmount fail");
+ unexpected (! (one != zeroSt), "STAmount fail");
+ unexpected ((one != one), "STAmount fail");
+ unexpected (! (one != hundred), "STAmount fail");
+ unexpected (! (hundred != zeroSt), "STAmount fail");
+ unexpected (! (hundred != one), "STAmount fail");
+ unexpected ((hundred != hundred), "STAmount fail");
+ unexpected (STAmount (noIssue()).getText () != "0", "STAmount fail");
+ unexpected (STAmount (noIssue(), 31).getText () != "31", "STAmount fail");
+ unexpected (STAmount (noIssue(), 31, 1).getText () != "310", "STAmount fail");
+ unexpected (STAmount (noIssue(), 31, -1).getText () != "3.1", "STAmount fail");
+ unexpected (STAmount (noIssue(), 31, -2).getText () != "0.31", "STAmount fail");
+ unexpected (multiply (STAmount (noIssue(), 20), STAmount (3), noIssue()).getText () != "60",
+ "STAmount multiply fail 1");
+ unexpected (multiply (STAmount (noIssue(), 20), STAmount (3), xrpIssue ()).getText () != "60",
+ "STAmount multiply fail 2");
+ unexpected (multiply (STAmount (20), STAmount (3), noIssue()).getText () != "60",
+ "STAmount multiply fail 3");
+ unexpected (multiply (STAmount (20), STAmount (3), xrpIssue ()).getText () != "60",
+ "STAmount multiply fail 4");
+
+ if (divide (STAmount (noIssue(), 60), STAmount (3), noIssue()).getText () != "20")
+ {
+ WriteLog (lsFATAL, STAmount) << "60/3 = " <<
+ divide (STAmount (noIssue(), 60),
+ STAmount (3), noIssue()).getText ();
+ fail ("STAmount divide fail");
+ }
+ else
+ {
+ pass ();
+ }
+
+ unexpected (divide (STAmount (noIssue(), 60), STAmount (3), xrpIssue ()).getText () != "20",
+ "STAmount divide fail");
+
+ unexpected (divide (STAmount (noIssue(), 60), STAmount (noIssue(), 3), noIssue()).getText () != "20",
+ "STAmount divide fail");
+
+ unexpected (divide (STAmount (noIssue(), 60), STAmount (noIssue(), 3), xrpIssue ()).getText () != "20",
+ "STAmount divide fail");
+
+ STAmount a1 (noIssue(), 60), a2 (noIssue(), 10, -1);
+
+ unexpected (divide (a2, a1, noIssue()) != amountFromQuality (getRate (a1, a2)),
+ "STAmount setRate(getRate) fail");
+
+ unexpected (divide (a1, a2, noIssue()) != amountFromQuality (getRate (a2, a1)),
+ "STAmount setRate(getRate) fail");
+ }
+
+ //--------------------------------------------------------------------------
+
+ void testArithmetic ()
+ {
+ testcase ("arithmetic");
+
+ CBigNum b;
+
+ for (int i = 0; i < 16; ++i)
+ {
+ std::uint64_t r = rand ();
+ r <<= 32;
+ r |= rand ();
+ b.setuint64 (r);
+
+ if (b.getuint64 () != r)
+ {
+ WriteLog (lsFATAL, STAmount) << r << " != " << b.getuint64 () << " " << b.ToString (16);
+ fail ("setull64/getull64 failure");
+ }
+ else
+ {
+ pass ();
+ }
+ }
+
+ // Test currency multiplication and division operations such as
+ // convertToDisplayAmount, convertToInternalAmount, getRate, getClaimed, and getNeeded
+
+ unexpected (getRate (STAmount (1), STAmount (10)) != (((100ull - 14) << (64 - 8)) | 1000000000000000ull),
+ "STAmount getRate fail 1");
+
+ unexpected (getRate (STAmount (10), STAmount (1)) != (((100ull - 16) << (64 - 8)) | 1000000000000000ull),
+ "STAmount getRate fail 2");
+
+ unexpected (getRate (STAmount (noIssue(), 1), STAmount (noIssue(), 10)) != (((100ull - 14) << (64 - 8)) | 1000000000000000ull),
+ "STAmount getRate fail 3");
+
+ unexpected (getRate (STAmount (noIssue(), 10), STAmount (noIssue(), 1)) != (((100ull - 16) << (64 - 8)) | 1000000000000000ull),
+ "STAmount getRate fail 4");
+
+ unexpected (getRate (STAmount (noIssue(), 1), STAmount (10)) != (((100ull - 14) << (64 - 8)) | 1000000000000000ull),
+ "STAmount getRate fail 5");
+
+ unexpected (getRate (STAmount (noIssue(), 10), STAmount (1)) != (((100ull - 16) << (64 - 8)) | 1000000000000000ull),
+ "STAmount getRate fail 6");
+
+ unexpected (getRate (STAmount (1), STAmount (noIssue(), 10)) != (((100ull - 14) << (64 - 8)) | 1000000000000000ull),
+ "STAmount getRate fail 7");
+
+ unexpected (getRate (STAmount (10), STAmount (noIssue(), 1)) != (((100ull - 16) << (64 - 8)) | 1000000000000000ull),
+ "STAmount getRate fail 8");
+
+ roundTest (1, 3, 3);
+ roundTest (2, 3, 9);
+ roundTest (1, 7, 21);
+ roundTest (1, 2, 4);
+ roundTest (3, 9, 18);
+ roundTest (7, 11, 44);
+
+ for (int i = 0; i <= 100000; ++i)
+ mulTest (rand () % 10000000, rand () % 10000000);
+ }
+
+ //--------------------------------------------------------------------------
+
+ template
+ bool
+ expect (Cond cond, beast::String const& s)
+ {
+ return suite::expect (cond, s.toStdString());
+ }
+
+ template
+ bool
+ expect (Cond cond)
+ {
+ return suite::expect (cond);
+ }
+
+ void testUnderflow ()
+ {
+ testcase ("underflow");
+
+ STAmount bigNative (STAmount::cMaxNative / 2);
+ STAmount bigValue (noIssue(),
+ (STAmount::cMinValue + STAmount::cMaxValue) / 2, STAmount::cMaxOffset - 1);
+ STAmount smallValue (noIssue(),
+ (STAmount::cMinValue + STAmount::cMaxValue) / 2, STAmount::cMinOffset + 1);
+ STAmount zeroSt (noIssue(), 0);
+
+ STAmount smallXsmall = multiply (smallValue, smallValue, noIssue());
+
+ expect (smallXsmall == zero, "smallXsmall != 0");
+
+ STAmount bigDsmall = divide (smallValue, bigValue, noIssue());
+
+ expect (bigDsmall == zero, beast::String ("small/big != 0: ") + bigDsmall.getText ());
+
+#if 0
+ // TODO(tom): this test makes no sense - we should have no way to have
+ // the currency not be XRP while the account is XRP.
+ bigDsmall = divide (smallValue, bigNative, noCurrency(), xrpAccount ());
+#endif
+
+ expect (bigDsmall == zero, beast::String ("small/bigNative != 0: ") + bigDsmall.getText ());
+
+ bigDsmall = divide (smallValue, bigValue, xrpIssue ());
+
+ expect (bigDsmall == zero, beast::String ("(small/big)->N != 0: ") + bigDsmall.getText ());
+
+ bigDsmall = divide (smallValue, bigNative, xrpIssue ());
+
+ expect (bigDsmall == zero, beast::String ("(small/bigNative)->N != 0: ") + bigDsmall.getText ());
+
+ // very bad offer
+ std::uint64_t r = getRate (smallValue, bigValue);
+
+ expect (r == 0, "getRate(smallOut/bigIn) != 0");
+
+ // very good offer
+ r = getRate (bigValue, smallValue);
+
+ expect (r == 0, "getRate(smallIn/bigOUt) != 0");
+ }
+
+ //--------------------------------------------------------------------------
+
+ void testRounding ()
+ {
+ // VFALCO TODO There are no actual tests here, just printed output?
+ // Change this to actually do something.
+
+#if 0
+ beginTestCase ("rounding ");
+
+ std::uint64_t value = 25000000000000000ull;
+ int offset = -14;
+ canonicalizeRound (false, value, offset, true);
+
+ STAmount one (noIssue(), 1);
+ STAmount two (noIssue(), 2);
+ STAmount three (noIssue(), 3);
+
+ STAmount oneThird1 = divRound (one, three, noIssue(), false);
+ STAmount oneThird2 = divide (one, three, noIssue());
+ STAmount oneThird3 = divRound (one, three, noIssue(), true);
+ WriteLog (lsINFO, STAmount) << oneThird1;
+ WriteLog (lsINFO, STAmount) << oneThird2;
+ WriteLog (lsINFO, STAmount) << oneThird3;
+
+ STAmount twoThird1 = divRound (two, three, noIssue(), false);
+ STAmount twoThird2 = divide (two, three, noIssue());
+ STAmount twoThird3 = divRound (two, three, noIssue(), true);
+ WriteLog (lsINFO, STAmount) << twoThird1;
+ WriteLog (lsINFO, STAmount) << twoThird2;
+ WriteLog (lsINFO, STAmount) << twoThird3;
+
+ STAmount oneA = mulRound (oneThird1, three, noIssue(), false);
+ STAmount oneB = multiply (oneThird2, three, noIssue());
+ STAmount oneC = mulRound (oneThird3, three, noIssue(), true);
+ WriteLog (lsINFO, STAmount) << oneA;
+ WriteLog (lsINFO, STAmount) << oneB;
+ WriteLog (lsINFO, STAmount) << oneC;
+
+ STAmount fourThirdsA = addRound (twoThird2, twoThird2, false);
+ STAmount fourThirdsB = twoThird2 + twoThird2;
+ STAmount fourThirdsC = addRound (twoThird2, twoThird2, true);
+ WriteLog (lsINFO, STAmount) << fourThirdsA;
+ WriteLog (lsINFO, STAmount) << fourThirdsB;
+ WriteLog (lsINFO, STAmount) << fourThirdsC;
+
+ STAmount dripTest1 = mulRound (twoThird2, two, xrpIssue (), false);
+ STAmount dripTest2 = multiply (twoThird2, two, xrpIssue ());
+ STAmount dripTest3 = mulRound (twoThird2, two, xrpIssue (), true);
+ WriteLog (lsINFO, STAmount) << dripTest1;
+ WriteLog (lsINFO, STAmount) << dripTest2;
+ WriteLog (lsINFO, STAmount) << dripTest3;
+#endif
+ }
+
+ //--------------------------------------------------------------------------
+
+ void run ()
+ {
+ testSetValue ();
+ testNativeCurrency ();
+ testCustomCurrency ();
+ testArithmetic ();
+ testUnderflow ();
+ testRounding ();
+ }
+};
+
+BEAST_DEFINE_TESTSUITE(STAmount,ripple_data,ripple);
+
+} // ripple
diff --git a/src/ripple/module/data/protocol/STAmountRound.cpp b/src/ripple/module/data/protocol/STAmountRound.cpp
deleted file mode 100644
index 97c8dc28c7..0000000000
--- a/src/ripple/module/data/protocol/STAmountRound.cpp
+++ /dev/null
@@ -1,329 +0,0 @@
-//------------------------------------------------------------------------------
-/*
- This file is part of rippled: https://github.com/ripple/rippled
- Copyright (c) 2012, 2013 Ripple Labs Inc.
-
- Permission to use, copy, modify, and/or distribute this software for any
- purpose with or without fee is hereby granted, provided that the above
- copyright notice and this permission notice appear in all copies.
-
- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-*/
-//==============================================================================
-
-namespace ripple {
-
-void STAmount::canonicalizeRound (
- bool isNative, std::uint64_t& value, int& offset, bool roundUp)
-{
- if (!roundUp) // canonicalize already rounds down
- return;
-
- WriteLog (lsTRACE, STAmount)
- << "canonicalizeRound< " << value << ":" << offset;
-
- if (isNative)
- {
- if (offset < 0)
- {
- int loops = 0;
-
- while (offset < -1)
- {
- value /= 10;
- ++offset;
- ++loops;
- }
-
- value += (loops >= 2) ? 9 : 10; // add before last divide
- value /= 10;
- ++offset;
- }
- }
- else if (value > STAmount::cMaxValue)
- {
- while (value > (10 * STAmount::cMaxValue))
- {
- value /= 10;
- ++offset;
- }
-
- value += 9; // add before last divide
- value /= 10;
- ++offset;
- }
-
- WriteLog (lsTRACE, STAmount)
- << "canonicalizeRound> " << value << ":" << offset;
-}
-
-STAmount STAmount::addRound (STAmount const& v1, STAmount const& v2, bool roundUp)
-{
- v1.throwComparable (v2);
-
- if (v2.mValue == 0)
- return v1;
-
- if (v1.mValue == 0)
- return STAmount (v1.getFName (), v1.mIssue, v2.mValue,
- v2.mOffset, v2.mIsNegative);
-
- if (v1.mIsNative)
- return STAmount (v1.getFName (), v1.getSNValue () + v2.getSNValue ());
-
- int ov1 = v1.mOffset, ov2 = v2.mOffset;
- auto vv1 = static_cast (v1.mValue);
- auto vv2 = static_cast (v2.mValue);
-
- if (v1.mIsNegative)
- vv1 = -vv1;
-
- if (v2.mIsNegative)
- vv2 = -vv2;
-
- if (ov1 < ov2)
- {
- while (ov1 < (ov2 - 1))
- {
- vv1 /= 10;
- ++ov1;
- }
-
- if (roundUp)
- vv1 += 9;
-
- vv1 /= 10;
- ++ov1;
- }
-
- if (ov2 < ov1)
- {
- while (ov2 < (ov1 - 1))
- {
- vv2 /= 10;
- ++ov2;
- }
-
- if (roundUp)
- vv2 += 9;
-
- vv2 /= 10;
- ++ov2;
- }
-
- std::int64_t fv = vv1 + vv2;
-
- if ((fv >= -10) && (fv <= 10))
- return STAmount (v1.getFName (), v1.mIssue);
- else if (fv >= 0)
- {
- std::uint64_t v = static_cast (fv);
- canonicalizeRound (false, v, ov1, roundUp);
- return STAmount (v1.getFName (), v1.mIssue, v, ov1, false);
- }
- else
- {
- std::uint64_t v = static_cast (-fv);
- canonicalizeRound (false, v, ov1, !roundUp);
- return STAmount (v1.getFName (), v1.mIssue, v, ov1, true);
- }
-}
-
-STAmount STAmount::subRound (STAmount const& v1, STAmount const& v2, bool roundUp)
-{
- v1.throwComparable (v2);
-
- if (v2.mValue == 0)
- return v1;
-
- if (v1.mValue == 0)
- return STAmount (v1.getFName (), v1.mIssue, v2.mValue,
- v2.mOffset, !v2.mIsNegative);
-
- if (v1.mIsNative)
- return STAmount (v1.getFName (), v1.getSNValue () - v2.getSNValue ());
-
- int ov1 = v1.mOffset, ov2 = v2.mOffset;
- auto vv1 = static_cast (v1.mValue);
- auto vv2 = static_cast (v2.mValue);
-
- if (v1.mIsNegative)
- vv1 = -vv1;
-
- if (!v2.mIsNegative)
- vv2 = -vv2;
-
- if (ov1 < ov2)
- {
- while (ov1 < (ov2 - 1))
- {
- vv1 /= 10;
- ++ov1;
- }
-
- if (roundUp)
- vv1 += 9;
-
- vv1 /= 10;
- ++ov1;
- }
-
- if (ov2 < ov1)
- {
- while (ov2 < (ov1 - 1))
- {
- vv2 /= 10;
- ++ov2;
- }
-
- if (roundUp)
- vv2 += 9;
-
- vv2 /= 10;
- ++ov2;
- }
-
- std::int64_t fv = vv1 + vv2;
-
- if ((fv >= -10) && (fv <= 10))
- return STAmount (v1.getFName (), v1.mIssue);
-
- if (fv >= 0)
- {
- std::uint64_t v = static_cast (fv);
- canonicalizeRound (false, v, ov1, roundUp);
- return STAmount (v1.getFName (), v1.mIssue, v, ov1, false);
- }
- else
- {
- std::uint64_t v = static_cast (-fv);
- canonicalizeRound (false, v, ov1, !roundUp);
- return STAmount (v1.getFName (), v1.mIssue, v, ov1, true);
- }
-}
-
-STAmount STAmount::mulRound (
- STAmount const& v1, STAmount const& v2, Issue const& issue, bool roundUp)
-{
- if (v1 == zero || v2 == zero)
- return {issue};
-
- if (v1.mIsNative && v2.mIsNative && isXRP (issue))
- {
- std::uint64_t minV = (v1.getSNValue () < v2.getSNValue ()) ?
- v1.getSNValue () : v2.getSNValue ();
- std::uint64_t maxV = (v1.getSNValue () < v2.getSNValue ()) ?
- v2.getSNValue () : v1.getSNValue ();
-
- if (minV > 3000000000ull) // sqrt(cMaxNative)
- throw std::runtime_error ("Native value overflow");
-
- if (((maxV >> 32) * minV) > 2095475792ull) // cMaxNative / 2^32
- throw std::runtime_error ("Native value overflow");
-
- return STAmount (v1.getFName (), minV * maxV);
- }
-
- std::uint64_t value1 = v1.mValue, value2 = v2.mValue;
- int offset1 = v1.mOffset, offset2 = v2.mOffset;
-
- if (v1.mIsNative)
- {
- while (value1 < STAmount::cMinValue)
- {
- value1 *= 10;
- --offset1;
- }
- }
-
- if (v2.mIsNative)
- {
- while (value2 < STAmount::cMinValue)
- {
- value2 *= 10;
- --offset2;
- }
- }
-
- bool resultNegative = v1.mIsNegative != v2.mIsNegative;
- // Compute (numerator * denominator) / 10^14 with rounding
- // 10^16 <= result <= 10^18
- CBigNum v;
-
- if ((BN_add_word64 (&v, value1) != 1) || (BN_mul_word64 (&v, value2) != 1))
- throw std::runtime_error ("internal bn error");
-
- if (resultNegative != roundUp) // rounding down is automatic when we divide
- BN_add_word64 (&v, tenTo14m1);
-
- if (BN_div_word64 (&v, tenTo14) == ((std::uint64_t) - 1))
- throw std::runtime_error ("internal bn error");
-
- // 10^16 <= product <= 10^18
- assert (BN_num_bytes (&v) <= 64);
-
- std::uint64_t amount = v.getuint64 ();
- int offset = offset1 + offset2 + 14;
- canonicalizeRound (
- isXRP (issue), amount, offset, resultNegative != roundUp);
- return STAmount (issue, amount, offset, resultNegative);
-}
-
-STAmount STAmount::divRound (
- STAmount const& num, STAmount const& den,
- Issue const& issue, bool roundUp)
-{
- if (den == zero)
- throw std::runtime_error ("division by zero");
-
- if (num == zero)
- return {issue};
-
- std::uint64_t numVal = num.mValue, denVal = den.mValue;
- int numOffset = num.mOffset, denOffset = den.mOffset;
-
- if (num.mIsNative)
- while (numVal < STAmount::cMinValue)
- {
- // Need to bring into range
- numVal *= 10;
- --numOffset;
- }
-
- if (den.mIsNative)
- while (denVal < STAmount::cMinValue)
- {
- denVal *= 10;
- --denOffset;
- }
-
- bool resultNegative = num.mIsNegative != den.mIsNegative;
- // Compute (numerator * 10^17) / denominator
- CBigNum v;
-
- if ((BN_add_word64 (&v, numVal) != 1) || (BN_mul_word64 (&v, tenTo17) != 1))
- throw std::runtime_error ("internal bn error");
-
- if (resultNegative != roundUp) // Rounding down is automatic when we divide
- BN_add_word64 (&v, denVal - 1);
-
- if (BN_div_word64 (&v, denVal) == ((std::uint64_t) - 1))
- throw std::runtime_error ("internal bn error");
-
- // 10^16 <= quotient <= 10^18
- assert (BN_num_bytes (&v) <= 64);
-
- std::uint64_t amount = v.getuint64 ();
- int offset = numOffset - denOffset - 17;
- canonicalizeRound (
- isXRP (issue), amount, offset, resultNegative != roundUp);
- return STAmount (issue, amount, offset, resultNegative);
-}
-
-} // ripple
diff --git a/src/ripple/module/data/protocol/STParsedJSON.cpp b/src/ripple/module/data/protocol/STParsedJSON.cpp
index 8f0e9ed5e3..60113e4718 100644
--- a/src/ripple/module/data/protocol/STParsedJSON.cpp
+++ b/src/ripple/module/data/protocol/STParsedJSON.cpp
@@ -416,7 +416,7 @@ bool STParsedJSON::parse (std::string const& json_name,
case STI_AMOUNT:
try
{
- data.push_back (new STAmount (field, value));
+ data.push_back (new STAmount (amountFromJson (field, value)));
}
catch (...)
{
diff --git a/src/ripple/module/data/protocol/SerializedType.h b/src/ripple/module/data/protocol/SerializedType.h
index bdf07914b2..cc709f8da8 100644
--- a/src/ripple/module/data/protocol/SerializedType.h
+++ b/src/ripple/module/data/protocol/SerializedType.h
@@ -83,14 +83,78 @@ public:
assert (fName);
}
- virtual ~SerializedType () { }
+ virtual ~SerializedType () = default;
- static std::unique_ptr deserialize (SField::ref name)
+ //
+ // overridables
+ //
+
+ virtual
+ SerializedTypeID
+ getSType () const
+ {
+ return STI_NOTPRESENT;
+ }
+
+ virtual
+ std::string
+ getFullText() const;
+
+ // just the value
+ virtual
+ std::string
+ getText() const
+ {
+ return std::string();
+ }
+
+ virtual
+ Json::Value getJson (int /*options*/) const
+ {
+ return getText();
+ }
+
+ virtual
+ void
+ add (Serializer& s) const
+ {
+ // VFALCO Why not just make this pure virtual?
+ assert (false);
+ }
+
+ virtual
+ bool
+ isEquivalent (SerializedType const& t) const;
+
+ virtual
+ bool
+ isDefault () const
+ {
+ return true;
+ }
+
+private:
+ // VFALCO TODO Return std::unique_ptr
+ virtual
+ SerializedType*
+ duplicate () const
+ {
+ return new SerializedType (*fName);
+ }
+
+public:
+ //
+ // members
+ //
+
+ static
+ std::unique_ptr
+ deserialize (SField::ref name)
{
return std::unique_ptr (new SerializedType (name));
}
- /** A SerializeType is a field.
+ /** A SerializedType is a field.
This sets the name.
*/
void setFName (SField::ref n)
@@ -102,31 +166,11 @@ public:
{
return *fName;
}
- virtual SerializedTypeID getSType () const
- {
- return STI_NOTPRESENT;
- }
std::unique_ptr clone () const
{
return std::unique_ptr (duplicate ());
}
- virtual std::string getFullText () const;
- virtual std::string getText () const // just the value
- {
- return std::string ();
- }
- virtual Json::Value getJson (int /*options*/) const
- {
- return getText ();
- }
-
- virtual void add (Serializer& s) const
- {
- assert (false);
- }
-
- virtual bool isEquivalent (const SerializedType& t) const;
void addFieldID (Serializer& s) const
{
@@ -145,11 +189,6 @@ public:
return (getSType () != t.getSType ()) || !isEquivalent (t);
}
- virtual bool isDefault () const
- {
- return true;
- }
-
template
D& downcast()
{
@@ -171,12 +210,6 @@ public:
protected:
// VFALCO TODO make accessors for this
SField::ptr fName;
-
-private:
- virtual SerializedType* duplicate () const
- {
- return new SerializedType (*fName);
- }
};
//------------------------------------------------------------------------------
diff --git a/src/ripple/module/data/protocol/SerializedTypes.cpp b/src/ripple/module/data/protocol/SerializedTypes.cpp
index 920d730096..ea423602d2 100644
--- a/src/ripple/module/data/protocol/SerializedTypes.cpp
+++ b/src/ripple/module/data/protocol/SerializedTypes.cpp
@@ -19,8 +19,8 @@
namespace ripple {
-const STAmount saZero (noIssue(), 0);
-const STAmount saOne (noIssue(), 1);
+const STAmount saZero (noIssue(), 0u);
+const STAmount saOne (noIssue(), 1u);
SerializedType& SerializedType::operator= (const SerializedType& t)
{
diff --git a/src/ripple/module/rpc/handlers/RipplePathFind.cpp b/src/ripple/module/rpc/handlers/RipplePathFind.cpp
index dcb6209383..8c19803eb2 100644
--- a/src/ripple/module/rpc/handlers/RipplePathFind.cpp
+++ b/src/ripple/module/rpc/handlers/RipplePathFind.cpp
@@ -72,7 +72,7 @@ Json::Value doRipplePathFind (RPC::Context& context)
else if (
// Parse saDstAmount.
!context.params_.isMember ("destination_amount")
- || !saDstAmount.bSetJson (context.params_["destination_amount"])
+ || ! amountFromJsonNoThrow(saDstAmount, context.params_["destination_amount"])
|| saDstAmount <= zero
|| (!isXRP(saDstAmount.getCurrency ())
&& (!saDstAmount.getIssuer () ||
diff --git a/src/ripple/module/rpc/impl/TransactionSign.cpp b/src/ripple/module/rpc/impl/TransactionSign.cpp
index 95596c899e..b9cc821039 100644
--- a/src/ripple/module/rpc/impl/TransactionSign.cpp
+++ b/src/ripple/module/rpc/impl/TransactionSign.cpp
@@ -105,7 +105,7 @@ static Json::Value signPayment(
STAmount amount;
- if (!amount.bSetJson (tx_json ["Amount"]))
+ if (! amountFromJsonNoThrow (amount, tx_json ["Amount"]))
return RPC::invalid_field_error ("tx_json.Amount");
if (!tx_json.isMember ("Destination"))
@@ -131,7 +131,7 @@ static Json::Value signPayment(
if (tx_json.isMember ("SendMax"))
{
- if (!saSendMax.bSetJson (tx_json ["SendMax"]))
+ if (! amountFromJsonNoThrow (saSendMax, tx_json ["SendMax"]))
return RPC::invalid_field_error ("tx_json.SendMax");
}
else
diff --git a/src/ripple/unity/data.cpp b/src/ripple/unity/data.cpp
index 010e661771..0857df2d37 100644
--- a/src/ripple/unity/data.cpp
+++ b/src/ripple/unity/data.cpp
@@ -79,13 +79,8 @@
#include
#include
#include
-
-// These are for STAmount
-static const std::uint64_t tenTo14 = 100000000000000ull;
-static const std::uint64_t tenTo14m1 = tenTo14 - 1;
-static const std::uint64_t tenTo17 = tenTo14 * 1000;
#include
-#include
+#include
#if BEAST_MSVC
#pragma warning (pop)