Refactor STAmount:

* Remove unused functions
* Remove unused constructor
* Use delegating constructors
* Mark some observers deprecated
* Clean up declaration parameter names
* Add checked and unchecked constructors
* De-inline unnecessary inlined functions
* Reorder and regroup members into sections
* Move globals from the unity file to the .cpp
* Change some member functions to be free functions
* Put implementation in one .cpp and the test in another .cpp

Remove unused STAmount constructor and delegate two others No change in functionality.
This commit is contained in:
Vinnie Falco
2014-09-14 19:00:54 -07:00
parent 8fb9d5daaa
commit 0f30191d10
31 changed files with 2149 additions and 2091 deletions

View File

@@ -2598,7 +2598,7 @@
</ClCompile>
<ClInclude Include="..\..\src\ripple\module\data\protocol\STAmount.h">
</ClInclude>
<ClCompile Include="..\..\src\ripple\module\data\protocol\STAmountRound.cpp">
<ClCompile Include="..\..\src\ripple\module\data\protocol\STAmount.test.cpp">
<ExcludedFromBuild>True</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\src\ripple\module\data\protocol\STArray.cpp">

View File

@@ -3738,7 +3738,7 @@
<ClInclude Include="..\..\src\ripple\module\data\protocol\STAmount.h">
<Filter>ripple\module\data\protocol</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ripple\module\data\protocol\STAmountRound.cpp">
<ClCompile Include="..\..\src\ripple\module\data\protocol\STAmount.test.cpp">
<Filter>ripple\module\data\protocol</Filter>
</ClCompile>
<ClCompile Include="..\..\src\ripple\module\data\protocol\STArray.cpp">

View File

