diff --git a/src/ripple/ledger/impl/View.cpp b/src/ripple/ledger/impl/View.cpp index 0b16add76fc..39393cf9d30 100644 --- a/src/ripple/ledger/impl/View.cpp +++ b/src/ripple/ledger/impl/View.cpp @@ -268,7 +268,7 @@ accountHolds( auto const sle = view.read(keylet::line(account, issuer, currency)); if (!sle) { - amount.clear({currency, issuer}); + amount.clear(Issue(currency, issuer)); } else if ( (zeroIfFrozen == fhZERO_IF_FROZEN) && diff --git a/src/ripple/protocol/AssetAmount.h b/src/ripple/protocol/AssetAmount.h index 06b32cdf80a..56bd0638728 100644 --- a/src/ripple/protocol/AssetAmount.h +++ b/src/ripple/protocol/AssetAmount.h @@ -28,6 +28,31 @@ namespace ripple { +// Since `canonicalize` does not have access to a ledger, this is needed to put +// the low-level routine stAmountCanonicalize on an amendment switch. Only +// transactions need to use this switchover. Outside of a transaction it's safe +// to unconditionally use the new behavior. + +// Use a static inside a function to help prevent order-of-initialzation issues +inline LocalValue& +getStaticSTAmountCanonicalizeSwitchover() +{ + static LocalValue r{true}; + return r; +} + +inline bool +getSTAmountCanonicalizeSwitchover() +{ + return *getStaticSTAmountCanonicalizeSwitchover(); +} + +inline void +setSTAmountCanonicalizeSwitchover(bool v) +{ + *getStaticSTAmountCanonicalizeSwitchover() = v; +} + struct AssetAmountConst { static constexpr int cMinOffset = -96; @@ -47,10 +72,11 @@ struct AssetAmountConst }; template -concept AssetType = std::is_same_v || std::is_same_v || - std::is_same_v; +concept ValidAssetType = std::is_same_v || + std::is_same_v || std::is_same_v || + std::is_convertible_v || std::is_convertible_v; -template +template class AssetAmount : public AssetAmountConst { public: @@ -64,11 +90,11 @@ class AssetAmount : public AssetAmountConst }; protected: - TIss asset_; - mantissa_type value_; - exponent_type exponent_; - bool isNative_; - bool isNegative_; + TIss mAsset; + mantissa_type mValue; + exponent_type mOffset; + bool mIsNative; + bool mIsNegative; public: AssetAmount( @@ -86,13 +112,10 @@ class AssetAmount : public AssetAmountConst AssetAmount(TIss const& iss, Number const& n); - // XRP drops - AssetAmount(mantissa_type value = 0); - TIss const& asset() const { - return asset_; + return mAsset; } operator Number() const; @@ -103,6 +126,8 @@ class AssetAmount : public AssetAmountConst AssetAmount& operator-=(AssetAmount const&); + explicit operator bool() const noexcept; + AssetAmount& operator=(beast::Zero); AccountID const& @@ -146,11 +171,15 @@ class AssetAmount : public AssetAmountConst void canonicalize(); - friend AssetAmount - operator+(AssetAmount const& v1, AssetAmount const& v2); + void + set(std::int64_t v); + + template + friend AssetAmount + operator+(AssetAmount const& v1, AssetAmount const& v2); }; -template +template bool isNative(TIss const& issue) { @@ -162,7 +191,7 @@ isNative(TIss const& issue) return isXRP(issue.issue()); } -template +template bool isMPT(TIss const& issue) { @@ -174,37 +203,51 @@ isMPT(TIss const& issue) return issue.isMPT(); } -template +template +bool +isNative(AssetAmount const& amount) +{ + return isNative(amount.asset()); +} + +template +bool +isMPT(AssetAmount const& amount) +{ + return isMPT(amount.asset()); +} + +template AssetAmount::AssetAmount( TIss const& iss, mantissa_type value, exponent_type exponent, bool isNegative, unchecked) - : asset_(iss) - , value_(value) - , exponent_(exponent) - , isNegative_(isNegative) - , isNative_(isNative(iss)) + : mAsset(iss) + , mValue(value) + , mOffset(exponent) + , mIsNative(isNative(iss)) + , mIsNegative(isNegative) { } -template +template AssetAmount::AssetAmount( TIss const& iss, mantissa_type value, exponent_type exponent, bool isNegative) - : asset_(iss) - , value_(value) - , exponent_(exponent) - , isNegative_(isNegative) - , isNative_(isNative(iss)) + : mAsset(iss) + , mValue(value) + , mOffset(exponent) + , mIsNative(isNative(iss)) + , mIsNegative(isNegative) { canonicalize(); } -template +template AssetAmount::AssetAmount(TIss const& iss, Number const& n) : AssetAmount( iss, @@ -214,13 +257,13 @@ AssetAmount::AssetAmount(TIss const& iss, Number const& n) { } -template +template AssetAmount::operator Number() const { - return Number(isNegative_ ? -value_ : value_, exponent_); + return Number(mIsNegative ? -mValue : mValue, mOffset); } -template +template AssetAmount& AssetAmount::operator+=(AssetAmount const& a) { @@ -228,7 +271,7 @@ AssetAmount::operator+=(AssetAmount const& a) return *this; } -template +template AssetAmount& AssetAmount::operator-=(AssetAmount const& a) { @@ -236,100 +279,273 @@ AssetAmount::operator-=(AssetAmount const& a) return *this; } -template -AssetAmount& AssetAmount::operator=(beast::Zero) +template +AssetAmount& +AssetAmount::operator=(beast::Zero) { clear(); return *this; } -template +template +AssetAmount::operator bool() const noexcept +{ + return *this != beast::zero; +} + +template +AssetAmount +operator-(AssetAmount const& value) +{ + if (value.mantissa() == 0) + return value; + return AssetAmount( + value.asset(), + value.mantissa(), + value.exponent(), + !value.negative(), + typename AssetAmount::unchecked()); +} + +template AssetAmount::mantissa_type AssetAmount::mantissa() const { - return value_; + return mValue; } -template +template AssetAmount::exponent_type AssetAmount::exponent() const { - return exponent_; + return mOffset; } -template +template bool AssetAmount::negative() const { - return isNegative_; + return mIsNegative; } -template +template int AssetAmount::signum() const noexcept { - return value_ ? (isNegative_ ? -1 : 1) : 0; + return mValue ? (mIsNegative ? -1 : 1) : 0; } -template +template AccountID const& AssetAmount::getIssuer() const { - return asset_.getIssuer(); + return mAsset.getIssuer(); } -template +template AssetAmount const& AssetAmount::value() const noexcept { return *this; } -template +template AssetAmount AssetAmount::zeroed() const { - return AssetAmount(asset_); + return AssetAmount(mAsset); } -template +template void AssetAmount::clear() { - exponent_ = isNative_ ? 0 : -100; - value_ = 0; - isNegative_ = false; + mOffset = mIsNative ? 0 : -100; + mValue = 0; + mIsNegative = false; } -template +template void AssetAmount::clear(TIss const& iss) { - asset_ = iss; - isNative_ = isNative(iss); + mAsset = iss; + mIsNative = isNative(iss); clear(); } -template +template void AssetAmount::clear(AssetAmount const& a) { clear(a.asset()); } -template +template void AssetAmount::negate() { if (*this != beast::zero) - isNegative_ = !isNegative_; + mIsNegative = !mIsNegative; } -template +template void AssetAmount::setAsset(TIss const& iss) { - asset_ = iss; + mAsset = iss; +} + +// amount = mValue * [10 ^ mOffset] +// Representation range is 10^80 - 10^(-80). +// +// On the wire: +// - high bit is 0 for XRP, 1 for issued currency +// - next bit is 1 for positive, 0 for negative (except 0 issued currency, which +// is a special case of 0x8000000000000000 +// - for issued currencies, the next 8 bits are (mOffset+97). +// The +97 is so that this value is always positive. +// - The remaining bits are significant digits (mantissa) +// That's 54 bits for issued currency and 62 bits for native +// (but XRP only needs 57 bits for the max value of 10^17 drops) +// +// mValue is zero if the amount is zero, otherwise it's within the range +// 10^15 to (10^16 - 1) inclusive. +// mOffset is in the range -96 to +80. +template +void +AssetAmount::canonicalize() +{ + if (isNative(mAsset) || isMPT(mAsset)) + { + // native currency amounts should always have an offset of zero + mIsNative = isNative(mAsset); + + // log(2^64,10) ~ 19.2 + if (mValue == 0 || mOffset <= -20) + { + mValue = 0; + mOffset = 0; + mIsNegative = false; + return; + } + + if (getSTAmountCanonicalizeSwitchover()) + { + // log(cMaxNativeN, 10) == 17 + if (mOffset > 17) + Throw( + "Native currency amount out of range"); + } + + if (getSTNumberSwitchover() && getSTAmountCanonicalizeSwitchover()) + { + Number num( + mIsNegative ? -mValue : mValue, mOffset, Number::unchecked{}); + if (mIsNative) + { + XRPAmount xrp{num}; + mIsNegative = xrp.drops() < 0; + mValue = mIsNegative ? -xrp.drops() : xrp.drops(); + } + else + { + MPTAmount c{num}; + mIsNegative = c.mpt() < 0; + mValue = mIsNegative ? -c.mpt() : c.mpt(); + } + mOffset = 0; + } + else + { + while (mOffset < 0) + { + mValue /= 10; + ++mOffset; + } + + while (mOffset > 0) + { + if (getSTAmountCanonicalizeSwitchover()) + { + // N.B. do not move the overflow check to after the + // multiplication + if (mValue > cMaxNativeN) + Throw( + "Native currency amount out of range"); + } + mValue *= 10; + --mOffset; + } + } + + if (mValue > cMaxNativeN) + Throw("Native currency amount out of range"); + + return; + } + + mIsNative = false; + + if (getSTNumberSwitchover()) + { + *this = AssetAmount(mAsset, operator Number()); + return; + } + + if (mValue == 0) + { + mOffset = -100; + mIsNegative = false; + return; + } + + while ((mValue < cMinValue) && (mOffset > cMinOffset)) + { + mValue *= 10; + --mOffset; + } + + while (mValue > cMaxValue) + { + if (mOffset >= cMaxOffset) + Throw("value overflow"); + + mValue /= 10; + ++mOffset; + } + + if ((mOffset < cMinOffset) || (mValue < cMinValue)) + { + mValue = 0; + mIsNegative = false; + mOffset = -100; + return; + } + + if (mOffset > cMaxOffset) + Throw("value overflow"); + + assert((mValue == 0) || ((mValue >= cMinValue) && (mValue <= cMaxValue))); + assert( + (mValue == 0) || ((mOffset >= cMinOffset) && (mOffset <= cMaxOffset))); + assert((mValue != 0) || (mOffset != -100)); +} + +template +void +AssetAmount::set(std::int64_t v) +{ + if (v < 0) + { + mIsNegative = true; + mValue = static_cast(-v); + } + else + { + mIsNegative = false; + mValue = static_cast(v); + } } namespace detail { @@ -338,18 +554,19 @@ static constexpr std::uint64_t tenTo14 = 100000000000000ull; static constexpr std::uint64_t tenTo14m1 = tenTo14 - 1; static constexpr std::uint64_t tenTo17 = tenTo14 * 1000; -template +template bool areComparable(AssetAmount const& v1, AssetAmount const& v2) { if constexpr (std::is_same_v || std::is_same_v) - return v1.getAssetID() == v2.getAssetID(); + return v1.asset().getAssetID() == v2.asset().getAssetID(); else if constexpr (std::is_same_v) { return ( (isMPT(v1) && isMPT(v2) && v1.asset() == v2.asset()) || - (v1.isIssue() && isNative(v1) == isNative(v2) && - v1.issue().getAssetID() == v2.issue().getAssetID())); + (v1.asset().isIssue() && isNative(v1) == isNative(v2) && + v1.asset().issue().getAssetID() == + v2.asset().issue().getAssetID())); } } @@ -400,8 +617,8 @@ muldiv_round( return static_cast(ret); } -template -std::int64_t +template +std::uint64_t getSNValue(AssetAmount const& amount) { if (!isNative(amount)) @@ -417,8 +634,8 @@ getSNValue(AssetAmount const& amount) return ret; } -template -std::int64_t +template +std::uint64_t getMPTValue(AssetAmount const& amount) { if (!isMPT(amount)) @@ -493,7 +710,7 @@ canonicalizeRound(bool native, std::uint64_t& value, int& offset, bool) // be specified. It also ignored some of the bits that could contribute to // rounding decisions. canonicalizeRoundStrict() tracks all of the bits in // the value being rounded. -void +inline void canonicalizeRoundStrict( bool native, std::uint64_t& value, @@ -577,7 +794,7 @@ class DontAffectNumberRoundMode template < void (*CanonicalizeFunc)(bool, std::uint64_t&, int&, bool), typename MightSaveRound, - AssetType TIss> + ValidAssetType TIss> AssetAmount mulRoundImpl( AssetAmount const& v1, @@ -606,7 +823,7 @@ mulRoundImpl( if (((maxV >> 32) * minV) > 2095475792ull) // cMaxNative / 2^32 Throw("Native value overflow"); - return AssetAmount(minV * maxV); + return AssetAmount{asset, minV * maxV}; } // TODO MPT if (isMPT(v1) && isMPT(v2) && isMPT(asset)) @@ -699,7 +916,7 @@ mulRoundImpl( // We might need to use NumberRoundModeGuard. Allow the caller // to pass either that or a replacement as a template parameter. -template +template static AssetAmount divRoundImpl( AssetAmount const& num, @@ -790,11 +1007,11 @@ divRoundImpl( } // namespace detail -template +template AssetAmount operator+(AssetAmount const& v1, AssetAmount const& v2) { - if (!areComparable(v1, v2)) + if (!detail::areComparable(v1, v2)) Throw("Can't add amounts that aren't comparable!"); if (v2 == beast::zero) @@ -809,9 +1026,11 @@ operator+(AssetAmount const& v1, AssetAmount const& v2) // TODO if (isNative(v1)) - return {detail::getSNValue(v1) + detail::getSNValue(v2)}; + return AssetAmount{ + v1.asset(), detail::getSNValue(v1) + detail::getSNValue(v2)}; if (isMPT(v1)) - return {v1.mAsset, v1.mpt().mpt() + v2.mpt().mpt()}; + return AssetAmount{ + v1.asset(), detail::getMPTValue(v1) + detail::getMPTValue(v2)}; if (getSTNumberSwitchover()) return AssetAmount{ @@ -855,14 +1074,14 @@ operator+(AssetAmount const& v1, AssetAmount const& v2) v1.asset(), static_cast(-fv), ov1, true}; } -template +template AssetAmount operator-(AssetAmount const& v1, AssetAmount const& v2) { return v1 + (-v2); } -template +template AssetAmount divide( AssetAmount const& num, @@ -880,7 +1099,7 @@ divide( int numOffset = num.exponent(); int denOffset = den.exponent(); - if (num.native() || num.isMPT()) + if (isNative(num) || isMPT(num)) { while (numVal < AssetAmount::cMinValue) { @@ -890,7 +1109,7 @@ divide( } } - if (den.native() || den.isMPT()) + if (isNative(den) || isMPT(den)) { while (denVal < AssetAmount::cMinValue) { @@ -911,7 +1130,7 @@ divide( num.negative() != den.negative()); } -template +template AssetAmount multiply( AssetAmount const& v1, @@ -938,9 +1157,9 @@ multiply( if (((maxV >> 32) * minV) > 2095475792ull) // cMaxNative / 2^32 Throw("Native value overflow"); - return STAmount(v1.getFName(), minV * maxV); + return AssetAmount(asset, minV * maxV); } - if (v1.isMPT() && v2.isMPT() && asset.isMPT()) + if (isMPT(v1) && isMPT(v2) && isMPT(asset)) { std::uint64_t const minV = detail::getMPTValue(v1) < detail::getMPTValue(v2) @@ -961,7 +1180,7 @@ multiply( } if (getSTNumberSwitchover()) - return {Number{v1} * Number{v2}, asset}; + return AssetAmount{asset, Number{v1} * Number{v2}}; std::uint64_t value1 = v1.mantissa(); std::uint64_t value2 = v2.mantissa(); @@ -998,7 +1217,7 @@ multiply( v1.negative() != v2.negative()); } -template +template AssetAmount mulRound( AssetAmount const& v1, @@ -1012,7 +1231,7 @@ mulRound( TIss>(v1, v2, asset, roundUp); } -template +template AssetAmount mulRoundStrict( AssetAmount const& v1, @@ -1026,7 +1245,7 @@ mulRoundStrict( TIss>(v1, v2, asset, roundUp); } -template +template AssetAmount divRound( AssetAmount const& num, @@ -1038,7 +1257,7 @@ divRound( num, den, asset, roundUp); } -template +template AssetAmount divRoundStrict( AssetAmount const& num, diff --git a/src/ripple/protocol/STAmount.h b/src/ripple/protocol/STAmount.h index 419ac4ddf42..ab000f9e225 100644 --- a/src/ripple/protocol/STAmount.h +++ b/src/ripple/protocol/STAmount.h @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -35,11 +36,6 @@ namespace ripple { -template -concept AssetType = std::is_same_v || std::is_same_v || - std::is_same_v || std::is_convertible_v || - std::is_convertible_v; - // Internal form: // 1: If amount is zero, then value is zero and offset is -100 // 2: Otherwise: @@ -50,50 +46,20 @@ concept AssetType = std::is_same_v || std::is_same_v || // Wire form: // High 8 bits are (offset+142), legal range is, 80 to 22 inclusive // Low 56 bits are value, legal range is 10^15 to (10^16 - 1) inclusive -class STAmount final : public STBase, public CountedObject +class STAmount final : public AssetAmount, + public STBase, + public CountedObject { -public: - using mantissa_type = std::uint64_t; - using exponent_type = int; - using rep = std::pair; - -private: - Asset mAsset; - mantissa_type mValue; - exponent_type mOffset; - bool mIsNative; // A shorthand for isXRP(mIssue). - bool mIsNegative; - public: using value_type = STAmount; - static const int cMinOffset = -96; - static const int cMaxOffset = 80; - - // Maximum native value supported by the code - static const std::uint64_t cMinValue = 1000000000000000ull; - static const std::uint64_t cMaxValue = 9999999999999999ull; - static const std::uint64_t cMaxNative = 9000000000000000000ull; - - // Max native value on network. - static const std::uint64_t cMaxNativeN = 100000000000000000ull; - static const std::uint64_t cIssuedCurrency = 0x8000000000000000ull; - static const std::uint64_t cPositive = 0x4000000000000000ull; - static const std::uint64_t cMPToken = 0x2000000000000000ull; - static const std::uint64_t cValueMask = ~(cPositive | cMPToken); - static std::uint64_t const uRateOne; //-------------------------------------------------------------------------- STAmount(SerialIter& sit, SField const& name); - struct unchecked - { - explicit unchecked() = default; - }; - // Do not call canonicalize - template + template STAmount( SField const& name, A const& asset, @@ -103,7 +69,7 @@ class STAmount final : public STBase, public CountedObject bool negative, unchecked); - template + template STAmount( A const& asset, mantissa_type mantissa, @@ -113,7 +79,7 @@ class STAmount final : public STBase, public CountedObject unchecked); // Call canonicalize - template + template STAmount( SField const& name, A const& asset, @@ -129,7 +95,7 @@ class STAmount final : public STBase, public CountedObject std::uint64_t mantissa = 0, bool negative = false); - template + template STAmount( SField const& name, A const& asset, @@ -141,40 +107,36 @@ class STAmount final : public STBase, public CountedObject explicit STAmount(SField const& name, STAmount const& amt); - template + template STAmount( A const& asset, std::uint64_t mantissa = 0, int exponent = 0, bool negative = false) - : mAsset(asset) - , mValue(mantissa) - , mOffset(exponent) - , mIsNegative(negative) + : AssetAmount(asset, mantissa, exponent, negative) { - canonicalize(); } // VFALCO Is this needed when we have the previous signature? - template + template STAmount( A const& asset, std::uint32_t mantissa, int exponent = 0, bool negative = false); - template + template STAmount(A const& asset, std::int64_t mantissa, int exponent = 0); - template + template STAmount(A const& asset, int mantissa, int exponent = 0); + STAmount(SField const& name, AssetAmount const& amount); + // Legacy support for new-style amounts - template - STAmount(IOUAmount const& amount, A const& asset); + STAmount(IOUAmount const& amount, Asset const& asset); STAmount(XRPAmount const& amount); - template - STAmount(MPTAmount const& amount, A const& asset); + STAmount(MPTAmount const& amount, Asset const& asset); operator Number() const; //-------------------------------------------------------------------------- @@ -183,9 +145,6 @@ class STAmount final : public STBase, public CountedObject // //-------------------------------------------------------------------------- - int - exponent() const noexcept; - bool native() const noexcept; @@ -201,15 +160,6 @@ class STAmount final : public STBase, public CountedObject std::string getTypeName() const noexcept; - bool - negative() const noexcept; - - std::uint64_t - mantissa() const noexcept; - - Asset const& - asset() const; - Issue const& issue() const; @@ -223,9 +173,6 @@ class STAmount final : public STBase, public CountedObject AccountID const& getIssuer() const; - int - signum() const noexcept; - /** Returns a zero value with the same issuer and currency. */ STAmount zeroed() const; @@ -242,8 +189,6 @@ class STAmount final : public STBase, public CountedObject // //-------------------------------------------------------------------------- - explicit operator bool() const noexcept; - STAmount& operator+=(STAmount const&); STAmount& @@ -254,28 +199,15 @@ class STAmount final : public STBase, public CountedObject STAmount& operator=(XRPAmount const& amount); + explicit operator AssetAmount() const; + explicit operator AssetAmount() const; + //-------------------------------------------------------------------------- // // Modification // //-------------------------------------------------------------------------- - void - negate(); - - void - clear(); - - // Zero while copying currency and issuer. - void - clear(STAmount const& saTmpl); - - void - clear(Issue const& issue); - - void - clear(MPT const& mpt); - void setIssuer(AccountID const& uIssuer); @@ -316,7 +248,7 @@ class STAmount final : public STBase, public CountedObject MPTAmount mpt() const; - template + template void setAsset(A const& a, bool native); @@ -324,11 +256,6 @@ class STAmount final : public STBase, public CountedObject static std::unique_ptr construct(SerialIter&, SField const& name); - void - set(std::int64_t v); - void - canonicalize(); - STBase* copy(std::size_t n, void* buf) const override; STBase* @@ -343,7 +270,7 @@ class STAmount final : public STBase, public CountedObject operator+(STAmount const& v1, STAmount const& v2); }; -template +template void STAmount::setAsset(const A& asset, bool native) { @@ -353,7 +280,34 @@ STAmount::setAsset(const A& asset, bool native) mAsset = asset; } -template +//------------------------------------------------------------------------------ +// +// Creation +// +//------------------------------------------------------------------------------ + +// VFALCO TODO The parameter type should be Quality not uint64_t +STAmount +amountFromQuality(std::uint64_t rate); + +STAmount +amountFromString(Asset const& issue, std::string const& amount); + +STAmount +amountFromJson(SField const& name, Json::Value const& v); + +bool +amountFromJsonNoThrow(STAmount& result, Json::Value const& jvSource); + +// IOUAmount and XRPAmount define toSTAmount, defining this +// trivial conversion here makes writing generic code easier +inline STAmount const& +toSTAmount(STAmount const& a) +{ + return a; +} + +template STAmount::STAmount( SField const& name, A const& asset, @@ -362,16 +316,13 @@ STAmount::STAmount( bool native, bool negative, unchecked) - : STBase(name) - , mValue(mantissa) - , mOffset(exponent) - , mIsNative(native) - , mIsNegative(negative) + : AssetAmount(asset, mantissa, exponent, negative, unchecked()) + , STBase(name) { setAsset(asset, native); } -template +template STAmount::STAmount( A const& asset, mantissa_type mantissa, @@ -379,15 +330,12 @@ STAmount::STAmount( bool native, bool negative, unchecked) - : mValue(mantissa) - , mOffset(exponent) - , mIsNative(native) - , mIsNegative(negative) + : AssetAmount(asset, mantissa, exponent, negative, unchecked()) { setAsset(asset, native); } -template +template STAmount::STAmount( SField const& name, A const& asset, @@ -395,42 +343,32 @@ STAmount::STAmount( exponent_type exponent, bool native, bool negative) - : STBase(name) - , mValue(mantissa) - , mOffset(exponent) - , mIsNative(native) - , mIsNegative(negative) + : AssetAmount(asset, mantissa, exponent, negative), STBase(name) { setAsset(asset, native); - canonicalize(); } -template +template STAmount::STAmount( SField const& name, A const& asset, std::uint64_t mantissa, int exponent, bool negative) - : STBase(name) - , mAsset(asset) - , mValue(mantissa) - , mOffset(exponent) - , mIsNegative(negative) + : AssetAmount(asset, mantissa, exponent, negative), STBase(name) { assert(mValue <= std::numeric_limits::max()); - canonicalize(); } -template +template STAmount::STAmount(A const& asset, std::int64_t mantissa, int exponent) - : mAsset(asset), mOffset(exponent) + : AssetAmount(asset, mantissa, exponent, false, unchecked()) { set(mantissa); canonicalize(); } -template +template STAmount::STAmount( A const& asset, std::uint32_t mantissa, @@ -440,82 +378,18 @@ STAmount::STAmount( { } -template +template STAmount::STAmount(A const& asset, int mantissa, int exponent) : STAmount(asset, safe_cast(mantissa), exponent) { } -// Legacy support for new-style amounts -template -STAmount::STAmount(IOUAmount const& amount, A const& asset) - : mAsset(asset) - , mOffset(amount.exponent()) - , mIsNative(false) - , mIsNegative(amount < beast::zero) -{ - if (mIsNegative) - mValue = static_cast(-amount.mantissa()); - else - mValue = static_cast(amount.mantissa()); - - canonicalize(); -} - -template -STAmount::STAmount(MPTAmount const& amount, A const& asset) - : mAsset(asset) - , mOffset(0) - , mIsNative(false) - , mIsNegative(amount < beast::zero) -{ - if (mIsNegative) - mValue = unsafe_cast(-amount.mpt()); - else - mValue = unsafe_cast(amount.mpt()); - - canonicalize(); -} - -//------------------------------------------------------------------------------ -// -// Creation -// -//------------------------------------------------------------------------------ - -// VFALCO TODO The parameter type should be Quality not uint64_t -STAmount -amountFromQuality(std::uint64_t rate); - -STAmount -amountFromString(Asset const& issue, std::string const& amount); - -STAmount -amountFromJson(SField const& name, Json::Value const& v); - -bool -amountFromJsonNoThrow(STAmount& result, Json::Value const& jvSource); - -// IOUAmount and XRPAmount define toSTAmount, defining this -// trivial conversion here makes writing generic code easier -inline STAmount const& -toSTAmount(STAmount const& a) -{ - return a; -} - //------------------------------------------------------------------------------ // // Observers // //------------------------------------------------------------------------------ -inline int -STAmount::exponent() const noexcept -{ - return mOffset; -} - inline bool STAmount::native() const noexcept { @@ -540,24 +414,6 @@ STAmount::isIOU() const noexcept return mAsset.isIssue() && !mIsNative; } -inline bool -STAmount::negative() const noexcept -{ - return mIsNegative; -} - -inline std::uint64_t -STAmount::mantissa() const noexcept -{ - return mValue; -} - -inline Asset const& -STAmount::asset() const -{ - return mAsset; -} - inline Issue const& STAmount::issue() const { @@ -582,12 +438,6 @@ STAmount::getIssuer() const return mAsset.getIssuer(); } -inline int -STAmount::signum() const noexcept -{ - return mValue ? (mIsNegative ? -1 : 1) : 0; -} - inline STAmount STAmount::zeroed() const { @@ -596,11 +446,6 @@ STAmount::zeroed() const return STAmount(mAsset.mptIssue()); } -inline STAmount::operator bool() const noexcept -{ - return *this != beast::zero; -} - inline STAmount::operator Number() const { if (mIsNative) @@ -612,7 +457,7 @@ inline STAmount::operator Number() const inline STAmount& STAmount::operator=(beast::Zero) { - clear(); + AssetAmount::clear(); return *this; } @@ -623,47 +468,6 @@ STAmount::operator=(XRPAmount const& amount) return *this; } -inline void -STAmount::negate() -{ - if (*this != beast::zero) - mIsNegative = !mIsNegative; -} - -inline void -STAmount::clear() -{ - // The -100 is used to allow 0 to sort less than a small positive values - // which have a negative exponent. - mOffset = mIsNative ? 0 : -100; - mValue = 0; - mIsNegative = false; -} - -// Zero while copying currency and issuer. -inline void -STAmount::clear(STAmount const& saTmpl) -{ - if (saTmpl.isMPT()) - clear(saTmpl.mAsset.mptIssue()); - else - clear(saTmpl.issue()); -} - -inline void -STAmount::clear(Issue const& issue) -{ - setIssue(issue); - clear(); -} - -inline void -STAmount::clear(MPT const& mpt) -{ - mAsset = mpt; - clear(); -} - inline void STAmount::setIssuer(AccountID const& uIssuer) { @@ -677,6 +481,21 @@ STAmount::value() const noexcept return *this; } +inline STAmount::operator AssetAmount() const +{ + if (!mAsset.isIssue()) + Throw("STAmount is not for Issue"); + return AssetAmount(mAsset.issue(), mValue, mOffset, mIsNegative); +} + +inline STAmount::operator AssetAmount() const +{ + if (!mAsset.isMPT()) + Throw("STAmount is not for MPTIssue"); + return AssetAmount( + mAsset.mptIssue(), mValue, mOffset, mIsNegative); +} + inline bool isLegalNet(STAmount const& value) { @@ -790,17 +609,6 @@ isMPT(STAmount const& amount) return amount.isMPT(); } -// Since `canonicalize` does not have access to a ledger, this is needed to put -// the low-level routine stAmountCanonicalize on an amendment switch. Only -// transactions need to use this switchover. Outside of a transaction it's safe -// to unconditionally use the new behavior. - -bool -getSTAmountCanonicalizeSwitchover(); - -void -setSTAmountCanonicalizeSwitchover(bool v); - /** RAII class to set and restore the STAmount canonicalize switchover. */ diff --git a/src/ripple/protocol/impl/STAmount.cpp b/src/ripple/protocol/impl/STAmount.cpp index 8f96f3a5ec0..39ed78bad5b 100644 --- a/src/ripple/protocol/impl/STAmount.cpp +++ b/src/ripple/protocol/impl/STAmount.cpp @@ -35,76 +35,8 @@ namespace ripple { -namespace { - -// Use a static inside a function to help prevent order-of-initialzation issues -LocalValue& -getStaticSTAmountCanonicalizeSwitchover() -{ - static LocalValue r{true}; - return r; -} -} // namespace - -bool -getSTAmountCanonicalizeSwitchover() -{ - return *getStaticSTAmountCanonicalizeSwitchover(); -} - -void -setSTAmountCanonicalizeSwitchover(bool v) -{ - *getStaticSTAmountCanonicalizeSwitchover() = v; -} - -static const std::uint64_t tenTo14 = 100000000000000ull; -static const std::uint64_t tenTo14m1 = tenTo14 - 1; -static const std::uint64_t tenTo17 = tenTo14 * 1000; - -//------------------------------------------------------------------------------ -static std::int64_t -getSNValue(STAmount const& amount) -{ - if (!amount.native()) - Throw("amount is not native!"); - - auto ret = static_cast(amount.mantissa()); - - assert(static_cast(ret) == amount.mantissa()); - - if (amount.negative()) - ret = -ret; - - return ret; -} - -static std::int64_t -getMPTValue(STAmount const& amount) -{ - if (!amount.isMPT()) - Throw("amount is not native!"); - - auto ret = static_cast(amount.mantissa()); - - assert(static_cast(ret) == amount.mantissa()); - - if (amount.negative()) - ret = -ret; - - return ret; -} - -static bool -areComparable(STAmount const& v1, STAmount const& v2) -{ - return (v1.isMPT() && v2.isMPT() && - v1.asset().mptIssue() == v2.asset().mptIssue()) || - (v1.isIssue() && v1.native() == v2.native() && - v1.issue().currency == v2.issue().currency); -} - -STAmount::STAmount(SerialIter& sit, SField const& name) : STBase(name) +STAmount::STAmount(SerialIter& sit, SField const& name) + : AssetAmount(xrpIssue()), STBase(name) { // TODO MPT make sure backward compatible @@ -241,18 +173,13 @@ STAmount::STAmount( #endif STAmount::STAmount(SField const& name, std::int64_t mantissa) - : STBase(name), mAsset(xrpIssue()), mOffset(0), mIsNative(true) + : AssetAmount(xrpIssue(), mantissa, 0, false), STBase(name) { set(mantissa); } STAmount::STAmount(SField const& name, std::uint64_t mantissa, bool negative) - : STBase(name) - , mAsset(xrpIssue()) - , mValue(mantissa) - , mOffset(0) - , mIsNative(true) - , mIsNegative(negative) + : AssetAmount(xrpIssue(), mantissa, 0, negative), STBase(name) { assert(mValue <= std::numeric_limits::max()); } @@ -276,11 +203,13 @@ STAmount::STAmount( #endif STAmount::STAmount(SField const& name, STAmount const& from) - : STBase(name) - , mAsset(from.mAsset) - , mValue(from.mValue) - , mOffset(from.mOffset) - , mIsNegative(from.mIsNegative) + : AssetAmount( + from.mAsset, + from.mValue, + from.mOffset, + from.mIsNegative, + unchecked()) + , STBase(name) { assert(mValue <= std::numeric_limits::max()); canonicalize(); @@ -289,11 +218,7 @@ STAmount::STAmount(SField const& name, STAmount const& from) //------------------------------------------------------------------------------ STAmount::STAmount(std::uint64_t mantissa, bool negative) - : mAsset(xrpIssue()) - , mValue(mantissa) - , mOffset(0) - , mIsNative(true) - , mIsNegative(mantissa != 0 && negative) + : AssetAmount(xrpIssue(), mantissa, 0, negative, unchecked()) { assert(mValue <= std::numeric_limits::max()); } @@ -347,15 +272,54 @@ STAmount::STAmount(IOUAmount const& amount, Asset const& asset) #endif STAmount::STAmount(XRPAmount const& amount) - : mAsset(xrpIssue()) - , mOffset(0) - , mIsNative(true) - , mIsNegative(amount < beast::zero) + : AssetAmount( + xrpIssue(), + amount.drops(), + 0, + amount < beast::zero, + unchecked()) { if (mIsNegative) - mValue = unsafe_cast(-amount.drops()); - else - mValue = unsafe_cast(amount.drops()); + mValue = -mValue; + + canonicalize(); +} + +STAmount::STAmount(SField const& name, AssetAmount const& amount) + : STAmount{ + name, + amount.asset(), + amount.mantissa(), + amount.exponent(), + amount.negative()} +{ +} + +// Legacy support for new-style amounts +STAmount::STAmount(IOUAmount const& amount, Asset const& asset) + : AssetAmount( + asset, + amount.mantissa(), + amount.exponent(), + amount < beast::zero, + unchecked()) +{ + if (mIsNegative) + mValue = -mValue; + + canonicalize(); +} + +STAmount::STAmount(MPTAmount const& amount, Asset const& asset) + : AssetAmount( + asset, + amount.mpt(), + 0, + amount < beast::zero, + unchecked()) +{ + if (mIsNegative) + mValue = -mValue; canonicalize(); } @@ -479,76 +443,9 @@ STAmount::operator-=(STAmount const& a) STAmount operator+(STAmount const& v1, STAmount const& v2) { - if (!areComparable(v1, v2)) - Throw("Can't add amounts that are't comparable!"); - - if (v2 == beast::zero) - return v1; - - if (v1 == beast::zero) - { - // Result must be in terms of v1 currency and issuer. - return { - v1.getFName(), - v1.asset(), - v2.mantissa(), - v2.exponent(), - v2.negative()}; - } - - // TODO - if (v1.native()) - return {v1.getFName(), getSNValue(v1) + getSNValue(v2)}; - if (v1.isMPT()) - return {v1.mAsset, v1.mpt().mpt() + v2.mpt().mpt()}; - - if (getSTNumberSwitchover()) - { - auto x = v1; - x = v1.iou() + v2.iou(); - return x; - } - - int ov1 = v1.exponent(), ov2 = v2.exponent(); - std::int64_t vv1 = static_cast(v1.mantissa()); - std::int64_t vv2 = static_cast(v2.mantissa()); - - if (v1.negative()) - vv1 = -vv1; - - if (v2.negative()) - 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 {v1.getFName(), v1.asset()}; - - if (fv >= 0) - return STAmount{ - v1.getFName(), - v1.asset(), - static_cast(fv), - ov1, - false}; - - return STAmount{ - v1.getFName(), v1.asset(), static_cast(-fv), ov1, true}; + auto const res = static_cast const&>(v1) + + static_cast const&>(v2); + return STAmount{v1.getFName(), res}; } STAmount @@ -730,7 +627,8 @@ STAmount::getText() const return ret; } -Json::Value STAmount::getJson(JsonOptions) const +Json::Value +STAmount::getJson(JsonOptions) const { Json::Value elem; setJson(elem); @@ -798,158 +696,6 @@ STAmount::isDefault() const //------------------------------------------------------------------------------ -// amount = mValue * [10 ^ mOffset] -// Representation range is 10^80 - 10^(-80). -// -// On the wire: -// - high bit is 0 for XRP, 1 for issued currency -// - next bit is 1 for positive, 0 for negative (except 0 issued currency, which -// is a special case of 0x8000000000000000 -// - for issued currencies, the next 8 bits are (mOffset+97). -// The +97 is so that this value is always positive. -// - The remaining bits are significant digits (mantissa) -// That's 54 bits for issued currency and 62 bits for native -// (but XRP only needs 57 bits for the max value of 10^17 drops) -// -// mValue is zero if the amount is zero, otherwise it's within the range -// 10^15 to (10^16 - 1) inclusive. -// mOffset is in the range -96 to +80. -void -STAmount::canonicalize() -{ - if (isXRP(*this) || mAsset.isMPT()) - { - // native currency amounts should always have an offset of zero - mIsNative = isXRP(*this); - - // log(2^64,10) ~ 19.2 - if (mValue == 0 || mOffset <= -20) - { - mValue = 0; - mOffset = 0; - mIsNegative = false; - return; - } - - if (getSTAmountCanonicalizeSwitchover()) - { - // log(cMaxNativeN, 10) == 17 - if (mOffset > 17) - Throw( - "Native currency amount out of range"); - } - - if (getSTNumberSwitchover() && getSTAmountCanonicalizeSwitchover()) - { - Number num( - mIsNegative ? -mValue : mValue, mOffset, Number::unchecked{}); - if (mIsNative) - { - XRPAmount xrp{num}; - mIsNegative = xrp.drops() < 0; - mValue = mIsNegative ? -xrp.drops() : xrp.drops(); - } - else - { - MPTAmount c{num}; - mIsNegative = c.mpt() < 0; - mValue = mIsNegative ? -c.mpt() : c.mpt(); - } - mOffset = 0; - } - else - { - while (mOffset < 0) - { - mValue /= 10; - ++mOffset; - } - - while (mOffset > 0) - { - if (getSTAmountCanonicalizeSwitchover()) - { - // N.B. do not move the overflow check to after the - // multiplication - if (mValue > cMaxNativeN) - Throw( - "Native currency amount out of range"); - } - mValue *= 10; - --mOffset; - } - } - - if (mValue > cMaxNativeN) - Throw("Native currency amount out of range"); - - return; - } - - mIsNative = false; - - if (getSTNumberSwitchover()) - { - *this = iou(); - return; - } - - if (mValue == 0) - { - mOffset = -100; - mIsNegative = false; - return; - } - - while ((mValue < cMinValue) && (mOffset > cMinOffset)) - { - mValue *= 10; - --mOffset; - } - - while (mValue > cMaxValue) - { - if (mOffset >= cMaxOffset) - Throw("value overflow"); - - mValue /= 10; - ++mOffset; - } - - if ((mOffset < cMinOffset) || (mValue < cMinValue)) - { - mValue = 0; - mIsNegative = false; - mOffset = -100; - return; - } - - if (mOffset > cMaxOffset) - Throw("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 - { - mIsNegative = false; - mValue = static_cast(v); - } -} - -//------------------------------------------------------------------------------ - STAmount amountFromQuality(std::uint64_t rate) { @@ -1181,14 +927,15 @@ amountFromJsonNoThrow(STAmount& result, Json::Value const& jvSource) bool operator==(STAmount const& lhs, STAmount const& rhs) { - return areComparable(lhs, rhs) && lhs.negative() == rhs.negative() && - lhs.exponent() == rhs.exponent() && lhs.mantissa() == rhs.mantissa(); + return detail::areComparable(lhs, rhs) && + lhs.negative() == rhs.negative() && lhs.exponent() == rhs.exponent() && + lhs.mantissa() == rhs.mantissa(); } bool operator<(STAmount const& lhs, STAmount const& rhs) { - if (!areComparable(lhs, rhs)) + if (!detail::areComparable(lhs, rhs)) Throw( "Can't compare amounts that are't comparable!"); @@ -1235,440 +982,24 @@ operator-(STAmount const& value) STAmount::unchecked{}); } -//------------------------------------------------------------------------------ -// -// Arithmetic -// -//------------------------------------------------------------------------------ - -// Calculate (a * b) / c when all three values are 64-bit -// without loss of precision: -static std::uint64_t -muldiv( - std::uint64_t multiplier, - std::uint64_t multiplicand, - std::uint64_t divisor) -{ - boost::multiprecision::uint128_t ret; - - boost::multiprecision::multiply(ret, multiplier, multiplicand); - ret /= divisor; - - if (ret > std::numeric_limits::max()) - { - Throw( - "overflow: (" + std::to_string(multiplier) + " * " + - std::to_string(multiplicand) + ") / " + std::to_string(divisor)); - } - - return static_cast(ret); -} - -static std::uint64_t -muldiv_round( - std::uint64_t multiplier, - std::uint64_t multiplicand, - std::uint64_t divisor, - std::uint64_t rounding) -{ - boost::multiprecision::uint128_t ret; - - boost::multiprecision::multiply(ret, multiplier, multiplicand); - ret += rounding; - ret /= divisor; - - if (ret > std::numeric_limits::max()) - { - Throw( - "overflow: ((" + std::to_string(multiplier) + " * " + - std::to_string(multiplicand) + ") + " + std::to_string(rounding) + - ") / " + std::to_string(divisor)); - } - - return static_cast(ret); -} - STAmount divide(STAmount const& num, STAmount const& den, Asset const& asset) { - if (den == beast::zero) - Throw("division by zero"); - - if (num == beast::zero) - return {asset}; - - std::uint64_t numVal = num.mantissa(); - std::uint64_t denVal = den.mantissa(); - int numOffset = num.exponent(); - int denOffset = den.exponent(); - - if (num.native() || num.isMPT()) - { - while (numVal < STAmount::cMinValue) - { - // Need to bring into range - numVal *= 10; - --numOffset; - } - } - - if (den.native() || den.isMPT()) - { - while (denVal < STAmount::cMinValue) - { - denVal *= 10; - --denOffset; - } - } - - // We divide the two mantissas (each is between 10^15 - // and 10^16). To maintain precision, we multiply the - // numerator by 10^17 (the product is in the range of - // 10^32 to 10^33) followed by a division, so the result - // is in the range of 10^16 to 10^15. - return STAmount( - asset, - muldiv(numVal, tenTo17, denVal) + 5, - numOffset - denOffset - 17, - num.negative() != den.negative()); + auto const res = divide( + static_cast const&>(num), + static_cast const&>(den), + asset); + return STAmount{num.getFName(), res}; } STAmount multiply(STAmount const& v1, STAmount const& v2, Asset const& asset) { - if (v1 == beast::zero || v2 == beast::zero) - return STAmount(asset); - - if (v1.native() && v2.native() && isXRP(asset)) - { - std::uint64_t const minV = - getSNValue(v1) < getSNValue(v2) ? getSNValue(v1) : getSNValue(v2); - std::uint64_t const maxV = - getSNValue(v1) < getSNValue(v2) ? getSNValue(v2) : getSNValue(v1); - - if (minV > 3000000000ull) // sqrt(cMaxNative) - Throw("Native value overflow"); - - if (((maxV >> 32) * minV) > 2095475792ull) // cMaxNative / 2^32 - Throw("Native value overflow"); - - return STAmount(v1.getFName(), minV * maxV); - } - if (v1.isMPT() && v2.isMPT() && asset.isMPT()) - { - std::uint64_t const minV = getMPTValue(v1) < getMPTValue(v2) - ? getMPTValue(v1) - : getMPTValue(v2); - std::uint64_t const maxV = getMPTValue(v1) < getMPTValue(v2) - ? getMPTValue(v2) - : getMPTValue(v1); - - if (minV > 3000000000ull) // sqrt(cMaxNative) - Throw("Asset value overflow"); - - if (((maxV >> 32) * minV) > 2095475792ull) // cMaxNative / 2^32 - Throw("Asset value overflow"); - - return STAmount(asset, minV * maxV); - } - - if (getSTNumberSwitchover()) - return {IOUAmount{Number{v1} * Number{v2}}, asset}; - - std::uint64_t value1 = v1.mantissa(); - std::uint64_t value2 = v2.mantissa(); - int offset1 = v1.exponent(); - int offset2 = v2.exponent(); - - // TODO MPT - if (v1.native() || v1.isMPT()) - { - while (value1 < STAmount::cMinValue) - { - value1 *= 10; - --offset1; - } - } - - if (v2.native() || v2.isMPT()) - { - while (value2 < STAmount::cMinValue) - { - value2 *= 10; - --offset2; - } - } - - // We multiply the two mantissas (each is between 10^15 - // and 10^16), so their product is in the 10^30 to 10^32 - // range. Dividing their product by 10^14 maintains the - // precision, by scaling the result to 10^16 to 10^18. - return STAmount( - asset, - muldiv(value1, value2, tenTo14) + 7, - offset1 + offset2 + 14, - v1.negative() != v2.negative()); -} - -// This is the legacy version of canonicalizeRound. It's been in use -// for years, so it is deeply embedded in the behavior of cross-currency -// transactions. -// -// However in 2022 it was noticed that the rounding characteristics were -// surprising. When the code converts from IOU-like to XRP-like there may -// be a fraction of the IOU-like representation that is too small to be -// represented in drops. `canonicalizeRound()` currently does some unusual -// rounding. -// -// 1. If the fractional part is greater than or equal to 0.1, then the -// number of drops is rounded up. -// -// 2. However, if the fractional part is less than 0.1 (for example, -// 0.099999), then the number of drops is rounded down. -// -// The XRP Ledger has this rounding behavior baked in. But there are -// situations where this rounding behavior led to undesirable outcomes. -// So an alternative rounding approach was introduced. You'll see that -// alternative below. -static void -canonicalizeRound(bool native, std::uint64_t& value, int& offset, bool) -{ - if (native) - { - 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; - } -} - -// The original canonicalizeRound did not allow the rounding direction to -// be specified. It also ignored some of the bits that could contribute to -// rounding decisions. canonicalizeRoundStrict() tracks all of the bits in -// the value being rounded. -static void -canonicalizeRoundStrict( - bool native, - std::uint64_t& value, - int& offset, - bool roundUp) -{ - if (native) - { - if (offset < 0) - { - bool hadRemainder = false; - - while (offset < -1) - { - // It would be better to use std::lldiv than to separately - // compute the remainder. But std::lldiv does not support - // unsigned arguments. - std::uint64_t const newValue = value / 10; - hadRemainder |= (value != (newValue * 10)); - value = newValue; - ++offset; - } - value += - (hadRemainder && roundUp) ? 10 : 9; // 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; - } -} - -namespace { - -// saveNumberRoundMode doesn't do quite enough for us. What we want is a -// Number::RoundModeGuard that sets the new mode and restores the old mode -// when it leaves scope. Since Number doesn't have that facility, we'll -// build it here. -class NumberRoundModeGuard -{ - saveNumberRoundMode saved_; - -public: - explicit NumberRoundModeGuard(Number::rounding_mode mode) noexcept - : saved_{Number::setround(mode)} - { - } - - NumberRoundModeGuard(NumberRoundModeGuard const&) = delete; - - NumberRoundModeGuard& - operator=(NumberRoundModeGuard const&) = delete; -}; - -// We need a class that has an interface similar to NumberRoundModeGuard -// but does nothing. -class DontAffectNumberRoundMode -{ -public: - explicit DontAffectNumberRoundMode(Number::rounding_mode mode) noexcept - { - } - - DontAffectNumberRoundMode(DontAffectNumberRoundMode const&) = delete; - - DontAffectNumberRoundMode& - operator=(DontAffectNumberRoundMode const&) = delete; -}; - -} // anonymous namespace - -// Pass the canonicalizeRound function pointer as a template parameter. -// -// We might need to use NumberRoundModeGuard. Allow the caller -// to pass either that or a replacement as a template parameter. -template < - void (*CanonicalizeFunc)(bool, std::uint64_t&, int&, bool), - typename MightSaveRound> -static STAmount -mulRoundImpl( - STAmount const& v1, - STAmount const& v2, - Asset const& asset, - bool roundUp) -{ - if (v1 == beast::zero || v2 == beast::zero) - return {asset}; - - bool const xrp = isXRP(asset); - - // TODO MPT - if (v1.native() && v2.native() && xrp) - { - std::uint64_t minV = - (getSNValue(v1) < getSNValue(v2)) ? getSNValue(v1) : getSNValue(v2); - std::uint64_t maxV = - (getSNValue(v1) < getSNValue(v2)) ? getSNValue(v2) : getSNValue(v1); - - if (minV > 3000000000ull) // sqrt(cMaxNative) - Throw("Native value overflow"); - - if (((maxV >> 32) * minV) > 2095475792ull) // cMaxNative / 2^32 - Throw("Native value overflow"); - - return STAmount(v1.getFName(), minV * maxV); - } - // TODO MPT - if (v1.isMPT() && v2.isMPT() && asset.isMPT()) - { - std::uint64_t minV = (getMPTValue(v1) < getMPTValue(v2)) - ? getMPTValue(v1) - : getMPTValue(v2); - std::uint64_t maxV = (getMPTValue(v1) < getMPTValue(v2)) - ? getMPTValue(v2) - : getMPTValue(v1); - - if (minV > 3000000000ull) // sqrt(cMaxNative) - Throw("Asset value overflow"); - - if (((maxV >> 32) * minV) > 2095475792ull) // cMaxNative / 2^32 - Throw("Asset value overflow"); - - return STAmount(asset, minV * maxV); - } - - std::uint64_t value1 = v1.mantissa(), value2 = v2.mantissa(); - int offset1 = v1.exponent(), offset2 = v2.exponent(); - - // TODO MPT - if (v1.native() || v1.isMPT()) - { - while (value1 < STAmount::cMinValue) - { - value1 *= 10; - --offset1; - } - } - - // TODO MPT - if (v2.native() || v2.isMPT()) - { - while (value2 < STAmount::cMinValue) - { - value2 *= 10; - --offset2; - } - } - - bool const resultNegative = v1.negative() != v2.negative(); - - // We multiply the two mantissas (each is between 10^15 - // and 10^16), so their product is in the 10^30 to 10^32 - // range. Dividing their product by 10^14 maintains the - // precision, by scaling the result to 10^16 to 10^18. - // - // If the we're rounding up, we want to round up away - // from zero, and if we're rounding down, truncation - // is implicit. - std::uint64_t amount = muldiv_round( - value1, value2, tenTo14, (resultNegative != roundUp) ? tenTo14m1 : 0); - - int offset = offset1 + offset2 + 14; - if (resultNegative != roundUp) - { - CanonicalizeFunc(xrp, amount, offset, roundUp); - } - STAmount result = [&]() { - // If appropriate, tell Number to round down. This gives the desired - // result from STAmount::canonicalize. - MightSaveRound const savedRound(Number::towards_zero); - return STAmount(asset, amount, offset, resultNegative); - }(); - - if (roundUp && !resultNegative && !result) - { - if (xrp) - { - // return the smallest value above zero - amount = 1; - offset = 0; - } - else - { - // return the smallest value above zero - amount = STAmount::cMinValue; - offset = STAmount::cMinOffset; - } - return STAmount(asset, amount, offset, resultNegative); - } - return result; + auto const res = multiply( + static_cast const&>(v1), + static_cast const&>(v2), + asset); + return STAmount{v1.getFName(), res}; } STAmount @@ -1678,8 +1009,12 @@ mulRound( Asset const& asset, bool roundUp) { - return mulRoundImpl( - v1, v2, asset, roundUp); + auto const res = mulRound( + static_cast const&>(v1), + static_cast const&>(v2), + asset, + roundUp); + return STAmount{v1.getFName(), res}; } STAmount @@ -1689,96 +1024,12 @@ mulRoundStrict( Asset const& asset, bool roundUp) { - return mulRoundImpl( - v1, v2, asset, roundUp); -} - -// We might need to use NumberRoundModeGuard. Allow the caller -// to pass either that or a replacement as a template parameter. -template -static STAmount -divRoundImpl( - STAmount const& num, - STAmount const& den, - Asset const& asset, - bool roundUp) -{ - if (den == beast::zero) - Throw("division by zero"); - - if (num == beast::zero) - return {asset}; - - std::uint64_t numVal = num.mantissa(), denVal = den.mantissa(); - int numOffset = num.exponent(), denOffset = den.exponent(); - - // TODO MPT - if (num.native() || num.isMPT()) - { - while (numVal < STAmount::cMinValue) - { - numVal *= 10; - --numOffset; - } - } - - // TODO MPT - if (den.native() || den.isMPT()) - { - while (denVal < STAmount::cMinValue) - { - denVal *= 10; - --denOffset; - } - } - - bool const resultNegative = (num.negative() != den.negative()); - - // We divide the two mantissas (each is between 10^15 - // and 10^16). To maintain precision, we multiply the - // numerator by 10^17 (the product is in the range of - // 10^32 to 10^33) followed by a division, so the result - // is in the range of 10^16 to 10^15. - // - // We round away from zero if we're rounding up or - // truncate if we're rounding down. - std::uint64_t amount = muldiv_round( - numVal, tenTo17, denVal, (resultNegative != roundUp) ? denVal - 1 : 0); - - int offset = numOffset - denOffset - 17; - - // TODO MPT - if (resultNegative != roundUp) - canonicalizeRound( - isXRP(asset) /*|| issue.isMPT()*/, amount, offset, roundUp); - - STAmount result = [&]() { - // If appropriate, tell Number the rounding mode we are using. - // Note that "roundUp == true" actually means "round away from zero". - // Otherwise round toward zero. - using enum Number::rounding_mode; - MightSaveRound const savedRound( - roundUp ^ resultNegative ? upward : downward); - return STAmount(asset, amount, offset, resultNegative); - }(); - - if (roundUp && !resultNegative && !result) - { - if (isXRP(asset) || asset.isMPT()) - { - // return the smallest value above zero - amount = 1; - offset = 0; - } - else - { - // return the smallest value above zero - amount = STAmount::cMinValue; - offset = STAmount::cMinOffset; - } - return STAmount(asset, amount, offset, resultNegative); - } - return result; + auto const res = mulRoundStrict( + static_cast const&>(v1), + static_cast const&>(v2), + asset, + roundUp); + return STAmount{v1.getFName(), res}; } STAmount @@ -1788,7 +1039,12 @@ divRound( Asset const& asset, bool roundUp) { - return divRoundImpl(num, den, asset, roundUp); + auto const res = divRound( + static_cast const&>(num), + static_cast const&>(den), + asset, + roundUp); + return STAmount{num.getFName(), res}; } STAmount @@ -1798,7 +1054,12 @@ divRoundStrict( Asset const& asset, bool roundUp) { - return divRoundImpl(num, den, asset, roundUp); + auto const res = divRoundStrict( + static_cast const&>(num), + static_cast const&>(den), + asset, + roundUp); + return STAmount{num.getFName(), res}; } } // namespace ripple diff --git a/src/test/jtx/balance.h b/src/test/jtx/balance.h index 3a2cf0423f4..ff1fb7877ca 100644 --- a/src/test/jtx/balance.h +++ b/src/test/jtx/balance.h @@ -44,7 +44,7 @@ class balance public: balance(Account const& account, none_t) - : none_(true), account_(account), value_(XRP) + : none_(true), account_(account), value_(xrpIssue()) { }