Compare commits

..

2 Commits

Author SHA1 Message Date
Bart
599a651e9e Use the correct workflow 2026-07-09 09:46:47 -07:00
Bart
dd425ffc6b ci: Do not run conflict checker when label is applied 2026-07-09 09:39:00 -07:00
7 changed files with 432 additions and 1126 deletions

View File

@@ -14,6 +14,7 @@ permissions:
jobs:
main:
if: ${{ !contains(github.event.pull_request.labels.*.name, 'IgnoreConflicts') }}
runs-on: ubuntu-latest
steps:
- name: Check if PRs are dirty

View File

@@ -2,8 +2,8 @@
#include <xrpl/beast/utility/instrumentation.h>
#include <algorithm>
#include <array>
#include <concepts>
#include <cstddef>
#include <cstdint>
#include <functional>
@@ -13,15 +13,10 @@
#include <set>
#include <stdexcept>
#include <string>
#include <tuple>
#include <type_traits>
#include <unordered_map>
#include <utility>
#ifdef _MSC_VER
#include <boost/multiprecision/cpp_int.hpp>
#endif // !defined(_MSC_VER)
namespace xrpl {
class Number;
@@ -29,39 +24,18 @@ class Number;
std::string
to_string(Number const& amount);
/** Returns a rough estimate of log10(value).
*
* The return value is a pair (log, rem), where log is the estimated
* base-10 logarithm (roughly floor(log10(value))), and rem is value with
* all trailing 0s removed (i.e., divided by the largest power of 10 that
* evenly divides value). If rem is 1, then value is an exact power of ten, and
* log is the exact log10(value).
*
* This function only works for positive values.
*/
template <std::unsigned_integral T>
constexpr std::pair<int, T>
logTenEstimate(T value)
{
int log = 0;
T remainder = value;
while (value >= 10)
{
if (value % 10 == 0)
remainder = remainder / 10;
value /= 10;
++log;
}
return {log, remainder};
}
template <typename T>
constexpr std::optional<int>
logTen(T value)
{
auto const est = logTenEstimate(value);
if (est.second == 1)
return est.first;
int log = 0;
while (value >= 10 && value % 10 == 0)
{
value /= 10;
++log;
}
if (value == 1)
return log;
return std::nullopt;
}
@@ -116,9 +90,12 @@ static_assert(
/** 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 prevents the creation of any MantissaRanges representing other values.
* 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
@@ -132,14 +109,12 @@ static_assert(
* "large" scale.
*
* The "Large" scales are intended to represent all values that can be represented
* by an STAmount - IOUs, XRP, and MPTs.
*
* They have a min value of 2^63/10+1 (truncated), and a max value of 2^63-1.
*
* "LargeLegacy" is like "Large", but preserves a rounding error when
* a computation results in a mantissa of Number::kLargestMantissa that needs to
* be rounded up, but rounds down instead. It will maintain consistent
* behavior until the fixCleanup3_2_0 amendment is enabled.
* 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
@@ -164,42 +139,15 @@ struct MantissaRange final
explicit constexpr MantissaRange(MantissaScale sc) : scale(sc)
{
// Keep the error messages terse. Since this is constexpr, if any of these throw, it won't
// compile, so there's no real need to worry about runtime exceptions here.
if (min * 10 <= max)
throw std::out_of_range("Invalid mantissa range: min * 10 <= max");
if (max / 10 >= min)
throw std::out_of_range("Invalid mantissa range: max / 10 >= min");
if ((min - 1) * 10 > max)
throw std::out_of_range("Invalid mantissa range: (min - 1) * 10 > max");
// This is a little hacky
if ((max + 10) / 10 < min)
throw std::out_of_range("Invalid mantissa range: (max + 10) / 10 < min");
if (internalMin != kPowerOfTen[log])
throw std::out_of_range("Invalid mantissa range: internalMin != kPowersOfTen[log]");
}
// Explicitly delete copy and move operations
MantissaRange(MantissaRange const&) = delete;
MantissaRange(MantissaRange&&) = delete;
MantissaRange&
operator=(MantissaRange const&) = delete;
MantissaRange&
operator=(MantissaRange&&) = delete;
MantissaScale const scale;
int const log{getExponent(scale)};
rep const max{getMax(scale, log)};
rep const min{computeMin(max)};
/* Used to determine if mantissas are in range, but have fewer digits than max.
*
* Unlike min, internalMin is always an exact power of 10, so a mantissa in the internal
* representation will always have a consistent number of digits.
*/
rep const internalMin{getInternalMin(scale, log)};
rep const min{getMin(scale, log)};
rep const max{(min * 10) - 1};
CuspRoundingFix const cuspRoundingFixEnabled{isCuspFixEnabled(scale)};
static constexpr MantissaRange const&
static MantissaRange const&
getMantissaRange(MantissaScale scale);
static std::set<MantissaScale> const&
@@ -225,39 +173,13 @@ private:
}
}
// Keep this function for future use with different ways to compute
// the ranges.
static constexpr rep
getMax(MantissaScale scale, int log)
{
switch (scale)
{
case MantissaScale::Small:
return kPowerOfTen[log + 1] - 1;
case MantissaScale::LargeLegacy:
case MantissaScale::Large:
return std::numeric_limits<std::int64_t>::max();
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
}
}
static constexpr rep
computeMin(rep max)
{
return (max / 10) + 1;
}
static constexpr rep
getInternalMin(MantissaScale scale, int exponent)
getMin(MantissaScale scale, int exponent)
{
if (exponent < 0 || exponent >= kPowerOfTen.size())
{
// If called in a constexpr context, this throw assures that the build fails if an
// invalid exponent is used.
throw std::runtime_error("Invalid exponent"); // LCOV_EXCL_LINE
}
return kPowerOfTen[exponent];
}
@@ -286,26 +208,13 @@ private:
template <class T>
concept Integral64 = std::is_same_v<T, std::int64_t> || std::is_same_v<T, std::uint64_t>;
namespace detail {
#ifdef _MSC_VER
using uint128_t = boost::multiprecision::uint128_t;
using int128_t = boost::multiprecision::int128_t;
#else // !defined(_MSC_VER)
using uint128_t = __uint128_t;
using int128_t = __int128_t;
#endif // !defined(_MSC_VER)
template <class T>
concept UnsignedMantissa = std::is_unsigned_v<T> || std::is_same_v<T, uint128_t>;
} // namespace detail
/** 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 Operational Representation ----
* ---- Internal Representation ----
*
* Internally, Number is represented with three values:
* 1. a bool sign flag,
@@ -314,45 +223,40 @@ concept UnsignedMantissa = std::is_unsigned_v<T> || std::is_same_v<T, uint128_t>
*
* The internal mantissa is an unsigned integer in the range defined by the
* current MantissaRange. The exponent is an integer in the range
* [kMinExponent, kMaxExponent].
* [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.internalMin, MantissaRange.internalMin * 10 - 1].
*
* This internal representation is only used during some operations to ensure
* that the mantissa is a known, predictable size. The class itself stores the
* values using the external representation described below.
* [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. Unlike MantissaRange.min, internalMin is always an exact power of 10,
* so a mantissa in the internal representation will always have a
* consistent number of digits.
* 3. The functions toInternal() and fromInternal() are used to convert
* between the two representations.
* 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
* [kMinExponent, kMaxExponent]. The range of the mantissa depends on which
* [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. The "large" mantissa range is (2^63/10+1) to 2^63-1. 2^63-1 is between
* 10^18 and 10^19-1, and (2^63/10+1) is between 10^17 and 10^18-1. Thus,
* the mantissa may have 18 or 19 digits. This value will be modified to
* always have 19 digits before some operations to ensure consistency.
* 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.
* 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
@@ -407,7 +311,8 @@ class Number final
using rep = std::int64_t;
using internalrep = MantissaRange::rep;
rep mantissa_{0};
bool negative_{false};
internalrep mantissa_{0};
int exponent_{std::numeric_limits<int>::lowest()};
public:
@@ -415,6 +320,10 @@ public:
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
{
@@ -492,7 +401,8 @@ public:
friend constexpr bool
operator==(Number const& x, Number const& y) noexcept
{
return x.mantissa_ == y.mantissa_ && x.exponent_ == y.exponent_;
return x.negative_ == y.negative_ && x.mantissa_ == y.mantissa_ &&
x.exponent_ == y.exponent_;
}
friend constexpr bool
@@ -504,8 +414,8 @@ public:
friend constexpr bool
operator<(Number const& l, Number const& r) noexcept
{
bool const lneg = l.mantissa_ < 0;
bool const rneg = r.mantissa_ < 0;
bool const lneg = l.negative_;
bool const rneg = r.negative_;
// If the two amounts have different signs (zero is treated as positive)
// then the comparison is true iff the left is negative.
@@ -529,13 +439,10 @@ public:
return !lneg;
// If equal signs and exponents, compare mantissas.
if constexpr (std::is_unsigned_v<decltype(l.mantissa_)>)
if (lneg)
{
if (lneg)
{
// If negative, the operator is reversed.
return l.mantissa_ < r.mantissa_;
}
// If negative, the operator is reversed.
return l.mantissa_ > r.mantissa_;
}
return l.mantissa_ < r.mantissa_;
@@ -545,11 +452,9 @@ public:
[[nodiscard]] constexpr int
signum() const noexcept
{
if (mantissa_ < 0)
{
if (negative_)
return -1;
}
return (mantissa_ != 0 ? 1 : 0);
return (mantissa_ != 0u) ? 1 : 0;
}
[[nodiscard]] Number
@@ -588,9 +493,6 @@ public:
friend Number
root2(Number f);
friend Number
power(Number const& f, unsigned n, unsigned d);
// Thread local rounding control. Default is to_nearest
enum class RoundingMode { ToNearest, TowardsZero, Downward, Upward };
@@ -643,25 +545,6 @@ public:
std::pair<T, int>
normalizeToRange() const;
class Access
{
/** May use ranges that don't fit the restrictions of the "real"
* normalizeToRange().
*
*/
template <Integral64 T>
[[nodiscard]]
static std::pair<T, int>
normalizeToRangeImpl(
Number const& n,
T minMantissa,
T maxMantissa,
MantissaRange::CuspRoundingFix fix);
friend class Number;
friend class NumberTest;
};
private:
static thread_local RoundingMode mode;
// The available ranges for mantissa
@@ -671,14 +554,6 @@ private:
// changing the values inside the range.
static thread_local std::reference_wrapper<MantissaRange const> kRange;
// And one is needed because it needs to choose between oneSmall and
// oneLarge based on the current range
static Number
one(MantissaRange const& range);
static Number
root(MantissaRange const& range, Number f, unsigned d);
void
normalize(MantissaRange const& range);
@@ -709,10 +584,6 @@ private:
MantissaRange::CuspRoundingFix cuspRoundingFixEnabled,
bool dropped);
[[nodiscard]]
bool
isnormal(MantissaRange const& range) const noexcept;
[[nodiscard]] bool
isnormal() const noexcept;
@@ -722,66 +593,18 @@ private:
[[nodiscard]] Number
shiftExponent(int exponentDelta) const;
// Safely return the absolute value of a rep (int64) mantissa as an internalrep (uint64).
// 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);
/** Breaks down the number into components, potentially de-normalizing it.
*
* Ensures that the mantissa always has kRange.log + 1 digits.
*
*/
template <detail::UnsignedMantissa Rep = internalrep>
std::tuple<bool, Rep, int>
toInternal(MantissaRange const& range) const;
/** Breaks down the number into components, potentially de-normalizing it.
*
* Ensures that the mantissa always has kRange.log + 1 digits.
*
*/
template <detail::UnsignedMantissa Rep = internalrep>
std::tuple<bool, Rep, int>
toInternal() const;
/** Rebuilds the number from components.
*
* If "expectNormal" is true, the values are expected to be normalized - all
* in their valid ranges.
*
* If "expectNormal" is false, the values are expected to be "near
* normalized", meaning that the mantissa has to be modified at most once to
* bring it back into range.
*
*/
template <bool ExpectNormal = true, detail::UnsignedMantissa Rep = internalrep>
void
fromInternal(bool negative, Rep mantissa, int exponent, MantissaRange const* pRange);
/** Rebuilds the number from components.
*
* If "expectNormal" is true, the values are expected to be normalized - all
* in their valid ranges.
*
* If "expectNormal" is false, the values are expected to be "near
* normalized", meaning that the mantissa has to be modified at most once to
* bring it back into range.
*
*/
template <bool ExpectNormal = true, detail::UnsignedMantissa Rep = internalrep>
void
fromInternal(bool negative, Rep mantissa, int exponent);
class Guard;
public:
constexpr static internalrep kLargestMantissa =
MantissaRange{MantissaRange::MantissaScale::Large}.max;
};
constexpr Number::Number(bool negative, internalrep mantissa, int exponent, Unchecked) noexcept
: mantissa_{negative ? -static_cast<rep>(mantissa) : static_cast<rep>(mantissa)}
, exponent_{exponent}
: negative_(negative), mantissa_{mantissa}, exponent_{exponent}
{
}
@@ -792,6 +615,12 @@ constexpr Number::Number(internalrep mantissa, int exponent, Unchecked) noexcept
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{})
{
@@ -814,7 +643,17 @@ inline Number::Number(rep mantissa) : Number{mantissa, 0}
constexpr Number::rep
Number::mantissa() const noexcept
{
return mantissa_;
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.
@@ -825,7 +664,16 @@ Number::mantissa() const noexcept
constexpr int
Number::exponent() const noexcept
{
return exponent_;
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
@@ -840,7 +688,7 @@ Number::operator-() const noexcept
if (mantissa_ == 0)
return Number{};
auto x = *this;
x.mantissa_ = -x.mantissa_;
x.negative_ = !x.negative_;
return x;
}
@@ -921,29 +769,23 @@ Number::min() noexcept
inline Number
Number::max() noexcept
{
return Number{false, kRange.get().max, kMaxExponent, Unchecked{}};
return Number{false, std::min(kRange.get().max, kMaxRep), kMaxExponent, Unchecked{}};
}
inline Number
Number::lowest() noexcept
{
return Number{true, kRange.get().max, kMaxExponent, Unchecked{}};
}
inline bool
Number::isnormal(MantissaRange const& range) const noexcept
{
auto const absM = externalToInternal(mantissa_);
return *this == Number{} ||
(range.min <= absM && absM <= range.max && //
kMinExponent <= exponent_ && exponent_ <= kMaxExponent);
return Number{true, std::min(kRange.get().max, kMaxRep), kMaxExponent, Unchecked{}};
}
inline bool
Number::isnormal() const noexcept
{
return isnormal(kRange);
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>
@@ -957,34 +799,13 @@ Number::normalizeToRange() const
auto constexpr kMAX = static_cast<T>(MaxMantissa);
static_assert(kMIN > 0);
static_assert(kMIN % 10 == 0);
static_assert(isPowerOfTen(static_cast<std::make_unsigned_t<T>>(kMIN)));
static_assert(isPowerOfTen(kMIN));
static_assert(kMAX % 10 == 9);
static_assert((kMAX + 1) / 10 == kMIN);
// 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.
return Access::normalizeToRangeImpl(
*this, kMIN, kMAX, MantissaRange::CuspRoundingFix::Disabled);
}
/** Only intended to be used in tests
*
* May use ranges that don't fit the restrictions of the "real"
* normalizeToRange().
*
*/
template <Integral64 T>
[[nodiscard]]
std::pair<T, int>
Number::Access::normalizeToRangeImpl(
Number const& n,
T minMantissa,
T maxMantissa,
MantissaRange::CuspRoundingFix fix)
{
bool negative = n.mantissa_ < 0;
internalrep mantissa = externalToInternal(n.mantissa_);
int exponent = n.exponent_;
bool negative = negative_;
internalrep mantissa = mantissa_;
int exponent = exponent_;
if constexpr (std::is_unsigned_v<T>)
{
@@ -992,21 +813,14 @@ Number::Access::normalizeToRangeImpl(
!negative,
"xrpl::Number::normalizeToRange",
"Number is non-negative for unsigned range.");
// To avoid logical errors in release builds, throw if the Number is
// negative for an unsigned range.
if (negative)
{
throw std::runtime_error(
"Number::normalizeToRange: Number is negative for "
"unsigned range.");
}
}
Number::normalize(negative, mantissa, exponent, minMantissa, maxMantissa, fix);
// 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);
// Cast mantissa to signed type first (if T is a signed type) to avoid
// unsigned integer overflow when multiplying by negative sign
T signedMantissa = negative ? -static_cast<T>(mantissa) : static_cast<T>(mantissa);
return std::make_pair(signedMantissa, exponent);
auto const sign = negative ? -1 : 1;
return std::make_pair(static_cast<T>(sign * mantissa), exponent);
}
constexpr Number

View File

@@ -237,8 +237,8 @@ constexpr std::size_t kMaxPermissionedDomainCredentialsArraySize = 10;
constexpr std::size_t kMaxMpTokenMetadataLength = 1024;
/** The maximum amount of MPTokenIssuance */
std::uint64_t constexpr kMaxMpTokenAmount = 0x7FFF'FFFF'FFFF'FFFFull;
static_assert(Number::kLargestMantissa >= kMaxMpTokenAmount);
constexpr std::uint64_t kMaxMpTokenAmount = 0x7FFF'FFFF'FFFF'FFFFull;
static_assert(Number::kMaxRep >= kMaxMpTokenAmount);
/** The maximum length of Data payload */
constexpr std::size_t kMaxDataPayloadLength = 256;

View File

@@ -573,8 +573,6 @@ STAmount::fromNumber(A const& a, Number const& number)
return STAmount{asset, intValue, 0, negative};
}
XRPL_ASSERT_PARTS(
working.signum() >= 0, "xrpl::STAmount::fromNumber", "non-negative Number to normalize");
auto const [mantissa, exponent] = working.normalizeToRange<kMinValue, kMaxValue>();
return STAmount{asset, mantissa, exponent, negative};

View File

@@ -26,7 +26,7 @@ systemName()
/** Number of drops in the genesis account. */
constexpr XRPAmount kInitialXrp{100'000'000'000 * kDropsPerXrp};
static_assert(kInitialXrp.drops() == 100'000'000'000'000'000);
static_assert(Number::kLargestMantissa >= kInitialXrp.drops());
static_assert(Number::kMaxRep >= kInitialXrp.drops());
/** Returns true if the amount does not exceed the initial XRP in existence. */
inline bool

View File

@@ -8,22 +8,24 @@
#include <cstdint>
#include <functional>
#include <iterator>
#include <limits>
#include <numeric>
#include <set>
#include <stdexcept>
#include <string>
#include <string_view>
#include <tuple>
#include <type_traits>
#include <unordered_map>
#include <utility>
#ifdef _MSC_VER
#pragma message("Using boost::multiprecision::uint128_t and int128_t")
#endif
using uint128_t = xrpl::detail::uint128_t;
using int128_t = xrpl::detail::int128_t;
#include <boost/multiprecision/cpp_int.hpp>
using uint128_t = boost::multiprecision::uint128_t;
using int128_t = boost::multiprecision::int128_t;
#else // !defined(_MSC_VER)
using uint128_t = __uint128_t;
using int128_t = __int128_t;
#endif // !defined(_MSC_VER)
namespace xrpl {
@@ -58,39 +60,33 @@ MantissaRange::getRanges()
[[maybe_unused]]
constexpr static MantissaRange kRange{MantissaRange::MantissaScale::Small};
static_assert(isPowerOfTen(kRange.min));
static_assert(isPowerOfTen(kRange.internalMin));
static_assert(kRange.min == 1'000'000'000'000'000LL);
static_assert(kRange.internalMin == kRange.min);
static_assert(kRange.max == 9'999'999'999'999'999LL);
static_assert(kRange.log == 15);
static_assert(kRange.min < Number::kLargestMantissa);
static_assert(kRange.max < Number::kLargestMantissa);
static_assert(kRange.min < Number::kMaxRep);
static_assert(kRange.max < Number::kMaxRep);
static_assert(kRange.cuspRoundingFixEnabled == CuspRoundingFix::Disabled);
}
{
[[maybe_unused]]
constexpr static MantissaRange kRange{MantissaRange::MantissaScale::LargeLegacy};
static_assert(!isPowerOfTen(kRange.min));
static_assert(isPowerOfTen(kRange.internalMin));
static_assert(kRange.min == 922'337'203'685'477'581ULL);
static_assert(kRange.internalMin == 1'000'000'000'000'000'000ULL);
static_assert(kRange.max == rep(9'223'372'036'854'775'807ULL));
static_assert(isPowerOfTen(kRange.min));
static_assert(kRange.min == 1'000'000'000'000'000'000ULL);
static_assert(kRange.max == rep(9'999'999'999'999'999'999ULL));
static_assert(kRange.log == 18);
static_assert(kRange.min < Number::kLargestMantissa);
static_assert(kRange.max == Number::kLargestMantissa);
static_assert(kRange.min < Number::kMaxRep);
static_assert(kRange.max > Number::kMaxRep);
static_assert(kRange.cuspRoundingFixEnabled == CuspRoundingFix::Disabled);
}
{
[[maybe_unused]]
constexpr static MantissaRange kRange{MantissaRange::MantissaScale::Large};
static_assert(!isPowerOfTen(kRange.min));
static_assert(isPowerOfTen(kRange.internalMin));
static_assert(kRange.min == 922'337'203'685'477'581ULL);
static_assert(kRange.internalMin == 1'000'000'000'000'000'000ULL);
static_assert(kRange.max == rep(9'223'372'036'854'775'807ULL));
static_assert(isPowerOfTen(kRange.min));
static_assert(kRange.min == 1'000'000'000'000'000'000ULL);
static_assert(kRange.max == rep(9'999'999'999'999'999'999ULL));
static_assert(kRange.log == 18);
static_assert(kRange.min < Number::kLargestMantissa);
static_assert(kRange.max == Number::kLargestMantissa);
static_assert(kRange.min < Number::kMaxRep);
static_assert(kRange.max > Number::kMaxRep);
static_assert(kRange.cuspRoundingFixEnabled == CuspRoundingFix::Enabled);
}
return map;
@@ -99,7 +95,7 @@ MantissaRange::getRanges()
return kMap;
}
MantissaRange constexpr const&
MantissaRange const&
MantissaRange::getMantissaRange(MantissaScale scale)
{
return getRanges().at(scale);
@@ -165,6 +161,9 @@ divu10(uint128_t& u)
// precision to an operation. This enables the final result
// to be correctly rounded to the internal precision of Number.
template <class T>
concept UnsignedMantissa = std::is_unsigned_v<T> || std::is_same_v<T, uint128_t>;
class Number::Guard
{
std::uint64_t digits_{0}; // 16 decimal guard digits
@@ -214,7 +213,7 @@ public:
round() const noexcept;
// Modify the result to the correctly rounded value
template <detail::UnsignedMantissa T>
template <UnsignedMantissa T>
void
doRoundUp(
bool& negative,
@@ -223,22 +222,22 @@ public:
internalrep const& minMantissa,
internalrep const& maxMantissa,
MantissaRange::CuspRoundingFix cuspRoundingFixEnabled,
std::string_view location);
std::string location);
// Modify the result to the correctly rounded value
template <detail::UnsignedMantissa T>
template <UnsignedMantissa T>
void
doRoundDown(bool& negative, T& mantissa, int& exponent, internalrep const& minMantissa);
// Modify the result to the correctly rounded value
void
doRound(internalrep& drops, std::string_view location) const;
doRound(rep& drops, std::string location) const;
private:
void
doPush(unsigned d) noexcept;
template <detail::UnsignedMantissa T>
template <UnsignedMantissa T>
void
bringIntoRange(bool& negative, T& mantissa, int& exponent, internalrep const& minMantissa);
};
@@ -352,7 +351,7 @@ Number::Guard::round() const noexcept
return 0;
}
template <detail::UnsignedMantissa T>
template <UnsignedMantissa T>
void
Number::Guard::bringIntoRange(
bool& negative,
@@ -371,11 +370,13 @@ Number::Guard::bringIntoRange(
{
static constexpr Number kZero = Number{};
std::tie(negative, mantissa, exponent) = kZero.toInternal();
negative = kZero.negative_;
mantissa = kZero.mantissa_;
exponent = kZero.exponent_;
}
}
template <detail::UnsignedMantissa T>
template <UnsignedMantissa T>
void
Number::Guard::doRoundUp(
bool& negative,
@@ -384,13 +385,13 @@ Number::Guard::doRoundUp(
internalrep const& minMantissa,
internalrep const& maxMantissa,
MantissaRange::CuspRoundingFix cuspRoundingFixEnabled,
std::string_view location)
std::string location)
{
auto r = round();
if (r == 1 || (r == 0 && (mantissa & 1) == 1))
{
auto const safeToIncrement = [&maxMantissa](auto const& mantissa) {
return mantissa < maxMantissa && mantissa < kLargestMantissa;
return mantissa < maxMantissa && mantissa < kMaxRep;
};
if (cuspRoundingFixEnabled == MantissaRange::CuspRoundingFix::Enabled)
{
@@ -406,8 +407,8 @@ Number::Guard::doRoundUp(
// Incrementing the mantissa will require dividing, which will require rounding. So
// _don't_ increment the mantissa. Instead, divide and round recursively. It should
// be impossible to recurse more than once, because once the mantissa is divided by
// 10, it will be _well_ under maxMantissa and kLargestMantissa, so adding 1 will
// have no chance of bringing it back over.
// 10, it will be _well_ under maxMantissa and kMaxRep, so adding 1 will have no
// chance of bringing it back over.
doDropDigit(mantissa, exponent);
XRPL_ASSERT_PARTS(
safeToIncrement(mantissa),
@@ -431,7 +432,7 @@ Number::Guard::doRoundUp(
++mantissa;
// Ensure mantissa after incrementing fits within both the
// min/maxMantissa range and is a valid "rep".
if (mantissa > maxMantissa || mantissa > kLargestMantissa)
if (mantissa > maxMantissa || mantissa > kMaxRep)
{
// Don't use doDropDigit here
mantissa /= 10;
@@ -444,7 +445,7 @@ Number::Guard::doRoundUp(
Throw<std::overflow_error>(std::string(location));
}
template <detail::UnsignedMantissa T>
template <UnsignedMantissa T>
void
Number::Guard::doRoundDown(
bool& negative,
@@ -467,25 +468,26 @@ Number::Guard::doRoundDown(
// Modify the result to the correctly rounded value
void
Number::Guard::doRound(internalrep& drops, std::string_view location) const
Number::Guard::doRound(rep& drops, std::string location) const
{
auto r = round();
if (r == 1 || (r == 0 && (drops & 1) == 1))
{
auto const& range = kRange.get();
if (drops >= range.max)
if (drops >= kMaxRep)
{
static_assert(sizeof(internalrep) == sizeof(rep));
// This should be impossible, because it's impossible to represent
// "kLargestMantissa + 0.6" in Number, regardless of the scale. There aren't
// enough digits available. You'd either get a mantissa of "kLargestMantissa"
// or "kLargestMantissa / 10 + 1", neither of which will round up when
// "kMaxRep + 0.6" in Number, regardless of the scale. There aren't
// enough digits available. You'd either get a mantissa of "kMaxRep"
// or "(kMaxRep + 1) / 10", neither of which will round up when
// converting to rep, though the latter might overflow _before_
// rounding.
Throw<std::overflow_error>(std::string(location)); // LCOV_EXCL_LINE
}
++drops;
}
if (isNegative())
drops = -drops;
}
// Number
@@ -500,6 +502,10 @@ Number::externalToInternal(rep mantissa)
// If the mantissa is already positive, just return it
if (mantissa >= 0)
return mantissa;
// If the mantissa is negative, but fits within the positive range of rep,
// return it negated
if (mantissa >= -std::numeric_limits<rep>::max())
return -mantissa;
// If the mantissa doesn't fit within the positive range, convert to
// int128_t, negate that, and cast it back down to the internalrep
@@ -509,135 +515,11 @@ Number::externalToInternal(rep mantissa)
return static_cast<internalrep>(-temp);
}
/** Breaks down the number into components, potentially de-normalizing it.
*
* Ensures that the mantissa always has kRange.log + 1 digits.
*
*/
template <detail::UnsignedMantissa Rep>
std::tuple<bool, Rep, int>
Number::toInternal(MantissaRange const& range) const
{
auto exponent = exponent_;
bool const negative = mantissa_ < 0;
// It should be impossible for mantissa_ to be INT64_MIN, but use externalToInternal just in
// case.
Rep mantissa = static_cast<Rep>(externalToInternal(mantissa_));
auto const internalMin = range.internalMin;
auto const minMantissa = range.min;
if (mantissa != 0 && mantissa >= minMantissa && mantissa < internalMin)
{
// Ensure the mantissa has the correct number of digits
mantissa *= 10;
--exponent;
XRPL_ASSERT_PARTS(
mantissa >= internalMin && mantissa < internalMin * 10,
"xrpl::Number::toInternal()",
"Number is within reference range and has 'log' digits");
}
return {negative, mantissa, exponent};
}
/** Breaks down the number into components, potentially de-normalizing it.
*
* Ensures that the mantissa always has exactly kRange.log + 1 digits.
*
*/
template <detail::UnsignedMantissa Rep>
std::tuple<bool, Rep, int>
Number::toInternal() const
{
return toInternal(kRange);
}
/** Rebuilds the number from components.
*
* If "expectNormal" is true, the values are expected to be normalized - all
* in their valid ranges.
*
* If "expectNormal" is false, the values are expected to be "near
* normalized", meaning that the mantissa has to be modified at most once to
* bring it back into range.
*
*/
template <bool ExpectNormal, detail::UnsignedMantissa Rep>
void
Number::fromInternal(bool negative, Rep mantissa, int exponent, MantissaRange const* pRange)
{
if constexpr (std::is_same_v<std::bool_constant<ExpectNormal>, std::false_type>)
{
if (!pRange)
throw std::runtime_error("Missing range to Number::fromInternal!");
auto const& range = *pRange;
auto const maxMantissa = range.max;
auto const minMantissa = range.min;
XRPL_ASSERT_PARTS(
mantissa >= minMantissa, "xrpl::Number::fromInternal", "mantissa large enough");
if (mantissa > maxMantissa || mantissa < minMantissa)
{
normalize(negative, mantissa, exponent, range.min, maxMantissa);
}
XRPL_ASSERT_PARTS(
mantissa >= minMantissa && mantissa <= maxMantissa,
"xrpl::Number::fromInternal",
"mantissa in range");
}
// mantissa is unsigned, but it might not be uint64
mantissa_ = static_cast<rep>(static_cast<internalrep>(mantissa));
if (negative)
mantissa_ = -mantissa_;
exponent_ = exponent;
XRPL_ASSERT_PARTS(
(pRange && isnormal(*pRange)) || isnormal(),
"xrpl::Number::fromInternal",
"Number is normalized");
}
/** Rebuilds the number from components.
*
* If "expectNormal" is true, the values are expected to be normalized - all in
* their valid ranges.
*
* If "expectNormal" is false, the values are expected to be "near normalized",
* meaning that the mantissa has to be modified at most once to bring it back
* into range.
*
*/
template <bool ExpectNormal, detail::UnsignedMantissa Rep>
void
Number::fromInternal(bool negative, Rep mantissa, int exponent)
{
MantissaRange const* pRange = nullptr;
if constexpr (std::is_same_v<std::bool_constant<ExpectNormal>, std::false_type>)
{
pRange = &Number::kRange.get();
}
fromInternal(negative, mantissa, exponent, pRange);
}
Number
Number::one(MantissaRange const& range)
{
XRPL_ASSERT(isPowerOfTen(range.internalMin), "Number::one : valid range internalMin");
auto const result = Number{false, range.internalMin, -range.log, Number::Unchecked{}};
XRPL_ASSERT(result == 1, "Number::one : One == 1");
return result;
}
Number
Number::one()
{
return one(kRange);
auto const& range = kRange.get();
return Number{false, range.min, -range.log, Number::Unchecked{}};
}
template <class T>
@@ -651,19 +533,20 @@ doNormalize(
MantissaRange::CuspRoundingFix cuspRoundingFixEnabled,
bool dropped)
{
auto constexpr kMinExponent = Number::kMinExponent;
auto constexpr kMaxExponent = Number::kMaxExponent;
static constexpr auto kMinExponent = Number::kMinExponent;
static constexpr auto kMaxExponent = Number::kMaxExponent;
static constexpr auto kMaxRep = Number::kMaxRep;
using Guard = Number::Guard;
constexpr Number kZero = Number{};
auto const& range = Number::kRange.get();
if (mantissa == 0 || (mantissa < minMantissa && exponent <= kMinExponent))
static constexpr Number kZero = Number{};
if (mantissa == 0)
{
std::tie(negative, mantissa, exponent) = kZero.toInternal(range);
mantissa = kZero.mantissa_;
exponent = kZero.exponent_;
negative = kZero.negative_;
return;
}
auto m = mantissa;
while ((m < minMantissa) && (exponent > kMinExponent))
{
@@ -681,13 +564,38 @@ doNormalize(
throw std::overflow_error("Number::normalize 1");
g.doDropDigit(m, exponent);
}
if ((exponent < kMinExponent) || (m == 0))
if ((exponent < kMinExponent) || (m < minMantissa))
{
std::tie(negative, mantissa, exponent) = kZero.toInternal(range);
mantissa = kZero.mantissa_;
exponent = kZero.exponent_;
negative = kZero.negative_;
return;
}
XRPL_ASSERT_PARTS(m <= maxMantissa, "xrpl::doNormalize", "intermediate mantissa fits in int64");
// When using the largeRange, "m" needs fit within an int64, even if
// the final mantissa is going to end up larger to fit within the
// MantissaRange. Cut it down here so that the rounding will be done while
// it's smaller.
//
// Example: 9,900,000,000,000,123,456 > 9,223,372,036,854,775,807,
// so "m" will be modified to 990,000,000,000,012,345. Then that value
// will be rounded to 990,000,000,000,012,345 or
// 990,000,000,000,012,346, depending on the rounding mode. Finally,
// mantissa will be "m*10" so it fits within the range, and end up as
// 9,900,000,000,000,123,450 or 9,900,000,000,000,123,460.
// mantissa() will return mantissa / 10, and exponent() will return
// exponent + 1.
if (m > kMaxRep)
{
if (exponent >= kMaxExponent)
throw std::overflow_error("Number::normalize 1.5");
g.doDropDigit(m, exponent);
}
// Before modification, m should be within the min/max range. After
// modification, it must be less than kMaxRep. In other words, the original
// value should have been no more than kMaxRep * 10.
// (kMaxRep * 10 > maxMantissa)
XRPL_ASSERT_PARTS(m <= kMaxRep, "xrpl::doNormalize", "intermediate mantissa fits in int64");
mantissa = m;
g.doRoundUp(
@@ -698,15 +606,10 @@ doNormalize(
maxMantissa,
cuspRoundingFixEnabled,
"Number::normalize 2");
XRPL_ASSERT_PARTS(
mantissa >= minMantissa && mantissa <= maxMantissa,
"xrpl::doNormalize",
"final mantissa fits in range");
XRPL_ASSERT_PARTS(
exponent >= kMinExponent && exponent <= kMaxExponent,
"xrpl::doNormalize",
"final exponent fits in range");
}
template <>
@@ -762,11 +665,7 @@ Number::normalize<unsigned long>(
void
Number::normalize(MantissaRange const& range)
{
auto [negative, mantissa, exponent] = toInternal(range);
normalize(negative, mantissa, exponent, range.min, range.max, range.cuspRoundingFixEnabled);
fromInternal(negative, mantissa, exponent, &range);
normalize(negative_, mantissa_, exponent_, range.min, range.max, range.cuspRoundingFixEnabled);
}
// Copy the number, but set a new exponent. Because the mantissa doesn't change,
@@ -776,34 +675,22 @@ Number
Number::shiftExponent(int exponentDelta) const
{
XRPL_ASSERT_PARTS(isnormal(), "xrpl::Number::shiftExponent", "normalized");
Number result = *this;
result.exponent_ += exponentDelta;
if (result.exponent_ >= kMaxExponent)
auto const newExponent = exponent_ + exponentDelta;
if (newExponent >= kMaxExponent)
throw std::overflow_error("Number::shiftExponent");
if (result.exponent_ < kMinExponent)
if (newExponent < kMinExponent)
{
return Number{};
}
Number const result{negative_, mantissa_, newExponent, Unchecked{}};
XRPL_ASSERT_PARTS(result.isnormal(), "xrpl::Number::shiftExponent", "result is normalized");
return result;
}
Number::Number(bool negative, internalrep mantissa, int exponent, Normalized)
{
auto const& range = kRange.get();
normalize(negative, mantissa, exponent, range.min, range.max, range.cuspRoundingFixEnabled);
fromInternal(negative, mantissa, exponent, &range);
}
Number&
Number::operator+=(Number const& y)
{
auto const& range = kRange.get();
constexpr Number kZero = Number{};
static constexpr Number kZero = Number{};
if (y == kZero)
return *this;
if (*this == kZero)
@@ -817,8 +704,7 @@ Number::operator+=(Number const& y)
return *this;
}
XRPL_ASSERT(
isnormal(range) && y.isnormal(range), "xrpl::Number::operator+=(Number) : is normal");
XRPL_ASSERT(isnormal() && y.isnormal(), "xrpl::Number::operator+=(Number) : is normal");
// *n = negative
// *s = sign
// *m = mantissa
@@ -826,10 +712,13 @@ Number::operator+=(Number const& y)
// Need to use uint128_t, because large mantissas can overflow when added
// together.
auto [xn, xm, xe] = toInternal<uint128_t>(range);
auto [yn, ym, ye] = y.toInternal<uint128_t>(range);
bool xn = negative_;
uint128_t xm = mantissa_;
auto xe = exponent_;
bool const yn = y.negative_;
uint128_t ym = y.mantissa_;
auto ye = y.exponent_;
Guard g;
if (xe < ye)
{
@@ -850,6 +739,7 @@ Number::operator+=(Number const& y)
} while (xe > ye);
}
auto const& range = kRange.get();
auto const& minMantissa = range.min;
auto const& maxMantissa = range.max;
auto const cuspRoundingFixEnabled = range.cuspRoundingFixEnabled;
@@ -857,7 +747,7 @@ Number::operator+=(Number const& y)
if (xn == yn)
{
xm += ym;
if (xm > maxMantissa)
if (xm > maxMantissa || xm > kMaxRep)
{
g.doDropDigit(xm, xe);
}
@@ -882,7 +772,7 @@ Number::operator+=(Number const& y)
xe = ye;
xn = yn;
}
while (xm < minMantissa)
while (xm < minMantissa && xm * 10 <= kMaxRep)
{
xm *= 10;
xm -= g.pop();
@@ -891,17 +781,17 @@ Number::operator+=(Number const& y)
g.doRoundDown(xn, xm, xe, minMantissa);
}
normalize(xn, xm, xe, minMantissa, maxMantissa, cuspRoundingFixEnabled);
fromInternal(xn, xm, xe, &range);
negative_ = xn;
mantissa_ = static_cast<internalrep>(xm);
exponent_ = xe;
normalize(range);
return *this;
}
Number&
Number::operator*=(Number const& y)
{
auto const& range = kRange.get();
constexpr Number kZero = Number{};
static constexpr Number kZero = Number{};
if (*this == kZero)
return *this;
if (y == kZero)
@@ -914,11 +804,15 @@ Number::operator*=(Number const& y)
// *m = mantissa
// *e = exponent
auto [xn, xm, xe] = toInternal(range);
bool const xn = negative_;
int const xs = xn ? -1 : 1;
internalrep xm = mantissa_;
auto xe = exponent_;
auto [yn, ym, ye] = y.toInternal(range);
bool const yn = y.negative_;
int const ys = yn ? -1 : 1;
internalrep const ym = y.mantissa_;
auto ye = y.exponent_;
auto zm = uint128_t(xm) * uint128_t(ym);
auto ze = xe + ye;
@@ -928,11 +822,12 @@ Number::operator*=(Number const& y)
if (zn)
g.setNegative();
auto const& range = kRange.get();
auto const& minMantissa = range.min;
auto const& maxMantissa = range.max;
auto const cuspRoundingFixEnabled = range.cuspRoundingFixEnabled;
while (zm > maxMantissa)
while (zm > maxMantissa || zm > kMaxRep)
{
g.doDropDigit(zm, ze);
}
@@ -947,18 +842,18 @@ Number::operator*=(Number const& y)
maxMantissa,
cuspRoundingFixEnabled,
"Number::multiplication overflow : exponent is " + std::to_string(xe));
negative_ = zn;
mantissa_ = xm;
exponent_ = xe;
normalize(zn, xm, xe, minMantissa, maxMantissa, cuspRoundingFixEnabled);
fromInternal(zn, xm, xe, &range);
normalize(range);
return *this;
}
Number&
Number::operator/=(Number const& y)
{
auto const& range = kRange.get();
constexpr Number kZero = Number{};
static constexpr Number kZero = Number{};
if (y == kZero)
throw std::overflow_error("Number: divide by 0");
if (*this == kZero)
@@ -972,14 +867,19 @@ Number::operator/=(Number const& y)
// *m = mantissa
// *e = exponent
// Create the mantissas as 128-bit unsigned, since that's what we
// need to work with.
auto const [np, nm, ne] = toInternal<uint128_t>(range);
bool const np = negative_;
int const ns = (np ? -1 : 1);
auto nm = mantissa_;
auto ne = exponent_;
auto const [dp, dm, de] = y.toInternal<uint128_t>(range);
bool const dp = y.negative_;
int const ds = (dp ? -1 : 1);
// Create the denominator as 128-bit unsigned, since that's what we
// need to work with.
auto const dm = static_cast<uint128_t>(y.mantissa_);
auto const de = y.exponent_;
auto const& range = kRange.get();
auto const& minMantissa = range.min;
auto const& maxMantissa = range.max;
auto const cuspRoundingFixEnabled = range.cuspRoundingFixEnabled;
@@ -1122,8 +1022,10 @@ Number::operator/=(Number const& y)
}
}
doNormalize(zp, zm, ze, minMantissa, maxMantissa, cuspRoundingFixEnabled, dropped);
fromInternal(zp, zm, ze, &range);
XRPL_ASSERT_PARTS(isnormal(range), "xrpl::Number::operator/=", "result is normalized");
negative_ = zp;
mantissa_ = static_cast<internalrep>(zm);
exponent_ = ze;
XRPL_ASSERT_PARTS(isnormal(), "xrpl::Number::operator/=", "result is normalized");
return *this;
}
@@ -1131,35 +1033,27 @@ Number::operator/=(Number const& y)
Number::
operator rep() const
{
auto const m = mantissa();
// drops will always be non-negative
internalrep drops = externalToInternal(m);
if (drops == 0)
return drops;
rep drops = mantissa();
int offset = exponent();
Guard g;
if (m < 0)
if (drops != 0)
{
g.setNegative();
}
while (offset < 0)
{
g.doDropDigit(drops, offset);
}
for (; offset > 0; --offset)
{
if (drops > kLargestMantissa / 10)
throw std::overflow_error("Number::operator rep() overflow");
drops *= 10;
}
g.doRound(drops, "Number::operator rep() rounding overflow");
if (g.isNegative())
{
return -static_cast<rep>(drops);
if (negative_)
{
g.setNegative();
drops = -drops;
}
while (offset < 0)
{
g.doDropDigit(drops, offset);
}
for (; offset > 0; --offset)
{
if (drops > kMaxRep / 10)
throw std::overflow_error("Number::operator rep() overflow");
drops *= 10;
}
g.doRound(drops, "Number::operator rep() rounding overflow");
}
return drops;
}
@@ -1185,22 +1079,19 @@ Number::truncate() const noexcept
std::string
to_string(Number const& amount)
{
auto const& range = Number::kRange.get();
// keep full internal accuracy, but make more human friendly if possible
static constexpr Number kZero = Number{};
if (amount == kZero)
return "0";
// The mantissa must have a set number of decimal places for this to work
auto [negative, mantissa, exponent] = amount.toInternal(range);
auto exponent = amount.exponent_;
auto mantissa = amount.mantissa_;
bool const negative = amount.negative_;
// Use scientific notation for exponents that are too small or too large
auto const rangeLog = range.log;
if (((exponent != 0 && amount.exponent() != 0) &&
((exponent < -(rangeLog + 10)) || (exponent > -(rangeLog - 10)))))
auto const rangeLog = Number::mantissaLog();
if (((exponent != 0) && ((exponent < -(rangeLog + 10)) || (exponent > -(rangeLog - 10)))))
{
// Remove trailing zeroes from the mantissa.
while (mantissa != 0 && mantissa % 10 == 0 && exponent < Number::kMaxExponent)
{
mantissa /= 10;
@@ -1208,11 +1099,8 @@ to_string(Number const& amount)
}
std::string ret = negative ? "-" : "";
ret.append(std::to_string(mantissa));
if (exponent != 0)
{
ret.append(1, 'e');
ret.append(std::to_string(exponent));
}
ret.append(1, 'e');
ret.append(std::to_string(exponent));
return ret;
}
@@ -1300,11 +1188,20 @@ power(Number const& f, unsigned n)
return r;
}
// Returns f^(1/d)
// Uses NewtonRaphson iterations until the result stops changing
// to find the non-negative root of the polynomial g(x) = x^d - f
// This function, and power(Number f, unsigned n, unsigned d)
// treat corner cases such as 0 roots as advised by Annex F of
// the C standard, which itself is consistent with the IEEE
// floating point standards.
Number
Number::root(MantissaRange const& range, Number f, unsigned d)
root(Number f, unsigned d)
{
constexpr Number kZero = Number{};
auto const one = Number::one(range);
static constexpr Number kZero = Number{};
auto const one = Number::one();
if (f == one || d == 1)
return f;
@@ -1321,28 +1218,21 @@ Number::root(MantissaRange const& range, Number f, unsigned d)
if (f == kZero)
return f;
auto const [e, di] = [&]() {
auto const exponent = std::get<2>(f.toInternal(range));
// Scale f into the range (0, 1) such that the scale change (e) is a
// multiple of the root (d)
auto e = exponent + range.log + 1;
auto const di = static_cast<int>(d);
auto ex = [e = e, di = di]() // Euclidean remainder of e/d
{
int const k = (e >= 0 ? e : e - (di - 1)) / di;
int const k2 = e - (k * di);
if (k2 == 0)
return 0;
return di - k2;
}();
e += ex;
f = f.shiftExponent(-e); // f /= 10^e;
return std::make_tuple(e, di);
// Scale f into the range (0, 1) such that f's exponent is a multiple of d
auto e = f.exponent_ + Number::mantissaLog() + 1;
auto const di = static_cast<int>(d);
auto ex = [e = e, di = di]() // Euclidean remainder of e/d
{
int const k = (e >= 0 ? e : e - (di - 1)) / di;
int const k2 = e - (k * di);
if (k2 == 0)
return 0;
return di - k2;
}();
e += ex;
f = f.shiftExponent(-e); // f /= 10^e;
XRPL_ASSERT_PARTS(e % di == 0, "xrpl::root(Number, unsigned)", "e is divisible by d");
XRPL_ASSERT_PARTS(f.isnormal(range), "xrpl::root(Number, unsigned)", "f is normalized");
XRPL_ASSERT_PARTS(f.isnormal(), "xrpl::root(Number, unsigned)", "f is normalized");
bool neg = false;
if (f < kZero)
{
@@ -1377,33 +1267,15 @@ Number::root(MantissaRange const& range, Number f, unsigned d)
// return r * 10^(e/d) to reverse scaling
auto const result = r.shiftExponent(e / di);
XRPL_ASSERT_PARTS(
result.isnormal(range), "xrpl::root(Number, unsigned)", "result is normalized");
XRPL_ASSERT_PARTS(result.isnormal(), "xrpl::root(Number, unsigned)", "result is normalized");
return result;
}
// Returns f^(1/d)
// Uses NewtonRaphson iterations until the result stops changing
// to find the non-negative root of the polynomial g(x) = x^d - f
// This function, and power(Number f, unsigned n, unsigned d)
// treat corner cases such as 0 roots as advised by Annex F of
// the C standard, which itself is consistent with the IEEE
// floating point standards.
Number
root(Number f, unsigned d)
{
auto const& range = Number::kRange.get();
return Number::root(range, f, d);
}
Number
root2(Number f)
{
auto const& range = Number::kRange.get();
constexpr Number kZero = Number{};
auto const one = Number::one(range);
static constexpr Number kZero = Number{};
auto const one = Number::one();
if (f == one)
return f;
@@ -1412,18 +1284,12 @@ root2(Number f)
if (f == kZero)
return f;
auto const e = [&]() {
auto const exponent = std::get<2>(f.toInternal(range));
// Scale f into the range (0, 1) such that f's exponent is a
// multiple of d
auto e = exponent + range.log + 1;
if (e % 2 != 0)
++e;
f = f.shiftExponent(-e); // f /= 10^e;
return e;
}();
XRPL_ASSERT_PARTS(f.isnormal(range), "xrpl::root2(Number)", "f is normalized");
// Scale f into the range (0, 1) such that f's exponent is a multiple of d
auto e = f.exponent_ + Number::mantissaLog() + 1;
if (e % 2 != 0)
++e;
f = f.shiftExponent(-e); // f /= 10^e;
XRPL_ASSERT_PARTS(f.isnormal(), "xrpl::root2(Number)", "f is normalized");
// Quadratic least squares curve fit of f^(1/d) in the range [0, 1]
auto const D = 105; // NOLINT(readability-identifier-naming)
@@ -1445,7 +1311,7 @@ root2(Number f)
// return r * 10^(e/2) to reverse scaling
auto const result = r.shiftExponent(e / 2);
XRPL_ASSERT_PARTS(result.isnormal(range), "xrpl::root2(Number)", "result is normalized");
XRPL_ASSERT_PARTS(result.isnormal(), "xrpl::root2(Number)", "result is normalized");
return result;
}
@@ -1455,10 +1321,8 @@ root2(Number f)
Number
power(Number const& f, unsigned n, unsigned d)
{
auto const& range = Number::kRange.get();
constexpr Number kZero = Number{};
auto const one = Number::one(range);
static constexpr Number kZero = Number{};
auto const one = Number::one();
if (f == one)
return f;
@@ -1480,7 +1344,7 @@ power(Number const& f, unsigned n, unsigned d)
d /= g;
if ((n % 2) == 1 && (d % 2) == 0 && f < kZero)
throw std::overflow_error("Number::power nan");
return Number::root(range, power(f, n), d);
return root(power(f, n), d);
}
} // namespace xrpl

View File

@@ -15,7 +15,6 @@
#include <algorithm>
#include <array>
#include <cctype>
#include <chrono>
#include <cstdint>
#include <iomanip>
#include <limits>
@@ -105,9 +104,8 @@ TEST(NumberTest, limits)
NumberMantissaScaleGuard const sg(mantissaScale);
auto const scale = Number::getMantissaScale();
auto const minMantissa = Number::minMantissa();
bool caught = false;
auto const minMantissa = Number::minMantissa();
try
{
[[maybe_unused]] Number const x =
@@ -132,9 +130,8 @@ TEST(NumberTest, limits)
__LINE__);
test(Number{false, minMantissa, -32769, Number::Normalized{}}, Number{}, __LINE__);
test(
// Use 1501 to force rounding up
Number{false, minMantissa, 32000, Number::Normalized{}} * 1'000 +
Number{false, 1'501, 32000, Number::Normalized{}},
Number{false, 1'500, 32000, Number::Normalized{}},
Number{false, minMantissa + 2, 32003, Number::Normalized{}},
__LINE__);
// 9,223,372,036,854,775,808
@@ -247,9 +244,7 @@ TEST(NumberTest, add)
{Number{true, 9'999'999'999'999'999'999ULL, -37, Number::Normalized{}},
Number{1'000'000'000'000'000'000, -18},
Number{false, 9'999'999'999'999'999'990ULL, -19, Number::Normalized{}}},
{Number{Number::kLargestMantissa - 1},
Number{1, 0},
Number{Number::kLargestMantissa}},
{Number{Number::kMaxRep - 1}, Number{1, 0}, Number{Number::kMaxRep}},
// Test extremes
{
// Each Number operand rounds up, so the actual mantissa is
@@ -259,32 +254,21 @@ TEST(NumberTest, add)
Number{2, 19},
},
{
// Does not round. Mantissas are going to be >
// largestMantissa, so if added together as uint64_t's, the
// result will overflow. With addition using uint128_t,
// there's no problem. After normalizing, the resulting
// mantissa ends up less than largestMantissa.
Number{false, Number::kLargestMantissa, 0, Number::Normalized{}},
Number{false, Number::kLargestMantissa, 0, Number::Normalized{}},
Number{false, Number::kLargestMantissa * 2, 0, Number::Normalized{}},
},
{
// These mantissas round down, so adding them together won't
// have any consequences.
// Does not round. Mantissas are going to be > maxRep, so if
// added together as uint64_t's, the result will overflow.
// With addition using uint128_t, there's no problem. After
// normalizing, the resulting mantissa ends up less than
// maxRep.
Number{false, 9'999'999'999'999'999'990ULL, 0, Number::Normalized{}},
Number{false, 9'999'999'999'999'999'990ULL, 0, Number::Normalized{}},
Number{false, 1'999'999'999'999'999'998ULL, 1, Number::Normalized{}},
},
});
auto const cLargeLegacy = std::to_array<Case>({
{Number{Number::kLargestMantissa},
Number{6, -1},
Number{Number::kLargestMantissa / 10, 1}},
{Number{Number::kMaxRep}, Number{6, -1}, Number{Number::kMaxRep / 10, 1}},
});
auto const cLargeCorrected = std::to_array<Case>({
{Number{Number::kLargestMantissa},
Number{6, -1},
Number{(Number::kLargestMantissa / 10) + 1, 1}},
{Number{Number::kMaxRep}, Number{6, -1}, Number{(Number::kMaxRep / 10) + 1, 1}},
});
auto test = [](auto const& c) {
for (auto const& [x, y, z] : c)
@@ -384,16 +368,14 @@ TEST(NumberTest, sub)
{Number{1'000'000'000'000'000'001, -18},
Number{1'000'000'000'000'000'000, -18},
Number{1'000'000'000'000'000'000, -36}},
{Number{Number::kLargestMantissa},
Number{6, -1},
Number{Number::kLargestMantissa - 1}},
{Number{false, Number::kLargestMantissa + 1, 0, Number::Normalized{}},
{Number{Number::kMaxRep}, Number{6, -1}, Number{Number::kMaxRep - 1}},
{Number{false, Number::kMaxRep + 1, 0, Number::Normalized{}},
Number{1, 0},
Number{(Number::kLargestMantissa / 10) + 1, 1}},
{Number{false, Number::kLargestMantissa + 1, 0, Number::Normalized{}},
Number{(Number::kMaxRep / 10) + 1, 1}},
{Number{false, Number::kMaxRep + 1, 0, Number::Normalized{}},
Number{3, 0},
Number{Number::kLargestMantissa}},
{power(2, 63), Number{3, 0}, Number{Number::kLargestMantissa}},
Number{Number::kMaxRep}},
{power(2, 63), Number{3, 0}, Number{Number::kMaxRep}},
});
auto test = [](auto const& c) {
for (auto const& [x, y, z] : c)
@@ -415,15 +397,6 @@ TEST(NumberTest, sub)
}
}
static std::uint64_t
getMaxInternalMantissa()
{
return (static_cast<std::uint64_t>(
static_cast<std::int64_t>(power(10, Number::mantissaLog()))) *
10) -
1;
}
TEST(NumberTest, mul)
{
for (auto const mantissaScale : MantissaRange::getAllScales())
@@ -432,15 +405,13 @@ TEST(NumberTest, mul)
auto const scale = Number::getMantissaScale();
// Case: Factor 1, Factor 2, Expected product, Line number
using Case = std::tuple<Number, Number, Number, int>;
using Case = std::tuple<Number, Number, Number>;
auto test = [](auto const& c) {
for (auto const& [x, y, z, line] : c)
for (auto const& [x, y, z] : c)
{
auto const result = x * y;
std::stringstream ss;
ss << x << " * " << y << " = " << result << ". Expected: " << z
<< " line: " << line;
ss << x << " * " << y << " = " << result << ". Expected: " << z;
EXPECT_EQ(result, z) << ss.str();
}
};
@@ -455,366 +426,268 @@ TEST(NumberTest, mul)
}
};
auto const maxMantissa = Number::maxMantissa();
auto const maxInternalMantissa = getMaxInternalMantissa();
SaveNumberRoundMode const save{Number::setround(Number::RoundingMode::ToNearest)};
{
auto const cSmall = std::to_array<Case>({
{Number{7}, Number{8}, Number{56}, __LINE__},
{Number{7}, Number{8}, Number{56}},
{Number{1414213562373095, -15},
Number{1414213562373095, -15},
Number{2000000000000000, -15},
__LINE__},
Number{2000000000000000, -15}},
{Number{-1414213562373095, -15},
Number{1414213562373095, -15},
Number{-2000000000000000, -15},
__LINE__},
Number{-2000000000000000, -15}},
{Number{-1414213562373095, -15},
Number{-1414213562373095, -15},
Number{2000000000000000, -15},
__LINE__},
Number{2000000000000000, -15}},
{Number{3214285714285706, -15},
Number{3111111111111119, -15},
Number{1000000000000000, -14},
__LINE__},
{Number{1000000000000000, -32768},
Number{1000000000000000, -32768},
Number{0},
__LINE__},
Number{1000000000000000, -14}},
{Number{1000000000000000, -32768}, Number{1000000000000000, -32768}, Number{0}},
// Maximum mantissa range
{Number{9'999'999'999'999'999, 0},
Number{9'999'999'999'999'999, 0},
Number{9'999'999'999'999'998, 16},
__LINE__},
Number{9'999'999'999'999'998, 16}},
});
auto const cLarge = std::to_array<Case>({
// Note that items with extremely large mantissas need to be
// calculated, because otherwise they overflow uint64. Items
// from C with larger mantissa
{Number{7}, Number{8}, Number{56}, __LINE__},
{Number{7}, Number{8}, Number{56}},
{Number{1414213562373095, -15},
Number{1414213562373095, -15},
Number{1999999999999999862, -18},
__LINE__},
Number{1999999999999999862, -18}},
{Number{-1414213562373095, -15},
Number{1414213562373095, -15},
Number{-1999999999999999862, -18},
__LINE__},
Number{-1999999999999999862, -18}},
{Number{-1414213562373095, -15},
Number{-1414213562373095, -15},
Number{1999999999999999862, -18},
__LINE__},
Number{1999999999999999862, -18}},
{Number{3214285714285706, -15},
Number{3111111111111119, -15},
Number{false, 9'999'999'999'999'999'579ULL, -18, Number::Normalized{}},
__LINE__},
Number{false, 9'999'999'999'999'999'579ULL, -18, Number::Normalized{}}},
{Number{1000000000000000000, -32768},
Number{1000000000000000000, -32768},
Number{0},
__LINE__},
Number{0}},
// Items from cSmall expanded for the larger mantissa,
// except duplicates. Sadly, it looks like sqrt(2)^2 != 2
// with higher precision
{Number{1414213562373095049, -18},
Number{1414213562373095049, -18},
Number{2000000000000000001, -18},
__LINE__},
Number{2000000000000000001, -18}},
{Number{-1414213562373095048, -18},
Number{1414213562373095048, -18},
Number{-1999999999999999998, -18},
__LINE__},
Number{-1999999999999999998, -18}},
{Number{-1414213562373095048, -18},
Number{-1414213562373095049, -18},
Number{1999999999999999999, -18},
__LINE__},
{Number{3214285714285714278, -18},
Number{3111111111111111119, -18},
Number{10, 0},
__LINE__},
// Maximum internal mantissa range - rounds up to 1e19
{Number{false, maxInternalMantissa, 0, Number::Normalized{}},
Number{false, maxInternalMantissa, 0, Number::Normalized{}},
Number{1, 38},
__LINE__},
// Maximum actual mantissa range - same as int64 range
Number{1999999999999999999, -18}},
{Number{3214285714285714278, -18}, Number{3111111111111111119, -18}, Number{10, 0}},
// Maximum mantissa range - rounds up to 1e19
{Number{false, maxMantissa, 0, Number::Normalized{}},
Number{false, maxMantissa, 0, Number::Normalized{}},
Number{85'070'591'730'234'615'85, 19},
__LINE__},
Number{1, 38}},
// Maximum int64 range
{Number{Number::kLargestMantissa, 0},
Number{Number::kLargestMantissa, 0},
Number{85'070'591'730'234'615'85, 19},
__LINE__},
{Number{Number::kMaxRep, 0},
Number{Number::kMaxRep, 0},
Number{85'070'591'730'234'615'85, 19}},
});
tests(cSmall, cLarge);
}
Number::setround(Number::RoundingMode::TowardsZero);
{
auto const cSmall = std::to_array<Case>(
{{Number{7}, Number{8}, Number{56}, __LINE__},
{{Number{7}, Number{8}, Number{56}},
{Number{1414213562373095, -15},
Number{1414213562373095, -15},
Number{1999999999999999, -15},
__LINE__},
Number{1999999999999999, -15}},
{Number{-1414213562373095, -15},
Number{1414213562373095, -15},
Number{-1999999999999999, -15},
__LINE__},
Number{-1999999999999999, -15}},
{Number{-1414213562373095, -15},
Number{-1414213562373095, -15},
Number{1999999999999999, -15},
__LINE__},
Number{1999999999999999, -15}},
{Number{3214285714285706, -15},
Number{3111111111111119, -15},
Number{9999999999999999, -15},
__LINE__},
{Number{1000000000000000, -32768},
Number{1000000000000000, -32768},
Number{0},
__LINE__}});
Number{9999999999999999, -15}},
{Number{1000000000000000, -32768}, Number{1000000000000000, -32768}, Number{0}}});
auto const cLarge = std::to_array<Case>(
// Note that items with extremely large mantissas need to be
// calculated, because otherwise they overflow uint64. Items
// from C with larger mantissa
{
{Number{7}, Number{8}, Number{56}, __LINE__},
{Number{7}, Number{8}, Number{56}},
{Number{1414213562373095, -15},
Number{1414213562373095, -15},
Number{1999999999999999861, -18},
__LINE__},
Number{1999999999999999861, -18}},
{Number{-1414213562373095, -15},
Number{1414213562373095, -15},
Number{-1999999999999999861, -18},
__LINE__},
Number{-1999999999999999861, -18}},
{Number{-1414213562373095, -15},
Number{-1414213562373095, -15},
Number{1999999999999999861, -18},
__LINE__},
Number{1999999999999999861, -18}},
{Number{3214285714285706, -15},
Number{3111111111111119, -15},
Number{false, 9999999999999999579ULL, -18, Number::Normalized{}},
__LINE__},
Number{false, 9999999999999999579ULL, -18, Number::Normalized{}}},
{Number{1000000000000000000, -32768},
Number{1000000000000000000, -32768},
Number{0},
__LINE__},
Number{0}},
// Items from cSmall expanded for the larger mantissa,
// except duplicates. Sadly, it looks like sqrt(2)^2 != 2
// with higher precision
{Number{1414213562373095049, -18},
Number{1414213562373095049, -18},
Number{2, 0},
__LINE__},
Number{2, 0}},
{Number{-1414213562373095048, -18},
Number{1414213562373095048, -18},
Number{-1999999999999999997, -18},
__LINE__},
Number{-1999999999999999997, -18}},
{Number{-1414213562373095048, -18},
Number{-1414213562373095049, -18},
Number{1999999999999999999, -18},
__LINE__},
Number{1999999999999999999, -18}},
{Number{3214285714285714278, -18},
Number{3111111111111111119, -18},
Number{10, 0},
__LINE__},
// Maximum internal mantissa range - rounds down to
// maxMantissa/10e1
Number{10, 0}},
// Maximum mantissa range - rounds down to maxMantissa/10e1
// 99'999'999'999'999'999'800'000'000'000'000'000'100
{Number{false, maxInternalMantissa, 0, Number::Normalized{}},
Number{false, maxInternalMantissa, 0, Number::Normalized{}},
Number{false, (maxInternalMantissa / 10) - 1, 20, Number::Normalized{}},
__LINE__},
// Maximum actual mantissa range - same as int64
{Number{false, maxMantissa, 0, Number::Normalized{}},
Number{false, maxMantissa, 0, Number::Normalized{}},
Number{85'070'591'730'234'615'84, 19},
__LINE__},
Number{false, (maxMantissa / 10) - 1, 20, Number::Normalized{}}},
// Maximum int64 range
// 85'070'591'730'234'615'847'396'907'784'232'501'249
{Number{Number::kLargestMantissa, 0},
Number{Number::kLargestMantissa, 0},
Number{85'070'591'730'234'615'84, 19},
__LINE__},
{Number{Number::kMaxRep, 0},
Number{Number::kMaxRep, 0},
Number{85'070'591'730'234'615'84, 19}},
});
tests(cSmall, cLarge);
}
Number::setround(Number::RoundingMode::Downward);
{
auto const cSmall = std::to_array<Case>(
{{Number{7}, Number{8}, Number{56}, __LINE__},
{{Number{7}, Number{8}, Number{56}},
{Number{1414213562373095, -15},
Number{1414213562373095, -15},
Number{1999999999999999, -15},
__LINE__},
Number{1999999999999999, -15}},
{Number{-1414213562373095, -15},
Number{1414213562373095, -15},
Number{-2000000000000000, -15},
__LINE__},
Number{-2000000000000000, -15}},
{Number{-1414213562373095, -15},
Number{-1414213562373095, -15},
Number{1999999999999999, -15},
__LINE__},
Number{1999999999999999, -15}},
{Number{3214285714285706, -15},
Number{3111111111111119, -15},
Number{9999999999999999, -15},
__LINE__},
{Number{1000000000000000, -32768},
Number{1000000000000000, -32768},
Number{0},
__LINE__}});
Number{9999999999999999, -15}},
{Number{1000000000000000, -32768}, Number{1000000000000000, -32768}, Number{0}}});
auto const cLarge = std::to_array<Case>(
// Note that items with extremely large mantissas need to be
// calculated, because otherwise they overflow uint64. Items
// from C with larger mantissa
{
{Number{7}, Number{8}, Number{56}, __LINE__},
{Number{7}, Number{8}, Number{56}},
{Number{1414213562373095, -15},
Number{1414213562373095, -15},
Number{1999999999999999861, -18},
__LINE__},
Number{1999999999999999861, -18}},
{Number{-1414213562373095, -15},
Number{1414213562373095, -15},
Number{-1999999999999999862, -18},
__LINE__},
Number{-1999999999999999862, -18}},
{Number{-1414213562373095, -15},
Number{-1414213562373095, -15},
Number{1999999999999999861, -18},
__LINE__},
Number{1999999999999999861, -18}},
{Number{3214285714285706, -15},
Number{3111111111111119, -15},
Number{false, 9'999'999'999'999'999'579ULL, -18, Number::Normalized{}},
__LINE__},
Number{false, 9'999'999'999'999'999'579ULL, -18, Number::Normalized{}}},
{Number{1000000000000000000, -32768},
Number{1000000000000000000, -32768},
Number{0},
__LINE__},
Number{0}},
// Items from cSmall expanded for the larger mantissa,
// except duplicates. Sadly, it looks like sqrt(2)^2 != 2
// with higher precision
{Number{1414213562373095049, -18},
Number{1414213562373095049, -18},
Number{2, 0},
__LINE__},
Number{2, 0}},
{Number{-1414213562373095048, -18},
Number{1414213562373095048, -18},
Number{-1999999999999999998, -18},
__LINE__},
Number{-1999999999999999998, -18}},
{Number{-1414213562373095048, -18},
Number{-1414213562373095049, -18},
Number{1999999999999999999, -18},
__LINE__},
Number{1999999999999999999, -18}},
{Number{3214285714285714278, -18},
Number{3111111111111111119, -18},
Number{10, 0},
__LINE__},
// Maximum internal mantissa range - rounds down to
// maxInternalMantissa/10-1
Number{10, 0}},
// Maximum mantissa range - rounds down to maxMantissa/10e1
// 99'999'999'999'999'999'800'000'000'000'000'000'100
{Number{false, maxInternalMantissa, 0, Number::Normalized{}},
Number{false, maxInternalMantissa, 0, Number::Normalized{}},
Number{false, (maxInternalMantissa / 10) - 1, 20, Number::Normalized{}},
__LINE__},
// Maximum external mantissa range - same as INT64_MAX (2^63-1)
{Number{false, maxMantissa, 0, Number::Normalized{}},
Number{false, maxMantissa, 0, Number::Normalized{}},
Number{85'070'591'730'234'615'84, 19},
__LINE__},
Number{false, (maxMantissa / 10) - 1, 20, Number::Normalized{}}},
// Maximum int64 range
// 85'070'591'730'234'615'847'396'907'784'232'501'249
{Number{Number::kLargestMantissa, 0},
Number{Number::kLargestMantissa, 0},
Number{85'070'591'730'234'615'84, 19},
__LINE__},
{Number{Number::kMaxRep, 0},
Number{Number::kMaxRep, 0},
Number{85'070'591'730'234'615'84, 19}},
});
tests(cSmall, cLarge);
}
Number::setround(Number::RoundingMode::Upward);
{
auto const cSmall = std::to_array<Case>(
{{Number{7}, Number{8}, Number{56}, __LINE__},
{{Number{7}, Number{8}, Number{56}},
{Number{1414213562373095, -15},
Number{1414213562373095, -15},
Number{2000000000000000, -15},
__LINE__},
Number{2000000000000000, -15}},
{Number{-1414213562373095, -15},
Number{1414213562373095, -15},
Number{-1999999999999999, -15},
__LINE__},
Number{-1999999999999999, -15}},
{Number{-1414213562373095, -15},
Number{-1414213562373095, -15},
Number{2000000000000000, -15},
__LINE__},
Number{2000000000000000, -15}},
{Number{3214285714285706, -15},
Number{3111111111111119, -15},
Number{1000000000000000, -14},
__LINE__},
{Number{1000000000000000, -32768},
Number{1000000000000000, -32768},
Number{0},
__LINE__}});
Number{1000000000000000, -14}},
{Number{1000000000000000, -32768}, Number{1000000000000000, -32768}, Number{0}}});
auto const cLarge = std::to_array<Case>(
// Note that items with extremely large mantissas need to be
// calculated, because otherwise they overflow uint64. Items
// from C with larger mantissa
{
{Number{7}, Number{8}, Number{56}, __LINE__},
{Number{7}, Number{8}, Number{56}},
{Number{1414213562373095, -15},
Number{1414213562373095, -15},
Number{1999999999999999862, -18},
__LINE__},
Number{1999999999999999862, -18}},
{Number{-1414213562373095, -15},
Number{1414213562373095, -15},
Number{-1999999999999999861, -18},
__LINE__},
Number{-1999999999999999861, -18}},
{Number{-1414213562373095, -15},
Number{-1414213562373095, -15},
Number{1999999999999999862, -18},
__LINE__},
Number{1999999999999999862, -18}},
{Number{3214285714285706, -15},
Number{3111111111111119, -15},
Number{999999999999999958, -17},
__LINE__},
Number{999999999999999958, -17}},
{Number{1000000000000000000, -32768},
Number{1000000000000000000, -32768},
Number{0},
__LINE__},
Number{0}},
// Items from cSmall expanded for the larger mantissa,
// except duplicates. Sadly, it looks like sqrt(2)^2 != 2
// with higher precision
{Number{1414213562373095049, -18},
Number{1414213562373095049, -18},
Number{2000000000000000001, -18},
__LINE__},
Number{2000000000000000001, -18}},
{Number{-1414213562373095048, -18},
Number{1414213562373095048, -18},
Number{-1999999999999999997, -18},
__LINE__},
Number{-1999999999999999997, -18}},
{Number{-1414213562373095048, -18},
Number{-1414213562373095049, -18},
Number{2, 0},
__LINE__},
Number{2, 0}},
{Number{3214285714285714278, -18},
Number{3111111111111111119, -18},
Number{1000000000000000001, -17},
__LINE__},
// Maximum internal mantissa range - rounds up to
// minMantissa*10 1e19*1e19=1e38
{Number{false, maxInternalMantissa, 0, Number::Normalized{}},
Number{false, maxInternalMantissa, 0, Number::Normalized{}},
Number{1, 38},
__LINE__},
// Maximum mantissa range - same as int64
Number{1000000000000000001, -17}},
// Maximum mantissa range - rounds up to minMantissa*10
// 1e19*1e19=1e38
{Number{false, maxMantissa, 0, Number::Normalized{}},
Number{false, maxMantissa, 0, Number::Normalized{}},
Number{85'070'591'730'234'615'85, 19},
__LINE__},
Number{1, 38}},
// Maximum int64 range
// 85'070'591'730'234'615'847'396'907'784'232'501'249
{Number{Number::kLargestMantissa, 0},
Number{Number::kLargestMantissa, 0},
Number{85'070'591'730'234'615'85, 19},
__LINE__},
{Number{Number::kMaxRep, 0},
Number{Number::kMaxRep, 0},
Number{85'070'591'730'234'615'85, 19}},
});
tests(cSmall, cLarge);
}
@@ -1048,8 +921,6 @@ TEST(NumberTest, root)
};
*/
auto const maxInternalMantissa = getMaxInternalMantissa();
auto const cSmall = std::to_array<Case>(
{{Number{2}, 2, Number{1414213562373095049, -18}},
{Number{2'000'000}, 2, Number{1414213562373095049, -15}},
@@ -1061,16 +932,16 @@ TEST(NumberTest, root)
{Number{0}, 5, Number{0}},
{Number{5625, -4}, 2, Number{75, -2}}});
auto const cLarge = std::to_array<Case>({
{Number{false, maxInternalMantissa - 9, -1, Number::Normalized{}},
{Number{false, Number::maxMantissa() - 9, -1, Number::Normalized{}},
2,
Number{false, 999'999'999'999'999'999, -9, Number::Normalized{}}},
{Number{false, maxInternalMantissa - 9, 0, Number::Normalized{}},
{Number{false, Number::maxMantissa() - 9, 0, Number::Normalized{}},
2,
Number{false, 3'162'277'660'168'379'330, -9, Number::Normalized{}}},
{Number{Number::kLargestMantissa},
{Number{Number::kMaxRep},
2,
Number{false, 3'037'000'499'976049692, -9, Number::Normalized{}}},
{Number{Number::kLargestMantissa},
{Number{Number::kMaxRep},
4,
Number{false, 55'108'98747006743627, -14, Number::Normalized{}}},
});
@@ -1120,8 +991,6 @@ TEST(NumberTest, root2)
}
};
Number const maxInternalMantissa{getMaxInternalMantissa(), 0, Number::Normalized{}};
auto const cSmall = std::to_array<Number>({
Number{2},
Number{2'000'000},
@@ -1131,10 +1000,7 @@ TEST(NumberTest, root2)
Number{5, -1},
Number{0},
Number{5625, -4},
Number{Number::kLargestMantissa},
maxInternalMantissa,
Number{Number::minMantissa(), 0, Number::Unchecked{}},
Number{Number::maxMantissa(), 0, Number::Unchecked{}},
Number{Number::kMaxRep},
});
test(cSmall);
bool caught = false;
@@ -1503,18 +1369,18 @@ TEST(NumberTest, to_string)
case MantissaRange::MantissaScale::Large:
// Test the edges
// ((exponent < -(28)) || (exponent > -(8)))))
test(Number::min(), "922337203685477581e-32768");
test(Number::min(), "1e-32750");
test(Number::max(), "9223372036854775807e32768");
test(Number::lowest(), "-9223372036854775807e32768");
{
NumberRoundModeGuard const mg(Number::RoundingMode::TowardsZero);
auto const maxMantissa = Number::maxMantissa();
EXPECT_EQ(maxMantissa, 9'223'372'036'854'775'807ULL);
EXPECT_EQ((maxMantissa), (9'999'999'999'999'999'999ULL));
test(
Number{false, maxMantissa, 0, Number::Normalized{}}, "9223372036854775807");
Number{false, maxMantissa, 0, Number::Normalized{}}, "9999999999999999990");
test(
Number{true, maxMantissa, 0, Number::Normalized{}}, "-9223372036854775807");
Number{true, maxMantissa, 0, Number::Normalized{}}, "-9999999999999999990");
test(
Number{std::numeric_limits<std::int64_t>::max(), 0}, "9223372036854775807");
@@ -1865,7 +1731,7 @@ TEST(NumberTest, int64)
Number const initalXrp{kInitialXrp};
EXPECT_GT((initalXrp.exponent()), (0));
Number const maxInt64{Number::kLargestMantissa};
Number const maxInt64{Number::kMaxRep};
EXPECT_GT((maxInt64.exponent()), (0));
// 85'070'591'730'234'615'865'843'651'857'942'052'864 - 38 digits
EXPECT_EQ((power(maxInt64, 2)), (Number{85'070'591'730'234'62, 22}));
@@ -1882,258 +1748,21 @@ TEST(NumberTest, int64)
Number const initalXrp{kInitialXrp};
EXPECT_LE((initalXrp.exponent()), (0));
Number const maxInt64{Number::kLargestMantissa};
Number const maxInt64{Number::kMaxRep};
EXPECT_LE((maxInt64.exponent()), (0));
// 85'070'591'730'234'615'847'396'907'784'232'501'249 - 38 digits
EXPECT_EQ((power(maxInt64, 2)), (Number{85'070'591'730'234'615'85, 19}));
NumberRoundModeGuard const mg(Number::RoundingMode::TowardsZero);
{
auto const maxInternalMantissa = getMaxInternalMantissa();
// Rounds down to fit under 2^63
Number const max = Number{false, maxInternalMantissa, 0, Number::Normalized{}};
// No alterations by the accessors
EXPECT_EQ(max.mantissa(), maxInternalMantissa / 10);
EXPECT_EQ(max.exponent(), 1);
// 99'999'999'999'999'999'800'000'000'000'000'000'100 - also 38
// digits
EXPECT_EQ(
(power(max, 2)),
(Number{false, (maxInternalMantissa / 10) - 1, 20, Number::Normalized{}}));
}
{
auto const maxMantissa = Number::maxMantissa();
Number const max = Number{false, maxMantissa, 0, Number::Normalized{}};
// No alterations by the accessors
EXPECT_EQ(max.mantissa(), maxMantissa);
EXPECT_EQ(max.exponent(), 0);
// 85'070'591'730'234'615'847'396'907'784'232'501'249 - also 38
// digits
EXPECT_EQ(
(power(max, 2)),
(Number{false, 85'070'591'730'234'615'84, 19, Number::Normalized{}}));
}
}
}
}
class NumberTest
{
public:
template <Integral64 T>
[[nodiscard]]
static std::pair<T, int>
normalizeToRangeImpl(
Number const& n,
T minMantissa,
T maxMantissa,
MantissaRange::CuspRoundingFix fix)
{
return Number::Access::normalizeToRangeImpl(n, minMantissa, maxMantissa, fix);
}
};
TEST(NumberTest, normalize_to_range)
{
for (auto const mantissaScale : MantissaRange::getAllScales())
{
NumberMantissaScaleGuard const mg{mantissaScale};
// Test edge-cases of normalizeToRange
auto const scale = Number::getMantissaScale();
auto test = [](Number const& n,
auto const rangeMin,
auto const rangeMax,
auto const expectedMantissa,
auto const expectedExponent,
auto const line) {
auto const normalized = NumberTest::normalizeToRangeImpl(
n, rangeMin, rangeMax, MantissaRange::CuspRoundingFix::Enabled);
EXPECT_EQ(normalized.first, expectedMantissa)
<< "Number " << n << " scaled to " << rangeMax
<< ". Expected mantissa:" << expectedMantissa << ", got: " << normalized.first
<< " @ " << line;
EXPECT_EQ(normalized.second, expectedExponent)
<< "Number " << n << " scaled to " << rangeMax
<< ". Expected exponent:" << expectedExponent << ", got: " << normalized.second
<< " @ " << line;
};
std::int64_t constexpr kIRangeMin = 100;
std::int64_t constexpr kIRangeMax = 999;
std::uint64_t constexpr kURangeMin = 100;
std::uint64_t constexpr kURangeMax = 999;
constexpr static MantissaRange kLargeRange{MantissaRange::MantissaScale::Large};
std::int64_t constexpr kIBigMin = kLargeRange.min;
std::int64_t constexpr kIBigMax = kLargeRange.max;
auto const testSuite = [&](Number const& n,
auto const expectedSmallMantissa,
auto const expectedSmallExponent,
auto const expectedLargeMantissa,
auto const expectedLargeExponent,
auto const line) {
test(n, kIRangeMin, kIRangeMax, expectedSmallMantissa, expectedSmallExponent, line);
test(n, kIBigMin, kIBigMax, expectedLargeMantissa, expectedLargeExponent, line);
// Only test non-negative. testing a negative number with an
// unsigned range will assert, and asserts can't be tested.
if (n.signum() >= 0)
{
test(n, kURangeMin, kURangeMax, expectedSmallMantissa, expectedSmallExponent, line);
test(
n,
kLargeRange.min,
kLargeRange.max,
expectedLargeMantissa,
expectedLargeExponent,
line);
}
};
{
// zero
Number const n{0};
testSuite(
n,
0,
std::numeric_limits<int>::lowest(),
0,
std::numeric_limits<int>::lowest(),
__LINE__);
}
{
// Small positive number
Number const n{2};
testSuite(n, 200, -2, 2'000'000'000'000'000'000, -18, __LINE__);
}
{
// Negative number
Number const n{-2};
testSuite(n, -200, -2, -2'000'000'000'000'000'000, -18, __LINE__);
}
{
// Biggest valid mantissa
Number const n{Number::kLargestMantissa, 0, Number::Normalized{}};
if (scale == MantissaRange::MantissaScale::Small)
{
// With the small mantissa range, the value rounds up. Because
// it rounds up, when scaling up to the full int64 range, it
// can't go over the max, so it is one digit smaller than the
// full value.
testSuite(n, 922, 16, 922'337'203'685'477'600, 1, __LINE__);
}
else
{
testSuite(n, 922, 16, Number::kLargestMantissa, 0, __LINE__);
}
}
{
// Biggest valid mantissa + 1
Number const n{Number::kLargestMantissa + 1, 0, Number::Normalized{}};
if (scale == MantissaRange::MantissaScale::Small)
{
// With the small mantissa range, the value rounds up. Because
// it rounds up, when scaling up to the full int64 range, it
// can't go over the max, so it is one digit smaller than the
// full value.
testSuite(n, 922, 16, 922'337'203'685'477'600, 1, __LINE__);
}
else
{
testSuite(n, 922, 16, (Number::kLargestMantissa / 10) + 1, 1, __LINE__);
}
}
{
// Biggest valid mantissa + 2
Number const n{Number::kLargestMantissa + 2, 0, Number::Normalized{}};
if (scale == MantissaRange::MantissaScale::Small)
{
// With the small mantissa range, the value rounds up. Because
// it rounds up, when scaling up to the full int64 range, it
// can't go over the max, so it is one digit smaller than the
// full value.
testSuite(n, 922, 16, 922'337'203'685'477'600, 1, __LINE__);
}
else
{
testSuite(n, 922, 16, (Number::kLargestMantissa / 10) + 1, 1, __LINE__);
}
}
{
// Biggest valid mantissa + 3
Number const n{Number::kLargestMantissa + 3, 0, Number::Normalized{}};
if (scale == MantissaRange::MantissaScale::Small)
{
// With the small mantissa range, the value rounds up. Because
// it rounds up, when scaling up to the full int64 range, it
// can't go over the max, so it is one digit smaller than the
// full value.
testSuite(n, 922, 16, 922'337'203'685'477'600, 1, __LINE__);
}
else
{
testSuite(n, 922, 16, (Number::kLargestMantissa / 10) + 1, 1, __LINE__);
}
}
{
// int64 min
Number const n{std::numeric_limits<std::int64_t>::min(), 0};
if (scale == MantissaRange::MantissaScale::Small)
{
testSuite(n, -922, 16, -922'337'203'685'477'600, 1, __LINE__);
}
else
{
testSuite(n, -922, 16, -((Number::kLargestMantissa / 10) + 1), 1, __LINE__);
}
}
{
// int64 min + 1
Number const n{std::numeric_limits<std::int64_t>::min() + 1, 0};
if (scale == MantissaRange::MantissaScale::Small)
{
testSuite(n, -922, 16, -922'337'203'685'477'600, 1, __LINE__);
}
else
{
testSuite(n, -922, 16, -Number::kLargestMantissa, 0, __LINE__);
}
}
{
// int64 min - 1
// Need to cast to uint, even though we're dealing with a negative
// number to avoid overflow and UB
Number const n{
true,
static_cast<std::uint64_t>(std::numeric_limits<std::int64_t>::max()) + 1,
0,
Number::Normalized{}};
if (scale == MantissaRange::MantissaScale::Small)
{
testSuite(n, -922, 16, -922'337'203'685'477'600, 1, __LINE__);
}
else
{
testSuite(n, -922, 16, -((Number::kLargestMantissa / 10) + 1), 1, __LINE__);
}
auto const maxMantissa = Number::maxMantissa();
Number const max = Number{false, maxMantissa, 0, Number::Normalized{}};
EXPECT_EQ((max.mantissa()), (maxMantissa / 10));
EXPECT_EQ((max.exponent()), (1));
// 99'999'999'999'999'999'800'000'000'000'000'000'100 - also 38
// digits
EXPECT_EQ(
(power(max, 2)), (Number{false, (maxMantissa / 10) - 1, 20, Number::Normalized{}}));
}
}
}