mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-19 10:35:50 +00:00
- Overflow Number -> int64 if it doesn't fit in the mantissa range. - Only enabled if at least one of the SingleAssetVault or LendingProtocol amendments are enabled. - Will throw the overflow error if the value is larger than maxMantissa. Current behavior is to throw if the value is larger than max int64_t value.
561 lines
14 KiB
C++
561 lines
14 KiB
C++
#ifndef XRPL_BASICS_NUMBER_H_INCLUDED
|
||
#define XRPL_BASICS_NUMBER_H_INCLUDED
|
||
|
||
#include <cstdint>
|
||
#include <limits>
|
||
#include <ostream>
|
||
#include <string>
|
||
|
||
namespace ripple {
|
||
|
||
class Number;
|
||
|
||
std::string
|
||
to_string(Number const& amount);
|
||
|
||
template <typename T>
|
||
constexpr bool
|
||
isPowerOfTen(T value)
|
||
{
|
||
while (value >= 10 && value % 10 == 0)
|
||
value /= 10;
|
||
return value == 1;
|
||
}
|
||
|
||
class Number
|
||
{
|
||
using rep = std::int64_t;
|
||
rep mantissa_{0};
|
||
int exponent_{std::numeric_limits<int>::lowest()};
|
||
|
||
// Within Number itself limited_ is informational only. It is not
|
||
// serialized, transmitted, or used in calculations in any way. It is used
|
||
// only to indicate that a given Number's absolute value _might_ need to be
|
||
// less than maxIntValue or maxMantissa.
|
||
//
|
||
// It is a one-way switch. Once it's on, it stays on. It is also
|
||
// transmissible in that any operation (e.g +, /, power, etc.) involving a
|
||
// Number with this flag will have a result with this flag.
|
||
//
|
||
// The flag is checked in the following places:
|
||
// 1. "fits()" indicates whether the Number fits into the safe range of
|
||
// -maxIntValue to maxIntValue.
|
||
// 2. "representable()" indicates whether the Number can accurately
|
||
// represent an integer, meaning that it fits withing the allowable range
|
||
// of -maxMantissa to maxMantissa. Values larger than this will be
|
||
// truncated before the decimal point, rendering the value inaccurate.
|
||
// 3. In "operator rep()", which explicitly converts the number into a
|
||
// 64-bit integer, if the Number is not representable(), AND one of the
|
||
// SingleAssetVault (or LendingProtocol, coming soon) amendments are
|
||
// enabled, the operator will throw a "std::overflow_error" as if the
|
||
// number had overflowed the limits of the 64-bit integer range.
|
||
//
|
||
// The Number is usually only going to be checked in transactions, based on
|
||
// the specific transaction logic, and is entirely context dependent.
|
||
//
|
||
bool limited_ = false;
|
||
|
||
public:
|
||
// The range for the mantissa when normalized
|
||
constexpr static rep minMantissa = 1'000'000'000'000'000LL;
|
||
static_assert(isPowerOfTen(minMantissa));
|
||
constexpr static rep maxMantissa = minMantissa * 10 - 1;
|
||
static_assert(maxMantissa == 9'999'999'999'999'999LL);
|
||
|
||
constexpr static rep maxIntValue = maxMantissa / 100;
|
||
static_assert(maxIntValue == 99'999'999'999'999LL);
|
||
|
||
// The range for the exponent when normalized
|
||
constexpr static int minExponent = -32768;
|
||
constexpr static int maxExponent = 32768;
|
||
|
||
struct unchecked
|
||
{
|
||
explicit unchecked() = default;
|
||
};
|
||
|
||
explicit constexpr Number() = default;
|
||
|
||
Number(rep mantissa, bool limited = false);
|
||
explicit Number(rep mantissa, int exponent, bool limited = false);
|
||
explicit constexpr Number(rep mantissa, int exponent, unchecked) noexcept;
|
||
constexpr Number(Number const& other) = default;
|
||
constexpr Number(Number&& other) = default;
|
||
|
||
~Number() = default;
|
||
|
||
constexpr Number&
|
||
operator=(Number const& other);
|
||
constexpr Number&
|
||
operator=(Number&& other);
|
||
|
||
constexpr rep
|
||
mantissa() const noexcept;
|
||
constexpr int
|
||
exponent() const noexcept;
|
||
|
||
// Sets the limited_ flag. See the description of limited for how it works.
|
||
// Note that the flag can only change from false to true, not from true to
|
||
// false.
|
||
void
|
||
setLimited(bool limited);
|
||
|
||
// Gets the current value of the limited_ flag. See the description of
|
||
// limited for how it works.
|
||
bool
|
||
getLimited() const noexcept;
|
||
|
||
// 1. "fits()" indicates whether the Number fits into the safe range of
|
||
// -maxIntValue to maxIntValue.
|
||
bool
|
||
fits() const noexcept;
|
||
bool
|
||
// 2. "representable()" indicates whether the Number can accurately
|
||
// represent an integer, meaning that it fits withing the allowable range
|
||
// of -maxMantissa to maxMantissa. Values larger than this will be
|
||
// truncated before the decimal point, rendering the value inaccurate.
|
||
representable() const noexcept;
|
||
/// Combines setLimited(bool) and fits()
|
||
bool
|
||
fits(bool limited);
|
||
/// Because this function is const, it should only be used for one-off
|
||
/// checks
|
||
bool
|
||
fits(bool limited) const;
|
||
|
||
constexpr Number
|
||
operator+() const noexcept;
|
||
constexpr Number
|
||
operator-() const noexcept;
|
||
Number&
|
||
operator++();
|
||
Number
|
||
operator++(int);
|
||
Number&
|
||
operator--();
|
||
Number
|
||
operator--(int);
|
||
|
||
Number&
|
||
operator+=(Number const& x);
|
||
Number&
|
||
operator-=(Number const& x);
|
||
|
||
Number&
|
||
operator*=(Number const& x);
|
||
Number&
|
||
operator/=(Number const& x);
|
||
|
||
static constexpr Number
|
||
min() noexcept;
|
||
static constexpr Number
|
||
max() noexcept;
|
||
static constexpr Number
|
||
lowest() noexcept;
|
||
|
||
/** Conversions to Number are implicit and conversions away from Number
|
||
* are explicit. This design encourages and facilitates the use of Number
|
||
* as the preferred type for floating point arithmetic as it makes
|
||
* "mixed mode" more convenient, e.g. MPTAmount + Number.
|
||
|
||
3. In "operator rep()", which explicitly converts the number into a
|
||
64-bit integer, if the Number is not representable(), AND one of the
|
||
SingleAssetVault (or LendingProtocol, coming soon) amendments are
|
||
enabled, the operator will throw a "std::overflow_error" as if the
|
||
number had overflowed the limits of the 64-bit integer range.
|
||
*/
|
||
explicit
|
||
operator rep() const; // round to nearest, even on tie
|
||
|
||
friend constexpr bool
|
||
operator==(Number const& x, Number const& y) noexcept
|
||
{
|
||
return x.mantissa_ == y.mantissa_ && x.exponent_ == y.exponent_;
|
||
}
|
||
|
||
friend constexpr bool
|
||
operator!=(Number const& x, Number const& y) noexcept
|
||
{
|
||
return !(x == y);
|
||
}
|
||
|
||
friend constexpr bool
|
||
operator<(Number const& x, Number const& y) noexcept
|
||
{
|
||
// If the two amounts have different signs (zero is treated as positive)
|
||
// then the comparison is true iff the left is negative.
|
||
bool const lneg = x.mantissa_ < 0;
|
||
bool const rneg = y.mantissa_ < 0;
|
||
|
||
if (lneg != rneg)
|
||
return lneg;
|
||
|
||
// Both have same sign and the left is zero: the right must be
|
||
// greater than 0.
|
||
if (x.mantissa_ == 0)
|
||
return y.mantissa_ > 0;
|
||
|
||
// Both have same sign, the right is zero and the left is non-zero.
|
||
if (y.mantissa_ == 0)
|
||
return false;
|
||
|
||
// Both have the same sign, compare by exponents:
|
||
if (x.exponent_ > y.exponent_)
|
||
return lneg;
|
||
if (x.exponent_ < y.exponent_)
|
||
return !lneg;
|
||
|
||
// If equal exponents, compare mantissas
|
||
return x.mantissa_ < y.mantissa_;
|
||
}
|
||
|
||
/** Return the sign of the amount */
|
||
constexpr int
|
||
signum() const noexcept
|
||
{
|
||
return (mantissa_ < 0) ? -1 : (mantissa_ ? 1 : 0);
|
||
}
|
||
|
||
Number
|
||
truncate() const noexcept
|
||
{
|
||
if (exponent_ >= 0 || mantissa_ == 0)
|
||
return *this;
|
||
|
||
Number ret = *this;
|
||
while (ret.exponent_ < 0 && ret.mantissa_ != 0)
|
||
{
|
||
ret.exponent_ += 1;
|
||
ret.mantissa_ /= rep(10);
|
||
}
|
||
// We are guaranteed that normalize() will never throw an exception
|
||
// because exponent is either negative or zero at this point.
|
||
ret.normalize();
|
||
return ret;
|
||
}
|
||
|
||
friend constexpr bool
|
||
operator>(Number const& x, Number const& y) noexcept
|
||
{
|
||
return y < x;
|
||
}
|
||
|
||
friend constexpr bool
|
||
operator<=(Number const& x, Number const& y) noexcept
|
||
{
|
||
return !(y < x);
|
||
}
|
||
|
||
friend constexpr bool
|
||
operator>=(Number const& x, Number const& y) noexcept
|
||
{
|
||
return !(x < y);
|
||
}
|
||
|
||
friend std::ostream&
|
||
operator<<(std::ostream& os, Number const& x)
|
||
{
|
||
return os << to_string(x);
|
||
}
|
||
|
||
// Thread local rounding control. Default is to_nearest
|
||
enum rounding_mode { to_nearest, towards_zero, downward, upward };
|
||
static rounding_mode
|
||
getround();
|
||
// Returns previously set mode
|
||
static rounding_mode
|
||
setround(rounding_mode mode);
|
||
|
||
// Thread local integer overflow control. See overflowLargeIntegers_ for
|
||
// more info.
|
||
static bool
|
||
getEnforceIntegerOverflow();
|
||
// Thread local integer overflow control. See overflowLargeIntegers_ for
|
||
// more info.
|
||
static void
|
||
setEnforceIntegerOverflow(bool enforce);
|
||
|
||
private:
|
||
static thread_local rounding_mode mode_;
|
||
|
||
// This flag defaults to false. It is set and cleared by
|
||
// "setCurrentTransactionRules" in Rules.cpp. It will be set to true if and
|
||
// only if any of the SingleAssetVault or LendingProtocol amendments are
|
||
// enabled.
|
||
//
|
||
// If set, then any explicit conversions from Number to rep (which is
|
||
// std::int64_t) of Numbers that are not representable (which means their
|
||
// magnitude is larger than maxMantissa, and thus they will lose integer
|
||
// precision) will throw a std::overflow_error. Note that this coversion
|
||
// will already throw an overflow error if the Number is larger 2^63.
|
||
// See also "operator rep()".
|
||
static thread_local bool overflowLargeIntegers_;
|
||
|
||
void
|
||
normalize();
|
||
constexpr bool
|
||
isnormal() const noexcept;
|
||
|
||
class Guard;
|
||
};
|
||
|
||
inline constexpr Number::Number(rep mantissa, int exponent, unchecked) noexcept
|
||
: mantissa_{mantissa}, exponent_{exponent}
|
||
{
|
||
}
|
||
|
||
inline Number::Number(rep mantissa, int exponent, bool limited)
|
||
: mantissa_{mantissa}, exponent_{exponent}, limited_(limited)
|
||
{
|
||
normalize();
|
||
}
|
||
|
||
inline Number::Number(rep mantissa, bool limited) : Number{mantissa, 0, limited}
|
||
{
|
||
}
|
||
|
||
constexpr Number&
|
||
Number::operator=(Number const& other)
|
||
{
|
||
if (this != &other)
|
||
{
|
||
mantissa_ = other.mantissa_;
|
||
exponent_ = other.exponent_;
|
||
if (!limited_)
|
||
limited_ = other.limited_;
|
||
}
|
||
|
||
return *this;
|
||
}
|
||
|
||
constexpr Number&
|
||
Number::operator=(Number&& other)
|
||
{
|
||
if (this != &other)
|
||
{
|
||
// std::move doesn't really do anything for these types, but
|
||
// this is future-proof in case the types ever change
|
||
mantissa_ = std::move(other.mantissa_);
|
||
exponent_ = std::move(other.exponent_);
|
||
if (!limited_)
|
||
limited_ = std::move(other.limited_);
|
||
}
|
||
|
||
return *this;
|
||
}
|
||
|
||
inline constexpr Number::rep
|
||
Number::mantissa() const noexcept
|
||
{
|
||
return mantissa_;
|
||
}
|
||
|
||
inline constexpr int
|
||
Number::exponent() const noexcept
|
||
{
|
||
return exponent_;
|
||
}
|
||
|
||
inline void
|
||
Number::setLimited(bool limited)
|
||
{
|
||
if (limited_)
|
||
return;
|
||
limited_ = limited;
|
||
}
|
||
|
||
inline bool
|
||
Number::getLimited() const noexcept
|
||
{
|
||
return limited_;
|
||
}
|
||
|
||
inline constexpr Number
|
||
Number::operator+() const noexcept
|
||
{
|
||
return *this;
|
||
}
|
||
|
||
inline constexpr Number
|
||
Number::operator-() const noexcept
|
||
{
|
||
auto x = *this;
|
||
x.mantissa_ = -x.mantissa_;
|
||
return x;
|
||
}
|
||
|
||
inline Number&
|
||
Number::operator++()
|
||
{
|
||
*this += Number{1000000000000000, -15, unchecked{}};
|
||
return *this;
|
||
}
|
||
|
||
inline Number
|
||
Number::operator++(int)
|
||
{
|
||
auto x = *this;
|
||
++(*this);
|
||
return x;
|
||
}
|
||
|
||
inline Number&
|
||
Number::operator--()
|
||
{
|
||
*this -= Number{1000000000000000, -15, unchecked{}};
|
||
return *this;
|
||
}
|
||
|
||
inline Number
|
||
Number::operator--(int)
|
||
{
|
||
auto x = *this;
|
||
--(*this);
|
||
return x;
|
||
}
|
||
|
||
inline Number&
|
||
Number::operator-=(Number const& x)
|
||
{
|
||
return *this += -x;
|
||
}
|
||
|
||
inline Number
|
||
operator+(Number const& x, Number const& y)
|
||
{
|
||
auto z = x;
|
||
z += y;
|
||
return z;
|
||
}
|
||
|
||
inline Number
|
||
operator-(Number const& x, Number const& y)
|
||
{
|
||
auto z = x;
|
||
z -= y;
|
||
return z;
|
||
}
|
||
|
||
inline Number
|
||
operator*(Number const& x, Number const& y)
|
||
{
|
||
auto z = x;
|
||
z *= y;
|
||
return z;
|
||
}
|
||
|
||
inline Number
|
||
operator/(Number const& x, Number const& y)
|
||
{
|
||
auto z = x;
|
||
z /= y;
|
||
return z;
|
||
}
|
||
|
||
inline constexpr Number
|
||
Number::min() noexcept
|
||
{
|
||
return Number{minMantissa, minExponent, unchecked{}};
|
||
}
|
||
|
||
inline constexpr Number
|
||
Number::max() noexcept
|
||
{
|
||
return Number{maxMantissa, maxExponent, unchecked{}};
|
||
}
|
||
|
||
inline constexpr Number
|
||
Number::lowest() noexcept
|
||
{
|
||
return -Number{maxMantissa, maxExponent, unchecked{}};
|
||
}
|
||
|
||
inline constexpr bool
|
||
Number::isnormal() const noexcept
|
||
{
|
||
auto const abs_m = mantissa_ < 0 ? -mantissa_ : mantissa_;
|
||
return minMantissa <= abs_m && abs_m <= maxMantissa &&
|
||
minExponent <= exponent_ && exponent_ <= maxExponent;
|
||
}
|
||
|
||
inline constexpr Number
|
||
abs(Number x) noexcept
|
||
{
|
||
if (x < Number{})
|
||
x = -x;
|
||
return x;
|
||
}
|
||
|
||
// Returns f^n
|
||
// Uses a log_2(n) number of multiplications
|
||
|
||
Number
|
||
power(Number const& f, unsigned n);
|
||
|
||
// Returns f^(1/d)
|
||
// Uses Newton–Raphson iterations until the result stops changing
|
||
// to find the root of the polynomial g(x) = x^d - f
|
||
|
||
Number
|
||
root(Number f, unsigned d);
|
||
|
||
Number
|
||
root2(Number f);
|
||
|
||
// Returns f^(n/d)
|
||
|
||
Number
|
||
power(Number const& f, unsigned n, unsigned d);
|
||
|
||
// Return 0 if abs(x) < limit, else returns x
|
||
|
||
inline constexpr Number
|
||
squelch(Number const& x, Number const& limit) noexcept
|
||
{
|
||
if (abs(x) < limit)
|
||
return Number{};
|
||
return x;
|
||
}
|
||
|
||
class saveNumberRoundMode
|
||
{
|
||
Number::rounding_mode mode_;
|
||
|
||
public:
|
||
~saveNumberRoundMode()
|
||
{
|
||
Number::setround(mode_);
|
||
}
|
||
explicit saveNumberRoundMode(Number::rounding_mode mode) noexcept
|
||
: mode_{mode}
|
||
{
|
||
}
|
||
saveNumberRoundMode(saveNumberRoundMode const&) = delete;
|
||
saveNumberRoundMode&
|
||
operator=(saveNumberRoundMode const&) = delete;
|
||
};
|
||
|
||
// 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;
|
||
};
|
||
|
||
} // namespace ripple
|
||
|
||
#endif // XRPL_BASICS_NUMBER_H_INCLUDED
|