mirror of
https://github.com/XRPLF/rippled.git
synced 2026-06-02 16:26:48 +00:00
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Co-authored-by: Vito Tumas <5780819+Tapanito@users.noreply.github.com>
935 lines
27 KiB
C++
935 lines
27 KiB
C++
#pragma once
|
||
|
||
#include <xrpl/beast/utility/instrumentation.h>
|
||
|
||
#include <array>
|
||
#include <cstdint>
|
||
#include <functional>
|
||
#include <limits>
|
||
#include <optional>
|
||
#include <ostream>
|
||
#include <set>
|
||
#include <stdexcept>
|
||
#include <string>
|
||
#include <unordered_map>
|
||
|
||
namespace xrpl {
|
||
|
||
class Number;
|
||
|
||
std::string
|
||
to_string(Number const& amount);
|
||
|
||
template <typename T>
|
||
constexpr std::optional<int>
|
||
logTen(T value)
|
||
{
|
||
int log = 0;
|
||
while (value >= 10 && value % 10 == 0)
|
||
{
|
||
value /= 10;
|
||
++log;
|
||
}
|
||
if (value == 1)
|
||
return log;
|
||
return std::nullopt;
|
||
}
|
||
|
||
template <typename T>
|
||
constexpr bool
|
||
isPowerOfTen(T value)
|
||
{
|
||
return logTen(value).has_value();
|
||
}
|
||
|
||
namespace detail {
|
||
|
||
/** Builds a table of the powers of 10
|
||
*
|
||
* This function is marked consteval, so it can only be run in
|
||
* a constexpr context. This assures that it is and can only be run at
|
||
* compile time. Doing it at runtime would be pretty wasteful and
|
||
* inefficient.
|
||
*/
|
||
constexpr std::size_t kInt64Digits = 20;
|
||
consteval std::array<std::uint64_t, kInt64Digits>
|
||
buildPowersOfTen()
|
||
{
|
||
std::array<std::uint64_t, kInt64Digits> result{};
|
||
|
||
std::uint64_t power = 1;
|
||
std::size_t exponent = 0;
|
||
// end the loop early so it doesn't overflow;
|
||
for (; exponent < result.size() - 1; ++exponent, power *= 10)
|
||
{
|
||
result[exponent] = power;
|
||
if (power > std::numeric_limits<std::uint64_t>::max() / 10)
|
||
throw std::logic_error("Power of 10 table is too big");
|
||
}
|
||
result[exponent] = power;
|
||
if (power < std::numeric_limits<std::uint64_t>::max() / 10)
|
||
throw std::logic_error("Power of 10 table is not big enough for the uint64_t type");
|
||
|
||
return result;
|
||
}
|
||
|
||
} // namespace detail
|
||
|
||
constexpr std::array<std::uint64_t, detail::kInt64Digits> kPowerOfTen = detail::buildPowersOfTen();
|
||
|
||
static_assert(kPowerOfTen[0] == 1);
|
||
static_assert(kPowerOfTen[1] == 10);
|
||
static_assert(kPowerOfTen[10] == 10'000'000'000);
|
||
static_assert(
|
||
isPowerOfTen(kPowerOfTen.back()) && *logTen(kPowerOfTen.back()) == detail::kInt64Digits - 1);
|
||
|
||
/** MantissaRange defines a range for the mantissa of a normalized Number.
|
||
*
|
||
* The mantissa is in the range [min, max], where
|
||
* * min is a power of 10, and
|
||
* * max = min * 10 - 1.
|
||
*
|
||
* The MantissaScale enum indicates properties of the range: size, and some behavioral
|
||
* options. This intentionally restricts the number of unique MantissaRanges that can
|
||
* be instantiated: one for each scale.
|
||
*
|
||
* The "Small" scale is based on the behavior of STAmount for IOUs. It has a min
|
||
* value of 10^15, and a max value of 10^16-1. This was sufficient for
|
||
* uses before Lending Protocol was implemented, mostly related to AMM.
|
||
*
|
||
* However, it does not have sufficient precision to represent the full integer
|
||
* range of int64_t values (-2^63 to 2^63-1), which are needed for XRP and MPT
|
||
* values. The implementation of SingleAssetVault, and LendingProtocol need to
|
||
* represent those integer values accurately and precisely, both for the
|
||
* STNumber field type, and for internal calculations. That necessitated the
|
||
* "large" scale.
|
||
*
|
||
* The "Large" scales are intended to represent all values that can be represented
|
||
* by an STAmount - IOUs, XRP, and MPTs. It has a min value of 10^18, and a max
|
||
* value of 10^19-1. "LargeLegacy" is like "Large", but preserves
|
||
* a rounding error when a computation results in a mantissa of
|
||
* Number::kMaxRep that needs to be rounded up, but rounds down
|
||
* instead. It will maintain consistent behavior until the fixCleanup3_2_0
|
||
* amendment is enabled.
|
||
*
|
||
* Note that if the mentioned amendments are eventually retired, this class
|
||
* should be left in place, but the "Small" scale option should be removed. This
|
||
* will allow for future expansion beyond 64-bits if it is ever needed.
|
||
*/
|
||
struct MantissaRange final
|
||
{
|
||
using rep = std::uint64_t;
|
||
|
||
enum class MantissaScale {
|
||
Small,
|
||
// LargeLegacy can be removed when fixCleanup3_2_0 is retired
|
||
LargeLegacy,
|
||
Large,
|
||
};
|
||
|
||
// This entire enum can be removed when fixCleanup3_2_0 is retired
|
||
enum class CuspRoundingFix : bool {
|
||
Disabled = false,
|
||
Enabled = true,
|
||
};
|
||
|
||
explicit constexpr MantissaRange(MantissaScale sc) : scale(sc)
|
||
{
|
||
}
|
||
|
||
MantissaScale const scale;
|
||
int const log{getExponent(scale)};
|
||
rep const min{getMin(scale, log)};
|
||
rep const max{(min * 10) - 1};
|
||
CuspRoundingFix const cuspRoundingFixEnabled{isCuspFixEnabled(scale)};
|
||
|
||
static MantissaRange const&
|
||
getMantissaRange(MantissaScale scale);
|
||
|
||
static std::set<MantissaScale> const&
|
||
getAllScales();
|
||
|
||
private:
|
||
static constexpr int
|
||
getExponent(MantissaScale scale)
|
||
{
|
||
switch (scale)
|
||
{
|
||
case MantissaScale::Small:
|
||
return 15;
|
||
case MantissaScale::LargeLegacy:
|
||
case MantissaScale::Large:
|
||
return 18;
|
||
// LCOV_EXCL_START
|
||
default:
|
||
// If called in a constexpr context, this throw assures that the build fails if an
|
||
// invalid scale is used.
|
||
throw std::runtime_error("Unknown mantissa scale");
|
||
// LCOV_EXCL_STOP
|
||
}
|
||
}
|
||
|
||
// Keep this function for future use with different ways to compute
|
||
// the ranges.
|
||
static constexpr rep
|
||
getMin(MantissaScale scale, int exponent)
|
||
{
|
||
if (exponent < 0 || exponent >= kPowerOfTen.size())
|
||
throw std::runtime_error("Invalid exponent"); // LCOV_EXCL_LINE
|
||
return kPowerOfTen[exponent];
|
||
}
|
||
|
||
static constexpr CuspRoundingFix
|
||
isCuspFixEnabled(MantissaScale scale)
|
||
{
|
||
switch (scale)
|
||
{
|
||
case MantissaScale::Small:
|
||
case MantissaScale::LargeLegacy:
|
||
return CuspRoundingFix::Disabled;
|
||
case MantissaScale::Large:
|
||
return CuspRoundingFix::Enabled;
|
||
default:
|
||
// If called in a constexpr context, this throw assures that the build fails if an
|
||
// invalid scale is used.
|
||
throw std::runtime_error("Unknown mantissa scale"); // LCOV_EXCL_LINE
|
||
}
|
||
}
|
||
|
||
static std::unordered_map<MantissaScale, MantissaRange> const&
|
||
getRanges();
|
||
};
|
||
|
||
// Like std::integral, but only 64-bit integral types.
|
||
template <class T>
|
||
concept Integral64 = std::is_same_v<T, std::int64_t> || std::is_same_v<T, std::uint64_t>;
|
||
|
||
/** Number is a floating point type that can represent a wide range of values.
|
||
*
|
||
* It can represent all values that can be represented by an STAmount -
|
||
* regardless of asset type - XRPAmount, MPTAmount, and IOUAmount, with at least
|
||
* as much precision as those types require.
|
||
*
|
||
* ---- Internal Representation ----
|
||
*
|
||
* Internally, Number is represented with three values:
|
||
* 1. a bool sign flag,
|
||
* 2. a std::uint64_t mantissa,
|
||
* 3. an int exponent.
|
||
*
|
||
* The internal mantissa is an unsigned integer in the range defined by the
|
||
* current MantissaRange. The exponent is an integer in the range
|
||
* [minExponent, maxExponent].
|
||
*
|
||
* See the description of MantissaRange for more details on the ranges.
|
||
*
|
||
* A non-zero mantissa is (almost) always normalized, meaning it and the
|
||
* exponent are grown or shrunk until the mantissa is in the range
|
||
* [MantissaRange.min, MantissaRange.max].
|
||
*
|
||
* Note:
|
||
* 1. Normalization can be disabled by using the "unchecked" ctor tag. This
|
||
* should only be used at specific conversion points, some constexpr
|
||
* values, and in unit tests.
|
||
* 2. The max of the "large" range, 10^19-1, is the largest 10^X-1 value that
|
||
* fits in an unsigned 64-bit number. (10^19-1 < 2^64-1 and
|
||
* 10^20-1 > 2^64-1). This avoids under- and overflows.
|
||
*
|
||
* ---- External Interface ----
|
||
*
|
||
* The external interface of Number consists of a std::int64_t mantissa, which
|
||
* is restricted to 63-bits, and an int exponent, which must be in the range
|
||
* [minExponent, maxExponent]. The range of the mantissa depends on which
|
||
* MantissaRange is currently active. For the "short" range, the mantissa will
|
||
* be between 10^15 and 10^16-1. For the "large" range, the mantissa will be
|
||
* between -(2^63-1) and 2^63-1. As noted above, the "large" range is needed to
|
||
* represent the full range of valid XRP and MPT integer values accurately.
|
||
*
|
||
* Note:
|
||
* 1. 2^63-1 is between 10^18 and 10^19-1, which are the limits of the "large"
|
||
* mantissa range.
|
||
* 2. The functions mantissa() and exponent() return the external view of the
|
||
* Number value, specifically using a signed 63-bit mantissa. This may
|
||
* require altering the internal representation to fit into that range
|
||
* before the value is returned. The interface guarantees consistency of
|
||
* the two values.
|
||
* 3. Number cannot represent -2^63 (std::numeric_limits<std::int64_t>::min())
|
||
* as an exact integer, but it doesn't need to, because all asset values
|
||
* on-ledger are non-negative. This is due to implementation details of
|
||
* several operations which use unsigned arithmetic internally. This is
|
||
* sufficient to represent all valid XRP values (where the absolute value
|
||
* can not exceed INITIAL_XRP: 10^17), and MPT values (where the absolute
|
||
* value can not exceed maxMPTokenAmount: 2^63-1).
|
||
*
|
||
* ---- Mantissa Range Switching ----
|
||
*
|
||
* The mantissa range may be changed at runtime via setMantissaScale(). The
|
||
* default mantissa range is "large". The range is updated whenever transaction
|
||
* processing begins, based on whether SingleAssetVault or LendingProtocol are
|
||
* enabled. If either is enabled, the mantissa range is set to "large". If not,
|
||
* it is set to "small", preserving backward compatibility and correct
|
||
* "amendment-gating".
|
||
*
|
||
* It is extremely unlikely that any more calls to setMantissaScale() will be
|
||
* needed outside of unit tests.
|
||
*
|
||
* ---- Usage With Different Ranges ----
|
||
*
|
||
* Outside of unit tests, and existing checks, code that uses Number should not
|
||
* know or care which mantissa range is active.
|
||
*
|
||
* The results of computations using Numbers with a small mantissa may differ
|
||
* from computations using Numbers with a large mantissa, specifically as it
|
||
* effects the results after rounding. That is why the large mantissa range is
|
||
* amendment gated in transaction processing.
|
||
*
|
||
* It is extremely unlikely that any more calls to getMantissaScale() will be
|
||
* needed outside of unit tests.
|
||
*
|
||
* Code that uses Number should not assume or check anything about the
|
||
* mantissa() or exponent() except that they fit into the "large" range
|
||
* specified in the "External Interface" section.
|
||
*
|
||
* ----- Unit Tests -----
|
||
*
|
||
* Within unit tests, it may be useful to explicitly switch between the two
|
||
* ranges, or to check which range is active when checking the results of
|
||
* computations. If the test is doing the math directly, the
|
||
* set/getMantissaScale() functions may be most appropriate. However, if the
|
||
* test has anything to do with transaction processing, it should enable or
|
||
* disable the amendments that control the mantissa range choice
|
||
* (SingleAssetVault and LendingProtocol), and/or check if either of those
|
||
* amendments are enabled to determine which result to expect.
|
||
*
|
||
*/
|
||
class Number final
|
||
{
|
||
using rep = std::int64_t;
|
||
using internalrep = MantissaRange::rep;
|
||
|
||
bool negative_{false};
|
||
internalrep mantissa_{0};
|
||
int exponent_{std::numeric_limits<int>::lowest()};
|
||
|
||
public:
|
||
// The range for the exponent when normalized
|
||
static constexpr int kMinExponent = -32768;
|
||
static constexpr int kMaxExponent = 32768;
|
||
|
||
static constexpr internalrep kMaxRep = std::numeric_limits<rep>::max();
|
||
static_assert(kMaxRep == 9'223'372'036'854'775'807);
|
||
static_assert(-kMaxRep == std::numeric_limits<rep>::min() + 1);
|
||
|
||
// May need to make unchecked private
|
||
struct Unchecked
|
||
{
|
||
explicit Unchecked() = default;
|
||
};
|
||
|
||
// Like unchecked, normalized is used with the ctors that take an
|
||
// internalrep mantissa. Unlike unchecked, those ctors will normalize the
|
||
// value.
|
||
// Only unit tests are expected to use this class
|
||
struct Normalized
|
||
{
|
||
explicit Normalized() = default;
|
||
};
|
||
|
||
explicit constexpr Number() = default;
|
||
|
||
Number(rep mantissa);
|
||
explicit Number(rep mantissa, int exponent);
|
||
explicit constexpr Number(
|
||
bool negative,
|
||
internalrep mantissa,
|
||
int exponent,
|
||
Unchecked) noexcept;
|
||
// Assume unsigned values are... unsigned. i.e. positive
|
||
explicit constexpr Number(internalrep mantissa, int exponent, Unchecked) noexcept;
|
||
// Only unit tests are expected to use this ctor
|
||
explicit Number(bool negative, internalrep mantissa, int exponent, Normalized);
|
||
// Assume unsigned values are... unsigned. i.e. positive
|
||
explicit Number(internalrep mantissa, int exponent, Normalized);
|
||
|
||
[[nodiscard]] constexpr rep
|
||
mantissa() const noexcept;
|
||
[[nodiscard]] constexpr int
|
||
exponent() const noexcept;
|
||
|
||
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 Number
|
||
min() noexcept;
|
||
static Number
|
||
max() noexcept;
|
||
static 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.
|
||
*/
|
||
explicit
|
||
operator rep() const; // round to nearest, even on tie
|
||
|
||
friend constexpr bool
|
||
operator==(Number const& x, Number const& y) noexcept
|
||
{
|
||
return x.negative_ == y.negative_ && 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.negative_;
|
||
bool const rneg = y.negative_;
|
||
|
||
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 */
|
||
[[nodiscard]] constexpr int
|
||
signum() const noexcept
|
||
{
|
||
if (negative_)
|
||
return -1;
|
||
return (mantissa_ != 0u) ? 1 : 0;
|
||
}
|
||
|
||
[[nodiscard]] Number
|
||
truncate() const noexcept;
|
||
|
||
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);
|
||
}
|
||
|
||
friend std::string
|
||
to_string(Number const& amount);
|
||
|
||
friend Number
|
||
root(Number f, unsigned d);
|
||
|
||
friend Number
|
||
root2(Number f);
|
||
|
||
// Thread local rounding control. Default is to_nearest
|
||
enum class RoundingMode { ToNearest, TowardsZero, Downward, Upward };
|
||
|
||
static RoundingMode
|
||
getround();
|
||
|
||
static RoundingMode
|
||
setround(RoundingMode inMode);
|
||
|
||
/** Returns which mantissa scale is currently in use for normalization.
|
||
*
|
||
* If you think you need to call this outside of unit tests, no you don't.
|
||
*/
|
||
static MantissaRange::MantissaScale
|
||
getMantissaScale();
|
||
|
||
/** Changes which mantissa scale is used for normalization.
|
||
*
|
||
* If you think you need to call this outside of unit tests, no you don't.
|
||
*/
|
||
static void
|
||
setMantissaScale(MantissaRange::MantissaScale scale);
|
||
|
||
static internalrep
|
||
minMantissa()
|
||
{
|
||
return kRange.get().min;
|
||
}
|
||
|
||
static internalrep
|
||
maxMantissa()
|
||
{
|
||
return kRange.get().max;
|
||
}
|
||
|
||
static int
|
||
mantissaLog()
|
||
{
|
||
return kRange.get().log;
|
||
}
|
||
|
||
static Number
|
||
one();
|
||
|
||
template <
|
||
auto MinMantissa,
|
||
auto MaxMantissa,
|
||
Integral64 T = std::decay_t<decltype(MinMantissa)>>
|
||
[[nodiscard]]
|
||
std::pair<T, int>
|
||
normalizeToRange() const;
|
||
|
||
private:
|
||
static thread_local RoundingMode mode;
|
||
// The available ranges for mantissa
|
||
|
||
// The range for the mantissa when normalized.
|
||
// Use reference_wrapper to avoid making copies, and prevent accidentally
|
||
// changing the values inside the range.
|
||
static thread_local std::reference_wrapper<MantissaRange const> kRange;
|
||
|
||
void
|
||
normalize(MantissaRange const& range);
|
||
|
||
/** Normalize Number components to an arbitrary range.
|
||
*
|
||
* min/maxMantissa are parameters because this function is used by both
|
||
* normalize(), which reads from kRange, and by normalizeToRange,
|
||
* which is public and can accept an arbitrary range from the caller.
|
||
*/
|
||
template <class T>
|
||
static void
|
||
normalize(
|
||
bool& negative,
|
||
T& mantissa,
|
||
int& exponent,
|
||
internalrep const& minMantissa,
|
||
internalrep const& maxMantissa,
|
||
MantissaRange::CuspRoundingFix cuspRoundingFixEnabled);
|
||
|
||
template <class T>
|
||
friend void
|
||
doNormalize(
|
||
bool& negative,
|
||
T& mantissa,
|
||
int& exponent,
|
||
MantissaRange::rep const& minMantissa,
|
||
MantissaRange::rep const& maxMantissa,
|
||
MantissaRange::CuspRoundingFix cuspRoundingFixEnabled,
|
||
bool dropped);
|
||
|
||
[[nodiscard]] bool
|
||
isnormal() const noexcept;
|
||
|
||
// Copy the number, but modify the exponent by "exponentDelta". Because the
|
||
// mantissa doesn't change, the result will be "mostly" normalized, but the
|
||
// exponent could go out of range, so it will be checked.
|
||
[[nodiscard]] Number
|
||
shiftExponent(int exponentDelta) const;
|
||
|
||
// Safely convert rep (int64) mantissa to internalrep (uint64). If the rep
|
||
// is negative, returns the positive value. This takes a little extra work
|
||
// because converting std::numeric_limits<std::int64_t>::min() flirts with
|
||
// UB, and can vary across compilers.
|
||
static internalrep
|
||
externalToInternal(rep mantissa);
|
||
|
||
class Guard;
|
||
};
|
||
|
||
constexpr Number::Number(bool negative, internalrep mantissa, int exponent, Unchecked) noexcept
|
||
: negative_(negative), mantissa_{mantissa}, exponent_{exponent}
|
||
{
|
||
}
|
||
|
||
constexpr Number::Number(internalrep mantissa, int exponent, Unchecked) noexcept
|
||
: Number(false, mantissa, exponent, Unchecked{})
|
||
{
|
||
}
|
||
|
||
static constexpr Number kNumZero{};
|
||
|
||
inline Number::Number(bool negative, internalrep mantissa, int exponent, Normalized)
|
||
: Number(negative, mantissa, exponent, Unchecked{})
|
||
{
|
||
normalize(kRange);
|
||
}
|
||
|
||
inline Number::Number(internalrep mantissa, int exponent, Normalized)
|
||
: Number(false, mantissa, exponent, Normalized{})
|
||
{
|
||
}
|
||
|
||
inline Number::Number(rep mantissa, int exponent)
|
||
: Number(mantissa < 0, externalToInternal(mantissa), exponent, Normalized{})
|
||
{
|
||
}
|
||
|
||
inline Number::Number(rep mantissa) : Number{mantissa, 0}
|
||
{
|
||
}
|
||
|
||
/** Returns the mantissa of the external view of the Number.
|
||
*
|
||
* Please see the "---- External Interface ----" section of the class
|
||
* documentation for an explanation of why the internal value may be modified.
|
||
*/
|
||
constexpr Number::rep
|
||
Number::mantissa() const noexcept
|
||
{
|
||
auto m = mantissa_;
|
||
if (m > kMaxRep)
|
||
{
|
||
XRPL_ASSERT_PARTS(
|
||
!isnormal() || (m % 10 == 0 && m / 10 <= kMaxRep),
|
||
"xrpl::Number::mantissa",
|
||
"large normalized mantissa has no remainder");
|
||
m /= 10;
|
||
}
|
||
auto const sign = negative_ ? -1 : 1;
|
||
return sign * static_cast<Number::rep>(m);
|
||
}
|
||
|
||
/** Returns the exponent of the external view of the Number.
|
||
*
|
||
* Please see the "---- External Interface ----" section of the class
|
||
* documentation for an explanation of why the internal value may be modified.
|
||
*/
|
||
constexpr int
|
||
Number::exponent() const noexcept
|
||
{
|
||
auto e = exponent_;
|
||
if (mantissa_ > kMaxRep)
|
||
{
|
||
XRPL_ASSERT_PARTS(
|
||
!isnormal() || (mantissa_ % 10 == 0 && mantissa_ / 10 <= kMaxRep),
|
||
"xrpl::Number::exponent",
|
||
"large normalized mantissa has no remainder");
|
||
++e;
|
||
}
|
||
return e;
|
||
}
|
||
|
||
constexpr Number
|
||
Number::operator+() const noexcept
|
||
{
|
||
return *this;
|
||
}
|
||
|
||
constexpr Number
|
||
Number::operator-() const noexcept
|
||
{
|
||
if (mantissa_ == 0)
|
||
return Number{};
|
||
auto x = *this;
|
||
x.negative_ = !x.negative_;
|
||
return x;
|
||
}
|
||
|
||
inline Number&
|
||
Number::operator++()
|
||
{
|
||
*this += one();
|
||
return *this;
|
||
}
|
||
|
||
inline Number
|
||
Number::operator++(int)
|
||
{
|
||
auto x = *this;
|
||
++(*this);
|
||
return x;
|
||
}
|
||
|
||
inline Number&
|
||
Number::operator--()
|
||
{
|
||
*this -= one();
|
||
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 Number
|
||
Number::min() noexcept
|
||
{
|
||
return Number{false, kRange.get().min, kMinExponent, Unchecked{}};
|
||
}
|
||
|
||
inline Number
|
||
Number::max() noexcept
|
||
{
|
||
return Number{false, std::min(kRange.get().max, kMaxRep), kMaxExponent, Unchecked{}};
|
||
}
|
||
|
||
inline Number
|
||
Number::lowest() noexcept
|
||
{
|
||
return Number{true, std::min(kRange.get().max, kMaxRep), kMaxExponent, Unchecked{}};
|
||
}
|
||
|
||
inline bool
|
||
Number::isnormal() const noexcept
|
||
{
|
||
MantissaRange const& range = kRange;
|
||
auto const absM = mantissa_;
|
||
return *this == Number{} ||
|
||
(range.min <= absM && absM <= range.max && (absM <= kMaxRep || absM % 10 == 0) &&
|
||
kMinExponent <= exponent_ && exponent_ <= kMaxExponent);
|
||
}
|
||
|
||
template <auto MinMantissa, auto MaxMantissa, Integral64 T>
|
||
std::pair<T, int>
|
||
Number::normalizeToRange() const
|
||
{
|
||
static_assert(std::is_same_v<T, std::uint64_t> || std::is_same_v<T, std::int64_t>);
|
||
static_assert(std::is_same_v<T, std::decay_t<decltype(MinMantissa)>>);
|
||
static_assert(std::is_same_v<T, std::decay_t<decltype(MaxMantissa)>>);
|
||
auto constexpr kMIN = static_cast<T>(MinMantissa);
|
||
auto constexpr kMAX = static_cast<T>(MaxMantissa);
|
||
static_assert(kMIN > 0);
|
||
static_assert(kMIN % 10 == 0);
|
||
static_assert(isPowerOfTen(kMIN));
|
||
static_assert(kMAX % 10 == 9);
|
||
static_assert((kMAX + 1) / 10 == kMIN);
|
||
|
||
bool negative = negative_;
|
||
internalrep mantissa = mantissa_;
|
||
int exponent = exponent_;
|
||
|
||
if constexpr (std::is_unsigned_v<T>)
|
||
{
|
||
XRPL_ASSERT_PARTS(
|
||
!negative,
|
||
"xrpl::Number::normalizeToRange",
|
||
"Number is non-negative for unsigned range.");
|
||
}
|
||
// Don't need to worry about the cuspRounding fix because rounding up will never take the
|
||
// mantissa over maxMantissa with a ones digit value other than 0. 0 can safely be truncated.
|
||
Number::normalize(
|
||
negative, mantissa, exponent, kMIN, kMAX, MantissaRange::CuspRoundingFix::Disabled);
|
||
|
||
auto const sign = negative ? -1 : 1;
|
||
return std::make_pair(static_cast<T>(sign * mantissa), exponent);
|
||
}
|
||
|
||
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
|
||
|
||
constexpr Number
|
||
squelch(Number const& x, Number const& limit) noexcept
|
||
{
|
||
if (abs(x) < limit)
|
||
return Number{};
|
||
return x;
|
||
}
|
||
|
||
inline std::string
|
||
to_string(MantissaRange::MantissaScale const& scale)
|
||
{
|
||
switch (scale)
|
||
{
|
||
case MantissaRange::MantissaScale::Small:
|
||
return "small";
|
||
case MantissaRange::MantissaScale::LargeLegacy:
|
||
return "largeLegacy";
|
||
case MantissaRange::MantissaScale::Large:
|
||
return "large";
|
||
default:
|
||
throw std::runtime_error("Bad scale");
|
||
}
|
||
}
|
||
|
||
class SaveNumberRoundMode
|
||
{
|
||
Number::RoundingMode mode_;
|
||
|
||
public:
|
||
~SaveNumberRoundMode()
|
||
{
|
||
Number::setround(mode_);
|
||
}
|
||
explicit SaveNumberRoundMode(Number::RoundingMode 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::RoundingMode mode) noexcept
|
||
: saved_{Number::setround(mode)}
|
||
{
|
||
}
|
||
|
||
NumberRoundModeGuard(NumberRoundModeGuard const&) = delete;
|
||
|
||
NumberRoundModeGuard&
|
||
operator=(NumberRoundModeGuard const&) = delete;
|
||
};
|
||
|
||
/** Sets the new scale and restores the old scale when it leaves scope.
|
||
*
|
||
* If you think you need to use this class outside of unit tests, no you don't.
|
||
*
|
||
*/
|
||
class NumberMantissaScaleGuard
|
||
{
|
||
MantissaRange::MantissaScale const saved_;
|
||
|
||
public:
|
||
explicit NumberMantissaScaleGuard(MantissaRange::MantissaScale scale) noexcept
|
||
: saved_{Number::getMantissaScale()}
|
||
{
|
||
Number::setMantissaScale(scale);
|
||
}
|
||
|
||
~NumberMantissaScaleGuard()
|
||
{
|
||
Number::setMantissaScale(saved_);
|
||
}
|
||
|
||
NumberMantissaScaleGuard(NumberMantissaScaleGuard const&) = delete;
|
||
|
||
NumberMantissaScaleGuard&
|
||
operator=(NumberMantissaScaleGuard const&) = delete;
|
||
};
|
||
|
||
} // namespace xrpl
|