@@ -80,7 +80,7 @@ public:
Amount
rate () const
{
return Amount::setRate (m_value);
return amountFromQuality (m_value);
}
/** Returns the scaled amount with in capped.

View File

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

View File

@@ -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

View File

@@ -1278,7 +1278,7 @@ STAmount LedgerEntrySet::rippleTransferFee (
STAmount saTransitRate (
noIssue(), static_cast<std::uint64_t> (uTransitRate), -9);
STAmount saTransferTotal = STAmount::multiply (
STAmount saTransferTotal = multiply (
saAmount, saTransitRate, saAmount.issue ());
STAmount saTransferFee = saTransferTotal - saAmount;

View File

@@ -60,7 +60,7 @@ public:
*/
STAmount getCurrentRate () const
{
return STAmount::setRate (getCurrentQuality());
return amountFromQuality (getCurrentQuality());
}
/** Get the current quality

View File

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

View File

@@ -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)

View File

@@ -302,7 +302,7 @@ STPathSet Pathfinder::filterPaths(int iMaxPaths, STPath& extraPath)
std::vector<path_LQ_t> 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 <<

View File

@@ -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;

View File

@@ -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;

View File

@@ -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);
}

View File

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

View File

@@ -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.

View File

@@ -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)

View File

@@ -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)

View File

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

View File

@@ -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)

View File

@@ -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)

View File

@@ -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.

File diff suppressed because it is too large Load Diff

View File

@@ -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 <mantissa_type, exponent_type> 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<STAmount>
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<SerializedType> deserialize (
static
std::unique_ptr <SerializedType>
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,200 +257,207 @@ 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;
private:
STAmount*
duplicate() const override;
// 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;
void canonicalize();
void set (std::int64_t v);
};
STAmount& operator+= (STAmount const&);
STAmount& operator-= (STAmount const&);
STAmount& operator+= (std::uint64_t);
STAmount& operator-= (std::uint64_t);
STAmount& operator= (std::uint64_t);
//------------------------------------------------------------------------------
//
// Creation
//
//------------------------------------------------------------------------------
operator double () const;
// VFALCO TODO The parameter type should be Quality not uint64_t
STAmount
amountFromQuality (std::uint64_t rate);
friend STAmount operator+ (STAmount const& v1, STAmount const& v2);
friend STAmount operator- (STAmount const& v1, STAmount const& v2);
STAmount
amountFromJson (SField::ref name, Json::Value const& v);
static STAmount divide (
STAmount const& v1, STAmount const& v2, Issue const& issue);
bool
amountFromJsonNoThrow (STAmount& result, Json::Value const& jvSource);
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)
{
//------------------------------------------------------------------------------
//
// 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);
}
}
static STAmount multiply (
STAmount const& v1, STAmount const& v2, Issue const& issue);
STAmount
multiply (STAmount const& v1, STAmount const& v2, Issue const& issue);
static STAmount multiply (
STAmount const& v1, STAmount const& v2, STAmount const& saUnit)
{
inline
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
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
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);
*/
// Add, subtract, multiply, or divide rounding result in specified direction
STAmount
addRound (STAmount const& v1, STAmount const& v2, 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);
}
STAmount
subRound (STAmount const& v1, STAmount const& v2, bool 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);
STAmount
mulRound (STAmount const& v1, STAmount const& v2,
Issue const& issue, bool roundUp);
// Someone is offering X for Y, I need Z, how much do I pay
inline
STAmount
mulRound (STAmount const& v1, STAmount const& v2,
STAmount const& saUnit, bool roundUp)
{
return mulRound (v1, v2, saUnit.issue(), roundUp);
}
// 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);
inline
STAmount
mulRound (STAmount const& v1, STAmount const& v2, bool roundUp)
{
return mulRound (v1, v2, v1.issue(), roundUp);
}
static STAmount deserialize (SerializerIterator&);
STAmount
divRound (STAmount const& v1, STAmount const& v2,
Issue const& issue, bool roundUp);
Json::Value getJson (int) const;
void setJson (Json::Value&) const;
inline
STAmount
divRound (STAmount const& v1, STAmount const& v2,
STAmount const& saUnit, bool roundUp)
{
return divRound (v1, v2, saUnit.issue(), roundUp);
}
STAmount getRound () const;
void roundSelf ();
inline
STAmount
divRound (STAmount const& v1, STAmount const& v2, bool roundUp)
{
return divRound (v1, v2, v1.issue(), roundUp);
}
static void canonicalizeRound (
bool isNative, std::uint64_t& value, int& offset, bool 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);
private:
Issue mIssue;
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<STAmount>
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<std::uint64_t> (-v);
}
else
{
mIsNegative = false;
mValue = static_cast<std::uint64_t> (v);
}
}
void set (int v)
{
if (v < 0)
{
mIsNegative = true;
mValue = static_cast<std::uint64_t> (-v);
}
else
{
mIsNegative = false;
mValue = static_cast<std::uint64_t> (v);
}
}
};
//------------------------------------------------------------------------------
inline bool isXRP(STAmount const& amount)
{

View File

@@ -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 <ripple/module/data/protocol/STAmount.h>
#include <beast/unit_test/suite.h>
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<std::uint64_t> (a) * static_cast<std::uint64_t> (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 <class Cond>
bool
expect (Cond cond, beast::String const& s)
{
return suite::expect (cond, s.toStdString());
}
template <class Cond>
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

View File

@@ -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<std::int64_t> (v1.mValue);
auto vv2 = static_cast<std::int64_t> (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<std::uint64_t> (fv);
canonicalizeRound (false, v, ov1, roundUp);
return STAmount (v1.getFName (), v1.mIssue, v, ov1, false);
}
else
{
std::uint64_t v = static_cast<std::uint64_t> (-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<std::int64_t> (v1.mValue);
auto vv2 = static_cast<std::int64_t> (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<std::uint64_t> (fv);
canonicalizeRound (false, v, ov1, roundUp);
return STAmount (v1.getFName (), v1.mIssue, v, ov1, false);
}
else
{
std::uint64_t v = static_cast<std::uint64_t> (-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

View File

@@ -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 (...)
{

View File

@@ -83,14 +83,78 @@ public:
assert (fName);
}
virtual ~SerializedType () { }
virtual ~SerializedType () = default;
static std::unique_ptr<SerializedType> 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 <SerializedType>
virtual
SerializedType*
duplicate () const
{
return new SerializedType (*fName);
}
public:
//
// members
//
static
std::unique_ptr <SerializedType>
deserialize (SField::ref name)
{
return std::unique_ptr<SerializedType> (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<SerializedType> clone () const
{
return std::unique_ptr<SerializedType> (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 <class D>
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);
}
};
//------------------------------------------------------------------------------

View File

@@ -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)
{

View File

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

View File

@@ -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

View File

@@ -79,13 +79,8 @@
#include <ripple/module/data/protocol/STArray.cpp>
#include <ripple/module/data/protocol/TER.cpp>
#include <ripple/module/data/protocol/TxFormats.cpp>
// 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 <ripple/module/data/protocol/STAmount.cpp>
#include <ripple/module/data/protocol/STAmountRound.cpp>
#include <ripple/module/data/protocol/STAmount.test.cpp>
#if BEAST_MSVC
#pragma warning (pop)