Compare commits

...

17 Commits

Author SHA1 Message Date
Ed Hennis
06ff77458a fixup! fixup! fixup! fixup! Address review feedback from @copilot 2026-02-05 20:33:30 -05:00
Ed Hennis
f19ecb3b80 fixup! fixup! fixup! Address review feedback from @copilot 2026-02-05 19:56:18 -05:00
Ed Hennis
cc2406bf3f fixup! fixup! Address review feedback from @copilot 2026-02-05 19:13:14 -05:00
Ed Hennis
30c65320e4 fixup! Address review feedback from @copilot 2026-02-05 18:25:23 -05:00
Ed Hennis
569d9ea94e Address review feedback from @copilot
- Update explanations.
- Use saver conversions between signed and unsigned.
2026-02-05 14:06:09 -05:00
Ed Hennis
02b7bcfa2b Merge branch 'develop' into ximinez/number-maxint-range 2026-02-05 13:29:56 -04:00
Ed Hennis
07c0c320a7 Fix formatting 2026-02-05 12:28:43 -05:00
Ed Hennis
d57e37c34b Fix renaming 2026-02-05 12:28:43 -05:00
Ed Hennis
154bb65c35 Merge remote-tracking branch 'upstream/develop' into ximinez/number-maxint-range
* upstream/develop:
  chore: Update secp256k1 and openssl (6327)
  chore: Remove unnecessary script (6326)
  refactor: Replace include guards by '#pragma once' (6322)
  chore: Remove unity builds (6300)
  refactor: Add ServiceRegistry to help modularization (6222)
  fix: Deletes expired NFToken offers from ledger (5707)
  chore: Add .zed editor config directory to .gitignore (6317)
  docs: Update API changelog, add APIv2+APIv3 version documentation (6308)
  fix: Restore config changes that broke standalone mode (6301)
  chore: Add upper-case match for ARM64 in CompilationEnv (6315)
  ci: Update hashes of XRPLF/actions (6316)
  chore: Format all cmake files without comments (6294)
  chore: Add cmake-format pre-commit hook (6279)
  chore: Remove unnecessary `boost::system` requirement from conanfile (6290)
2026-02-04 21:10:15 -05:00
Ed Hennis
111eda22e9 Merge commit '5f638f55536def0d88b970d1018a465a238e55f4' into ximinez/number-maxint-range
* commit '5f638f55536def0d88b970d1018a465a238e55f4':
  chore: Set ColumnLimit to 120 in clang-format (6288)
2026-02-04 21:09:02 -05:00
Ed Hennis
f7b6834d2a Add unit tests for normalizeToRange
- Steal changes from @pratik's #6150 to avoid UB
2026-02-04 21:08:48 -05:00
Ed Hennis
e464adaee6 Clean-ups and tweaks 2026-02-04 21:08:48 -05:00
Ed Hennis
cca92dedca Reduce expensive(?) accesses to thread_local MantissaRange 2026-02-04 21:08:48 -05:00
Ed Hennis
3d6f57a4df Fix bugs
- Simplify shiftExponent().
- Clean up to_string() to prevent integers from including "e0".
- Fix root() and root2() computations by ensuring the mantissas have
  a consistent length.
2026-02-04 21:08:46 -05:00
Ed Hennis
fc29fbe946 Convert "bool negative_ & uint64_t mantissa_" combo back to "rep mantissa_" 2026-02-04 21:08:34 -05:00
Ed Hennis
5e0a8d5c8a Remove the _ suffixes from doNormalize function parameters 2026-02-04 21:08:33 -05:00
Ed Hennis
d27788f12a Use 2^63-1 as maxMantissa for large range
- That makes minMantissa 2^63/10+1.
- Simplifies many of the existing operations, and removes the need for
  the accessors (mantissa() & exponent()) to do any math.
2026-02-04 21:08:33 -05:00
6 changed files with 989 additions and 389 deletions

View File

@@ -9,6 +9,10 @@
#include <ostream> #include <ostream>
#include <string> #include <string>
#ifdef _MSC_VER
#include <boost/multiprecision/cpp_int.hpp>
#endif // !defined(_MSC_VER)
namespace xrpl { namespace xrpl {
class Number; class Number;
@@ -16,18 +20,37 @@ class Number;
std::string std::string
to_string(Number const& amount); 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 log10,
* and rem is value divided by 10^log. 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 <typename 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> template <typename T>
constexpr std::optional<int> constexpr std::optional<int>
logTen(T value) logTen(T value)
{ {
int log = 0; auto const est = logTenEstimate(value);
while (value >= 10 && value % 10 == 0) if (est.second == 1)
{ return est.first;
value /= 10;
++log;
}
if (value == 1)
return log;
return std::nullopt; return std::nullopt;
} }
@@ -41,12 +64,10 @@ isPowerOfTen(T value)
/** MantissaRange defines a range for the mantissa of a normalized Number. /** MantissaRange defines a range for the mantissa of a normalized Number.
* *
* The mantissa is in the range [min, max], where * The mantissa is in the range [min, max], where
* * min is a power of 10, and
* * max = min * 10 - 1.
* *
* The mantissa_scale enum indicates whether the range is "small" or "large". * The mantissa_scale enum indicates whether the range is "small" or "large".
* This intentionally restricts the number of MantissaRanges that can be * This intentionally restricts the number of MantissaRanges that can be
* instantiated to two: one for each scale. * used to two: one for each scale.
* *
* The "small" scale is based on the behavior of STAmount for IOUs. It has a min * 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 * value of 10^15, and a max value of 10^16-1. This was sufficient for
@@ -60,8 +81,8 @@ isPowerOfTen(T value)
* "large" scale. * "large" scale.
* *
* The "large" scale is intended to represent all values that can be represented * The "large" scale is 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 * by an STAmount - IOUs, XRP, and MPTs. It has a min value of 2^63/10+1
* value of 10^19-1. * (truncated), and a max value of 2^63-1.
* *
* Note that if the mentioned amendments are eventually retired, this class * 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 * should be left in place, but the "small" scale option should be removed. This
@@ -73,25 +94,50 @@ struct MantissaRange
enum mantissa_scale { small, large }; enum mantissa_scale { small, large };
explicit constexpr MantissaRange(mantissa_scale scale_) explicit constexpr MantissaRange(mantissa_scale scale_)
: min(getMin(scale_)), max(min * 10 - 1), log(logTen(min).value_or(-1)), scale(scale_) : max(getMax(scale_))
, min(computeMin(max))
, referenceMin(getReferenceMin(scale_, min))
, log(computeLog(min))
, scale(scale_)
{ {
// Since this is constexpr, if any of these throw, it won't compile
if (min * 10 <= max)
throw std::out_of_range("min * 10 <= max");
if (max / 10 >= min)
throw std::out_of_range("max / 10 >= min");
if ((min - 1) * 10 > max)
throw std::out_of_range("(min - 1) * 10 > max");
// This is a little hacky
if ((max + 10) / 10 < min)
throw std::out_of_range("(max + 10) / 10 < min");
} }
rep min; // Explicitly delete copy and move operations
MantissaRange(MantissaRange const&) = delete;
MantissaRange(MantissaRange&&) = delete;
MantissaRange&
operator=(MantissaRange const&) = delete;
MantissaRange&
operator=(MantissaRange&&) = delete;
rep max; rep max;
rep min;
// This is not a great name. Used to determine if mantissas are in range,
// but have fewer digits than max
rep referenceMin;
int log; int log;
mantissa_scale scale; mantissa_scale scale;
private: private:
static constexpr rep static constexpr rep
getMin(mantissa_scale scale_) getMax(mantissa_scale scale)
{ {
switch (scale_) switch (scale)
{ {
case small: case small:
return 1'000'000'000'000'000ULL; return 9'999'999'999'999'999ULL;
case large: case large:
return 1'000'000'000'000'000'000ULL; return std::numeric_limits<std::int64_t>::max();
default: default:
// Since this can never be called outside a non-constexpr // Since this can never be called outside a non-constexpr
// context, this throw assures that the build fails if an // context, this throw assures that the build fails if an
@@ -99,19 +145,59 @@ private:
throw std::runtime_error("Unknown mantissa scale"); throw std::runtime_error("Unknown mantissa scale");
} }
} }
static constexpr rep
computeMin(rep max)
{
return max / 10 + 1;
}
static constexpr rep
getReferenceMin(mantissa_scale scale, rep min)
{
switch (scale)
{
case large:
return 1'000'000'000'000'000'000ULL;
default:
if (isPowerOfTen(min))
return min;
throw std::runtime_error("Unknown/bad mantissa scale");
}
}
static constexpr rep
computeLog(rep min)
{
auto const estimate = logTenEstimate(min);
return estimate.first + (estimate.second == 1 ? 0 : 1);
}
}; };
// Like std::integral, but only 64-bit integral types. // Like std::integral, but only 64-bit integral types.
template <class T> template <class T>
concept Integral64 = std::is_same_v<T, std::int64_t> || std::is_same_v<T, std::uint64_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. /** 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 - * It can represent all values that can be represented by an STAmount -
* regardless of asset type - XRPAmount, MPTAmount, and IOUAmount, with at least * regardless of asset type - XRPAmount, MPTAmount, and IOUAmount, with at least
* as much precision as those types require. * as much precision as those types require.
* *
* ---- Internal Representation ---- * ---- Internal Operational Representation ----
* *
* Internally, Number is represented with three values: * Internally, Number is represented with three values:
* 1. a bool sign flag, * 1. a bool sign flag,
@@ -126,15 +212,21 @@ concept Integral64 = std::is_same_v<T, std::int64_t> || std::is_same_v<T, std::u
* *
* A non-zero mantissa is (almost) always normalized, meaning it and the * A non-zero mantissa is (almost) always normalized, meaning it and the
* exponent are grown or shrunk until the mantissa is in the range * exponent are grown or shrunk until the mantissa is in the range
* [MantissaRange.min, MantissaRange.max]. * [MantissaRange.referenceMin, MantissaRange.referenceMin * 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.
* *
* Note: * Note:
* 1. Normalization can be disabled by using the "unchecked" ctor tag. This * 1. Normalization can be disabled by using the "unchecked" ctor tag. This
* should only be used at specific conversion points, some constexpr * should only be used at specific conversion points, some constexpr
* values, and in unit tests. * values, and in unit tests.
* 2. The max of the "large" range, 10^19-1, is the largest 10^X-1 value that * 2. Unlike MantissaRange.min, referenceMin is always an exact power of 10,
* fits in an unsigned 64-bit number. (10^19-1 < 2^64-1 and * so a mantissa in the internal representation will always have a
* 10^20-1 > 2^64-1). This avoids under- and overflows. * consistent number of digits.
* 3. The functions toInternal() and fromInternal() are used to convert
* between the two representations.
* *
* ---- External Interface ---- * ---- External Interface ----
* *
@@ -147,13 +239,12 @@ concept Integral64 = std::is_same_v<T, std::int64_t> || std::is_same_v<T, std::u
* represent the full range of valid XRP and MPT integer values accurately. * represent the full range of valid XRP and MPT integer values accurately.
* *
* Note: * Note:
* 1. 2^63-1 is between 10^18 and 10^19-1, which are the limits of the "large" * 1. The "large" mantissa range is (2^63/10+1) to 2^63-1. 2^63-1 is between
* mantissa range. * 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.
* 2. The functions mantissa() and exponent() return the external view of the * 2. The functions mantissa() and exponent() return the external view of the
* Number value, specifically using a signed 63-bit mantissa. This may * Number value, specifically using a signed 63-bit mantissa.
* 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()) * 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 * 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 * on-ledger are non-negative. This is due to implementation details of
@@ -208,8 +299,7 @@ class Number
using rep = std::int64_t; using rep = std::int64_t;
using internalrep = MantissaRange::rep; using internalrep = MantissaRange::rep;
bool negative_{false}; rep mantissa_{0};
internalrep mantissa_{0};
int exponent_{std::numeric_limits<int>::lowest()}; int exponent_{std::numeric_limits<int>::lowest()};
public: public:
@@ -217,10 +307,6 @@ public:
constexpr static int minExponent = -32768; constexpr static int minExponent = -32768;
constexpr static int maxExponent = 32768; constexpr static int maxExponent = 32768;
constexpr static internalrep maxRep = std::numeric_limits<rep>::max();
static_assert(maxRep == 9'223'372'036'854'775'807);
static_assert(-maxRep == std::numeric_limits<rep>::min() + 1);
// May need to make unchecked private // May need to make unchecked private
struct unchecked struct unchecked
{ {
@@ -294,7 +380,7 @@ public:
friend constexpr bool friend constexpr bool
operator==(Number const& x, Number const& y) noexcept operator==(Number const& x, Number const& y) noexcept
{ {
return x.negative_ == y.negative_ && x.mantissa_ == y.mantissa_ && x.exponent_ == y.exponent_; return x.mantissa_ == y.mantissa_ && x.exponent_ == y.exponent_;
} }
friend constexpr bool friend constexpr bool
@@ -308,8 +394,8 @@ public:
{ {
// If the two amounts have different signs (zero is treated as positive) // If the two amounts have different signs (zero is treated as positive)
// then the comparison is true iff the left is negative. // then the comparison is true iff the left is negative.
bool const lneg = x.negative_; bool const lneg = x.mantissa_ < 0;
bool const rneg = y.negative_; bool const rneg = y.mantissa_ < 0;
if (lneg != rneg) if (lneg != rneg)
return lneg; return lneg;
@@ -337,7 +423,7 @@ public:
constexpr int constexpr int
signum() const noexcept signum() const noexcept
{ {
return negative_ ? -1 : (mantissa_ ? 1 : 0); return mantissa_ < 0 ? -1 : (mantissa_ ? 1 : 0);
} }
Number Number
@@ -376,6 +462,9 @@ public:
friend Number friend Number
root2(Number f); root2(Number f);
friend Number
power(Number const& f, unsigned n, unsigned d);
// Thread local rounding control. Default is to_nearest // Thread local rounding control. Default is to_nearest
enum rounding_mode { to_nearest, towards_zero, downward, upward }; enum rounding_mode { to_nearest, towards_zero, downward, upward };
static rounding_mode static rounding_mode
@@ -440,22 +529,39 @@ private:
static_assert(isPowerOfTen(smallRange.min)); static_assert(isPowerOfTen(smallRange.min));
static_assert(smallRange.min == 1'000'000'000'000'000LL); static_assert(smallRange.min == 1'000'000'000'000'000LL);
static_assert(smallRange.max == 9'999'999'999'999'999LL); static_assert(smallRange.max == 9'999'999'999'999'999LL);
static_assert(smallRange.referenceMin == smallRange.min);
static_assert(smallRange.log == 15); static_assert(smallRange.log == 15);
static_assert(smallRange.min < maxRep);
static_assert(smallRange.max < maxRep);
constexpr static MantissaRange largeRange{MantissaRange::large}; constexpr static MantissaRange largeRange{MantissaRange::large};
static_assert(isPowerOfTen(largeRange.min)); static_assert(!isPowerOfTen(largeRange.min));
static_assert(largeRange.min == 1'000'000'000'000'000'000ULL); static_assert(largeRange.min == 922'337'203'685'477'581ULL);
static_assert(largeRange.max == internalrep(9'999'999'999'999'999'999ULL)); static_assert(largeRange.max == internalrep(9'223'372'036'854'775'807ULL));
static_assert(largeRange.max == std::numeric_limits<rep>::max());
static_assert(largeRange.referenceMin == 1'000'000'000'000'000'000ULL);
static_assert(largeRange.log == 18); static_assert(largeRange.log == 18);
static_assert(largeRange.min < maxRep); // There are 2 values that will not fit in largeRange without some extra
static_assert(largeRange.max > maxRep); // work
// * 9223372036854775808
// * 9223372036854775809
// They both end up < min, but with a leftover. If they round up, everything
// will be fine. If they don't, we'll need to bring them up into range.
// Guard::bringIntoRange handles this situation.
// The range for the mantissa when normalized. // The range for the mantissa when normalized.
// Use reference_wrapper to avoid making copies, and prevent accidentally // Use reference_wrapper to avoid making copies, and prevent accidentally
// changing the values inside the range. // changing the values inside the range.
static thread_local std::reference_wrapper<MantissaRange const> range_; static thread_local std::reference_wrapper<MantissaRange const> range_;
// 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);
void void
normalize(); normalize();
@@ -478,11 +584,14 @@ private:
friend void friend void
doNormalize( doNormalize(
bool& negative, bool& negative,
T& mantissa_, T& mantissa,
int& exponent_, int& exponent,
MantissaRange::rep const& minMantissa, MantissaRange::rep const& minMantissa,
MantissaRange::rep const& maxMantissa); MantissaRange::rep const& maxMantissa);
bool
isnormal(MantissaRange const& range) const noexcept;
bool bool
isnormal() const noexcept; isnormal() const noexcept;
@@ -492,18 +601,64 @@ private:
Number Number
shiftExponent(int exponentDelta) const; shiftExponent(int exponentDelta) const;
// Safely convert rep (int64) mantissa to internalrep (uint64). If the rep // Safely return the absolute value of a rep (int64) mantissa as an internalrep (uint64).
// 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 static internalrep
externalToInternal(rep mantissa); externalToInternal(rep mantissa);
/** Breaks down the number into components, potentially de-normalizing it.
*
* Ensures that the mantissa always has range_.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 range_.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; class Guard;
public:
constexpr static internalrep largestMantissa = largeRange.max;
}; };
inline constexpr Number::Number(bool negative, internalrep mantissa, int exponent, unchecked) noexcept inline constexpr Number::Number(bool negative, internalrep mantissa, int exponent, unchecked) noexcept
: negative_(negative), mantissa_{mantissa}, exponent_{exponent} : mantissa_{negative ? -static_cast<rep>(mantissa) : static_cast<rep>(mantissa)}, exponent_{exponent}
{ {
} }
@@ -514,12 +669,6 @@ inline constexpr Number::Number(internalrep mantissa, int exponent, unchecked) n
constexpr static Number numZero{}; constexpr static Number numZero{};
inline Number::Number(bool negative, internalrep mantissa, int exponent, normalized)
: Number(negative, mantissa, exponent, unchecked{})
{
normalize();
}
inline Number::Number(internalrep mantissa, int exponent, normalized) : Number(false, mantissa, exponent, normalized{}) inline Number::Number(internalrep mantissa, int exponent, normalized) : Number(false, mantissa, exponent, normalized{})
{ {
} }
@@ -541,17 +690,7 @@ inline Number::Number(rep mantissa) : Number{mantissa, 0}
inline constexpr Number::rep inline constexpr Number::rep
Number::mantissa() const noexcept Number::mantissa() const noexcept
{ {
auto m = mantissa_; return mantissa_;
if (m > maxRep)
{
XRPL_ASSERT_PARTS(
!isnormal() || (m % 10 == 0 && m / 10 <= maxRep),
"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. /** Returns the exponent of the external view of the Number.
@@ -562,16 +701,7 @@ Number::mantissa() const noexcept
inline constexpr int inline constexpr int
Number::exponent() const noexcept Number::exponent() const noexcept
{ {
auto e = exponent_; return exponent_;
if (mantissa_ > maxRep)
{
XRPL_ASSERT_PARTS(
!isnormal() || (mantissa_ % 10 == 0 && mantissa_ / 10 <= maxRep),
"xrpl::Number::exponent",
"large normalized mantissa has no remainder");
++e;
}
return e;
} }
inline constexpr Number inline constexpr Number
@@ -586,7 +716,7 @@ Number::operator-() const noexcept
if (mantissa_ == 0) if (mantissa_ == 0)
return Number{}; return Number{};
auto x = *this; auto x = *this;
x.negative_ = !x.negative_; x.mantissa_ = -x.mantissa_;
return x; return x;
} }
@@ -667,39 +797,55 @@ Number::min() noexcept
inline Number inline Number
Number::max() noexcept Number::max() noexcept
{ {
return Number{false, std::min(range_.get().max, maxRep), maxExponent, unchecked{}}; return Number{false, range_.get().max, maxExponent, unchecked{}};
} }
inline Number inline Number
Number::lowest() noexcept Number::lowest() noexcept
{ {
return Number{true, std::min(range_.get().max, maxRep), maxExponent, unchecked{}}; return Number{true, range_.get().max, maxExponent, unchecked{}};
}
inline bool
Number::isnormal(MantissaRange const& range) const noexcept
{
auto const abs_m = externalToInternal(mantissa_);
return *this == Number{} ||
(range.min <= abs_m && abs_m <= range.max && //
minExponent <= exponent_ && exponent_ <= maxExponent);
} }
inline bool inline bool
Number::isnormal() const noexcept Number::isnormal() const noexcept
{ {
MantissaRange const& range = range_; return isnormal(range_);
auto const abs_m = mantissa_;
return *this == Number{} ||
(range.min <= abs_m && abs_m <= range.max && (abs_m <= maxRep || abs_m % 10 == 0) && minExponent <= exponent_ &&
exponent_ <= maxExponent);
} }
template <Integral64 T> template <Integral64 T>
std::pair<T, int> std::pair<T, int>
Number::normalizeToRange(T minMantissa, T maxMantissa) const Number::normalizeToRange(T minMantissa, T maxMantissa) const
{ {
bool negative = negative_; bool negative = mantissa_ < 0;
internalrep mantissa = mantissa_; internalrep mantissa = externalToInternal(mantissa_);
int exponent = exponent_; int exponent = exponent_;
if constexpr (std::is_unsigned_v<T>) if constexpr (std::is_unsigned_v<T>)
{
XRPL_ASSERT_PARTS(!negative, "xrpl::Number::normalizeToRange", "Number is non-negative for unsigned range."); XRPL_ASSERT_PARTS(!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); Number::normalize(negative, mantissa, exponent, minMantissa, maxMantissa);
auto const sign = negative ? -1 : 1; // Cast mantissa to signed type first (if T is a signed type) to avoid
return std::make_pair(static_cast<T>(sign * mantissa), exponent); // 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);
} }
inline constexpr Number inline constexpr Number

View File

@@ -231,7 +231,7 @@ std::size_t constexpr maxMPTokenMetadataLength = 1024;
/** The maximum amount of MPTokenIssuance */ /** The maximum amount of MPTokenIssuance */
std::uint64_t constexpr maxMPTokenAmount = 0x7FFF'FFFF'FFFF'FFFFull; std::uint64_t constexpr maxMPTokenAmount = 0x7FFF'FFFF'FFFF'FFFFull;
static_assert(Number::maxRep >= maxMPTokenAmount); static_assert(Number::largestMantissa >= maxMPTokenAmount);
/** The maximum length of Data payload */ /** The maximum length of Data payload */
std::size_t constexpr maxDataPayloadLength = 256; std::size_t constexpr maxDataPayloadLength = 256;

View File

@@ -521,6 +521,7 @@ STAmount::fromNumber(A const& a, Number const& number)
return STAmount{asset, intValue, 0, negative}; 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(cMinValue, cMaxValue); auto const [mantissa, exponent] = working.normalizeToRange(cMinValue, cMaxValue);
return STAmount{asset, mantissa, exponent, negative}; return STAmount{asset, mantissa, exponent, negative};

View File

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

View File

@@ -11,18 +11,16 @@
#include <numeric> #include <numeric>
#include <stdexcept> #include <stdexcept>
#include <string> #include <string>
#include <string_view>
#include <type_traits> #include <type_traits>
#include <utility> #include <utility>
#ifdef _MSC_VER #ifdef _MSC_VER
#pragma message("Using boost::multiprecision::uint128_t and int128_t") #pragma message("Using boost::multiprecision::uint128_t and int128_t")
#include <boost/multiprecision/cpp_int.hpp> #endif
using uint128_t = boost::multiprecision::uint128_t;
using int128_t = boost::multiprecision::int128_t; using uint128_t = xrpl::detail::uint128_t;
#else // !defined(_MSC_VER) using int128_t = xrpl::detail::int128_t;
using uint128_t = __uint128_t;
using int128_t = __int128_t;
#endif // !defined(_MSC_VER)
namespace xrpl { namespace xrpl {
@@ -61,9 +59,6 @@ Number::setMantissaScale(MantissaRange::mantissa_scale scale)
// precision to an operation. This enables the final result // precision to an operation. This enables the final result
// to be correctly rounded to the internal precision of Number. // 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 class Number::Guard
{ {
std::uint64_t digits_; // 16 decimal guard digits std::uint64_t digits_; // 16 decimal guard digits
@@ -99,7 +94,7 @@ public:
round() noexcept; round() noexcept;
// Modify the result to the correctly rounded value // Modify the result to the correctly rounded value
template <UnsignedMantissa T> template <detail::UnsignedMantissa T>
void void
doRoundUp( doRoundUp(
bool& negative, bool& negative,
@@ -107,22 +102,22 @@ public:
int& exponent, int& exponent,
internalrep const& minMantissa, internalrep const& minMantissa,
internalrep const& maxMantissa, internalrep const& maxMantissa,
std::string location); std::string_view location);
// Modify the result to the correctly rounded value // Modify the result to the correctly rounded value
template <UnsignedMantissa T> template <detail::UnsignedMantissa T>
void void
doRoundDown(bool& negative, T& mantissa, int& exponent, internalrep const& minMantissa); doRoundDown(bool& negative, T& mantissa, int& exponent, internalrep const& minMantissa);
// Modify the result to the correctly rounded value // Modify the result to the correctly rounded value
void void
doRound(rep& drops, std::string location); doRound(rep& drops, std::string_view location);
private: private:
void void
doPush(unsigned d) noexcept; doPush(unsigned d) noexcept;
template <UnsignedMantissa T> template <detail::UnsignedMantissa T>
void void
bringIntoRange(bool& negative, T& mantissa, int& exponent, internalrep const& minMantissa); bringIntoRange(bool& negative, T& mantissa, int& exponent, internalrep const& minMantissa);
}; };
@@ -209,7 +204,7 @@ Number::Guard::round() noexcept
return 0; return 0;
} }
template <UnsignedMantissa T> template <detail::UnsignedMantissa T>
void void
Number::Guard::bringIntoRange(bool& negative, T& mantissa, int& exponent, internalrep const& minMantissa) Number::Guard::bringIntoRange(bool& negative, T& mantissa, int& exponent, internalrep const& minMantissa)
{ {
@@ -224,13 +219,13 @@ Number::Guard::bringIntoRange(bool& negative, T& mantissa, int& exponent, intern
{ {
constexpr Number zero = Number{}; constexpr Number zero = Number{};
negative = zero.negative_; negative = false;
mantissa = zero.mantissa_; mantissa = zero.mantissa_;
exponent = zero.exponent_; exponent = zero.exponent_;
} }
} }
template <UnsignedMantissa T> template <detail::UnsignedMantissa T>
void void
Number::Guard::doRoundUp( Number::Guard::doRoundUp(
bool& negative, bool& negative,
@@ -238,7 +233,7 @@ Number::Guard::doRoundUp(
int& exponent, int& exponent,
internalrep const& minMantissa, internalrep const& minMantissa,
internalrep const& maxMantissa, internalrep const& maxMantissa,
std::string location) std::string_view location)
{ {
auto r = round(); auto r = round();
if (r == 1 || (r == 0 && (mantissa & 1) == 1)) if (r == 1 || (r == 0 && (mantissa & 1) == 1))
@@ -246,7 +241,7 @@ Number::Guard::doRoundUp(
++mantissa; ++mantissa;
// Ensure mantissa after incrementing fits within both the // Ensure mantissa after incrementing fits within both the
// min/maxMantissa range and is a valid "rep". // min/maxMantissa range and is a valid "rep".
if (mantissa > maxMantissa || mantissa > maxRep) if (mantissa > maxMantissa)
{ {
mantissa /= 10; mantissa /= 10;
++exponent; ++exponent;
@@ -254,10 +249,10 @@ Number::Guard::doRoundUp(
} }
bringIntoRange(negative, mantissa, exponent, minMantissa); bringIntoRange(negative, mantissa, exponent, minMantissa);
if (exponent > maxExponent) if (exponent > maxExponent)
throw std::overflow_error(location); throw std::overflow_error(std::string{location});
} }
template <UnsignedMantissa T> template <detail::UnsignedMantissa T>
void void
Number::Guard::doRoundDown(bool& negative, T& mantissa, int& exponent, internalrep const& minMantissa) Number::Guard::doRoundDown(bool& negative, T& mantissa, int& exponent, internalrep const& minMantissa)
{ {
@@ -276,21 +271,22 @@ Number::Guard::doRoundDown(bool& negative, T& mantissa, int& exponent, internalr
// Modify the result to the correctly rounded value // Modify the result to the correctly rounded value
void void
Number::Guard::doRound(rep& drops, std::string location) Number::Guard::doRound(rep& drops, std::string_view location)
{ {
auto r = round(); auto r = round();
if (r == 1 || (r == 0 && (drops & 1) == 1)) if (r == 1 || (r == 0 && (drops & 1) == 1))
{ {
if (drops >= maxRep) auto const& range = range_.get();
if (drops >= range.max)
{ {
static_assert(sizeof(internalrep) == sizeof(rep)); static_assert(sizeof(internalrep) == sizeof(rep));
// This should be impossible, because it's impossible to represent // This should be impossible, because it's impossible to represent
// "maxRep + 0.6" in Number, regardless of the scale. There aren't // "largestMantissa + 0.6" in Number, regardless of the scale. There aren't
// enough digits available. You'd either get a mantissa of "maxRep" // enough digits available. You'd either get a mantissa of "largestMantissa "
// or "(maxRep + 1) / 10", neither of which will round up when // or "largestMantissa / 10 + 1", neither of which will round up when
// converting to rep, though the latter might overflow _before_ // converting to rep, though the latter might overflow _before_
// rounding. // rounding.
throw std::overflow_error(location); // LCOV_EXCL_LINE throw std::overflow_error(std::string{location}); // LCOV_EXCL_LINE
} }
++drops; ++drops;
} }
@@ -310,23 +306,126 @@ Number::externalToInternal(rep mantissa)
// If the mantissa is already positive, just return it // If the mantissa is already positive, just return it
if (mantissa >= 0) if (mantissa >= 0)
return mantissa; 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 // Cast to unsigned before negating to avoid undefined behavior
// int128_t, negate that, and cast it back down to the internalrep // when v == INT64_MIN (negating INT64_MIN in signed is UB)
// In practice, this is only going to cover the case of return -static_cast<internalrep>(mantissa);
// std::numeric_limits<rep>::min(). }
int128_t temp = mantissa;
return static_cast<internalrep>(-temp); /** Breaks down the number into components, potentially de-normalizing it.
*
* Ensures that the mantissa always has range_.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 referenceMin = range.referenceMin;
auto const minMantissa = range.min;
if (mantissa != 0 && mantissa >= minMantissa && mantissa < referenceMin)
{
// Ensure the mantissa has the correct number of digits
mantissa *= 10;
--exponent;
XRPL_ASSERT_PARTS(
mantissa >= referenceMin && mantissa < referenceMin * 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 range_.log + 1 digits.
*
*/
template <detail::UnsignedMantissa Rep>
std::tuple<bool, Rep, int>
Number::toInternal() const
{
return toInternal(range_);
}
/** 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::range_.get();
}
fromInternal(negative, mantissa, exponent, pRange);
} }
constexpr Number constexpr Number
Number::oneSmall() Number::oneSmall()
{ {
return Number{false, Number::smallRange.min, -Number::smallRange.log, Number::unchecked{}}; return Number{false, Number::smallRange.referenceMin, -Number::smallRange.log, Number::unchecked{}};
}; };
constexpr Number oneSml = Number::oneSmall(); constexpr Number oneSml = Number::oneSmall();
@@ -334,101 +433,84 @@ constexpr Number oneSml = Number::oneSmall();
constexpr Number constexpr Number
Number::oneLarge() Number::oneLarge()
{ {
return Number{false, Number::largeRange.min, -Number::largeRange.log, Number::unchecked{}}; return Number{false, Number::largeRange.referenceMin, -Number::largeRange.log, Number::unchecked{}};
}; };
constexpr Number oneLrg = Number::oneLarge(); constexpr Number oneLrg = Number::oneLarge();
Number Number
Number::one() Number::one(MantissaRange const& range)
{ {
if (&range_.get() == &smallRange) if (&range == &smallRange)
return oneSml; return oneSml;
XRPL_ASSERT(&range_.get() == &largeRange, "Number::one() : valid range_"); XRPL_ASSERT(&range == &largeRange, "Number::one() : valid range");
return oneLrg; return oneLrg;
} }
Number
Number::one()
{
return one(range_);
}
// Use the member names in this static function for now so the diff is cleaner // Use the member names in this static function for now so the diff is cleaner
// TODO: Rename the function parameters to get rid of the "_" suffix
template <class T> template <class T>
void void
doNormalize( doNormalize(
bool& negative, bool& negative,
T& mantissa_, T& mantissa,
int& exponent_, int& exponent,
MantissaRange::rep const& minMantissa, MantissaRange::rep const& minMantissa,
MantissaRange::rep const& maxMantissa) MantissaRange::rep const& maxMantissa)
{ {
auto constexpr minExponent = Number::minExponent; auto constexpr minExponent = Number::minExponent;
auto constexpr maxExponent = Number::maxExponent; auto constexpr maxExponent = Number::maxExponent;
auto constexpr maxRep = Number::maxRep;
using Guard = Number::Guard; using Guard = Number::Guard;
constexpr Number zero = Number{}; constexpr Number zero = Number{};
if (mantissa_ == 0) if (mantissa == 0 || (mantissa < minMantissa && exponent <= minExponent))
{ {
mantissa_ = zero.mantissa_; mantissa = zero.mantissa_;
exponent_ = zero.exponent_; exponent = zero.exponent_;
negative = zero.negative_; negative = false;
return; return;
} }
auto m = mantissa_;
while ((m < minMantissa) && (exponent_ > minExponent)) auto m = mantissa;
while ((m < minMantissa) && (exponent > minExponent))
{ {
m *= 10; m *= 10;
--exponent_; --exponent;
} }
Guard g; Guard g;
if (negative) if (negative)
g.set_negative(); g.set_negative();
while (m > maxMantissa) while (m > maxMantissa)
{ {
if (exponent_ >= maxExponent) if (exponent >= maxExponent)
throw std::overflow_error("Number::normalize 1"); throw std::overflow_error("Number::normalize 1");
g.push(m % 10); g.push(m % 10);
m /= 10; m /= 10;
++exponent_; ++exponent;
} }
if ((exponent_ < minExponent) || (m < minMantissa)) if ((exponent < minExponent) || (m == 0))
{ {
mantissa_ = zero.mantissa_; mantissa = zero.mantissa_;
exponent_ = zero.exponent_; exponent = zero.exponent_;
negative = zero.negative_; negative = false;
return; return;
} }
// When using the largeRange, "m" needs fit within an int64, even if XRPL_ASSERT_PARTS(m <= maxMantissa, "xrpl::doNormalize", "intermediate mantissa fits in int64");
// the final mantissa_ is going to end up larger to fit within the mantissa = m;
// MantissaRange. Cut it down here so that the rounding will be done while
// it's smaller. g.doRoundUp(negative, mantissa, exponent, minMantissa, maxMantissa, "Number::normalize 2");
//
// 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 > maxRep)
{
if (exponent_ >= maxExponent)
throw std::overflow_error("Number::normalize 1.5");
g.push(m % 10);
m /= 10;
++exponent_;
}
// Before modification, m should be within the min/max range. After
// modification, it must be less than maxRep. In other words, the original
// value should have been no more than maxRep * 10.
// (maxRep * 10 > maxMantissa)
XRPL_ASSERT_PARTS(m <= maxRep, "xrpl::doNormalize", "intermediate mantissa fits in int64");
mantissa_ = m;
g.doRoundUp(negative, mantissa_, exponent_, minMantissa, maxMantissa, "Number::normalize 2");
XRPL_ASSERT_PARTS( XRPL_ASSERT_PARTS(
mantissa_ >= minMantissa && mantissa_ <= maxMantissa, "xrpl::doNormalize", "final mantissa fits in range"); mantissa >= minMantissa && mantissa <= maxMantissa, "xrpl::doNormalize", "final mantissa fits in range");
XRPL_ASSERT_PARTS(
exponent >= minExponent && exponent <= maxExponent, "xrpl::doNormalize", "final exponent fits in range");
} }
template <> template <>
@@ -467,11 +549,20 @@ Number::normalize<unsigned long>(
doNormalize(negative, mantissa, exponent, minMantissa, maxMantissa); doNormalize(negative, mantissa, exponent, minMantissa, maxMantissa);
} }
void
Number::normalize(MantissaRange const& range)
{
auto [negative, mantissa, exponent] = toInternal(range);
normalize(negative, mantissa, exponent, range.min, range.max);
fromInternal(negative, mantissa, exponent, &range);
}
void void
Number::normalize() Number::normalize()
{ {
auto const& range = range_.get(); normalize(range_);
normalize(negative_, mantissa_, exponent_, range.min, range.max);
} }
// Copy the number, but set a new exponent. Because the mantissa doesn't change, // Copy the number, but set a new exponent. Because the mantissa doesn't change,
@@ -481,21 +572,33 @@ Number
Number::shiftExponent(int exponentDelta) const Number::shiftExponent(int exponentDelta) const
{ {
XRPL_ASSERT_PARTS(isnormal(), "xrpl::Number::shiftExponent", "normalized"); XRPL_ASSERT_PARTS(isnormal(), "xrpl::Number::shiftExponent", "normalized");
auto const newExponent = exponent_ + exponentDelta;
if (newExponent >= maxExponent) Number result = *this;
result.exponent_ += exponentDelta;
if (result.exponent_ >= maxExponent)
throw std::overflow_error("Number::shiftExponent"); throw std::overflow_error("Number::shiftExponent");
if (newExponent < minExponent) if (result.exponent_ < minExponent)
{ {
return Number{}; return Number{};
} }
Number const result{negative_, mantissa_, newExponent, unchecked{}};
XRPL_ASSERT_PARTS(result.isnormal(), "xrpl::Number::shiftExponent", "result is normalized");
return result; return result;
} }
Number::Number(bool negative, internalrep mantissa, int exponent, normalized)
{
auto const& range = range_.get();
normalize(negative, mantissa, exponent, range.min, range.max);
fromInternal(negative, mantissa, exponent, &range);
}
Number& Number&
Number::operator+=(Number const& y) Number::operator+=(Number const& y)
{ {
auto const& range = range_.get();
constexpr Number zero = Number{}; constexpr Number zero = Number{};
if (y == zero) if (y == zero)
return *this; return *this;
@@ -510,7 +613,7 @@ Number::operator+=(Number const& y)
return *this; return *this;
} }
XRPL_ASSERT(isnormal() && y.isnormal(), "xrpl::Number::operator+=(Number) : is normal"); XRPL_ASSERT(isnormal(range) && y.isnormal(range), "xrpl::Number::operator+=(Number) : is normal");
// *n = negative // *n = negative
// *s = sign // *s = sign
// *m = mantissa // *m = mantissa
@@ -518,13 +621,10 @@ Number::operator+=(Number const& y)
// Need to use uint128_t, because large mantissas can overflow when added // Need to use uint128_t, because large mantissas can overflow when added
// together. // together.
bool xn = negative_; auto [xn, xm, xe] = toInternal<uint128_t>(range);
uint128_t xm = mantissa_;
auto xe = exponent_; auto [yn, ym, ye] = y.toInternal<uint128_t>(range);
bool yn = y.negative_;
uint128_t ym = y.mantissa_;
auto ye = y.exponent_;
Guard g; Guard g;
if (xe < ye) if (xe < ye)
{ {
@@ -549,14 +649,13 @@ Number::operator+=(Number const& y)
} while (xe > ye); } while (xe > ye);
} }
auto const& range = range_.get();
auto const& minMantissa = range.min; auto const& minMantissa = range.min;
auto const& maxMantissa = range.max; auto const& maxMantissa = range.max;
if (xn == yn) if (xn == yn)
{ {
xm += ym; xm += ym;
if (xm > maxMantissa || xm > maxRep) if (xm > maxMantissa)
{ {
g.push(xm % 10); g.push(xm % 10);
xm /= 10; xm /= 10;
@@ -576,7 +675,7 @@ Number::operator+=(Number const& y)
xe = ye; xe = ye;
xn = yn; xn = yn;
} }
while (xm < minMantissa && xm * 10 <= maxRep) while (xm < minMantissa)
{ {
xm *= 10; xm *= 10;
xm -= g.pop(); xm -= g.pop();
@@ -585,10 +684,8 @@ Number::operator+=(Number const& y)
g.doRoundDown(xn, xm, xe, minMantissa); g.doRoundDown(xn, xm, xe, minMantissa);
} }
negative_ = xn; normalize(xn, xm, xe, minMantissa, maxMantissa);
mantissa_ = static_cast<internalrep>(xm); fromInternal(xn, xm, xe, &range);
exponent_ = xe;
normalize();
return *this; return *this;
} }
@@ -623,6 +720,8 @@ divu10(uint128_t& u)
Number& Number&
Number::operator*=(Number const& y) Number::operator*=(Number const& y)
{ {
auto const& range = range_.get();
constexpr Number zero = Number{}; constexpr Number zero = Number{};
if (*this == zero) if (*this == zero)
return *this; return *this;
@@ -636,15 +735,11 @@ Number::operator*=(Number const& y)
// *m = mantissa // *m = mantissa
// *e = exponent // *e = exponent
bool xn = negative_; auto [xn, xm, xe] = toInternal(range);
int xs = xn ? -1 : 1; int xs = xn ? -1 : 1;
internalrep xm = mantissa_;
auto xe = exponent_;
bool yn = y.negative_; auto [yn, ym, ye] = y.toInternal(range);
int ys = yn ? -1 : 1; int ys = yn ? -1 : 1;
internalrep ym = y.mantissa_;
auto ye = y.exponent_;
auto zm = uint128_t(xm) * uint128_t(ym); auto zm = uint128_t(xm) * uint128_t(ym);
auto ze = xe + ye; auto ze = xe + ye;
@@ -654,11 +749,10 @@ Number::operator*=(Number const& y)
if (zn) if (zn)
g.set_negative(); g.set_negative();
auto const& range = range_.get();
auto const& minMantissa = range.min; auto const& minMantissa = range.min;
auto const& maxMantissa = range.max; auto const& maxMantissa = range.max;
while (zm > maxMantissa || zm > maxRep) while (zm > maxMantissa)
{ {
// The following is optimization for: // The following is optimization for:
// g.push(static_cast<unsigned>(zm % 10)); // g.push(static_cast<unsigned>(zm % 10));
@@ -670,17 +764,17 @@ Number::operator*=(Number const& y)
xe = ze; xe = ze;
g.doRoundUp( g.doRoundUp(
zn, xm, xe, minMantissa, maxMantissa, "Number::multiplication overflow : exponent is " + std::to_string(xe)); zn, xm, xe, minMantissa, maxMantissa, "Number::multiplication overflow : exponent is " + std::to_string(xe));
negative_ = zn;
mantissa_ = xm;
exponent_ = xe;
normalize(); normalize(zn, xm, xe, minMantissa, maxMantissa);
fromInternal(zn, xm, xe, &range);
return *this; return *this;
} }
Number& Number&
Number::operator/=(Number const& y) Number::operator/=(Number const& y)
{ {
auto const& range = range_.get();
constexpr Number zero = Number{}; constexpr Number zero = Number{};
if (y == zero) if (y == zero)
throw std::overflow_error("Number: divide by 0"); throw std::overflow_error("Number: divide by 0");
@@ -693,17 +787,12 @@ Number::operator/=(Number const& y)
// *m = mantissa // *m = mantissa
// *e = exponent // *e = exponent
bool np = negative_; auto [np, nm, ne] = toInternal(range);
int ns = (np ? -1 : 1); int ns = (np ? -1 : 1);
auto nm = mantissa_;
auto ne = exponent_;
bool dp = y.negative_; auto [dp, dm, de] = y.toInternal(range);
int ds = (dp ? -1 : 1); int ds = (dp ? -1 : 1);
auto dm = y.mantissa_;
auto de = y.exponent_;
auto const& range = range_.get();
auto const& minMantissa = range.min; auto const& minMantissa = range.min;
auto const& maxMantissa = range.max; auto const& maxMantissa = range.max;
@@ -715,7 +804,7 @@ Number::operator/=(Number const& y)
// f can be up to 10^(38-19) = 10^19 safely // f can be up to 10^(38-19) = 10^19 safely
static_assert(smallRange.log == 15); static_assert(smallRange.log == 15);
static_assert(largeRange.log == 18); static_assert(largeRange.log == 18);
bool small = Number::getMantissaScale() == MantissaRange::small; bool small = range.scale == MantissaRange::small;
uint128_t const f = small ? 100'000'000'000'000'000 : 10'000'000'000'000'000'000ULL; uint128_t const f = small ? 100'000'000'000'000'000 : 10'000'000'000'000'000'000ULL;
XRPL_ASSERT_PARTS(f >= minMantissa * 10, "Number::operator/=", "factor expected size"); XRPL_ASSERT_PARTS(f >= minMantissa * 10, "Number::operator/=", "factor expected size");
@@ -765,10 +854,8 @@ Number::operator/=(Number const& y)
} }
} }
normalize(zn, zm, ze, minMantissa, maxMantissa); normalize(zn, zm, ze, minMantissa, maxMantissa);
negative_ = zn; fromInternal(zn, zm, ze, &range);
mantissa_ = static_cast<internalrep>(zm); XRPL_ASSERT_PARTS(isnormal(range), "xrpl::Number::operator/=", "result is normalized");
exponent_ = ze;
XRPL_ASSERT_PARTS(isnormal(), "xrpl::Number::operator/=", "result is normalized");
return *this; return *this;
} }
@@ -780,10 +867,10 @@ Number::operator rep() const
Guard g; Guard g;
if (drops != 0) if (drops != 0)
{ {
if (negative_) if (drops < 0)
{ {
g.set_negative(); g.set_negative();
drops = -drops; drops = externalToInternal(drops);
} }
for (; offset < 0; ++offset) for (; offset < 0; ++offset)
{ {
@@ -792,7 +879,7 @@ Number::operator rep() const
} }
for (; offset > 0; --offset) for (; offset > 0; --offset)
{ {
if (drops > maxRep / 10) if (drops >= largeRange.min)
throw std::overflow_error("Number::operator rep() overflow"); throw std::overflow_error("Number::operator rep() overflow");
drops *= 10; drops *= 10;
} }
@@ -822,19 +909,21 @@ Number::truncate() const noexcept
std::string std::string
to_string(Number const& amount) to_string(Number const& amount)
{ {
auto const& range = Number::range_.get();
// keep full internal accuracy, but make more human friendly if possible // keep full internal accuracy, but make more human friendly if possible
constexpr Number zero = Number{}; constexpr Number zero = Number{};
if (amount == zero) if (amount == zero)
return "0"; return "0";
auto exponent = amount.exponent_; // The mantissa must have a set number of decimal places for this to work
auto mantissa = amount.mantissa_; auto [negative, mantissa, exponent] = amount.toInternal(range);
bool const negative = amount.negative_;
// Use scientific notation for exponents that are too small or too large // Use scientific notation for exponents that are too small or too large
auto const rangeLog = Number::mantissaLog(); auto const rangeLog = range.log;
if (((exponent != 0) && ((exponent < -(rangeLog + 10)) || (exponent > -(rangeLog - 10))))) if (((exponent != 0 && amount.exponent() != 0) && ((exponent < -(rangeLog + 10)) || (exponent > -(rangeLog - 10)))))
{ {
// Remove trailing zeroes from the mantissa.
while (mantissa != 0 && mantissa % 10 == 0 && exponent < Number::maxExponent) while (mantissa != 0 && mantissa % 10 == 0 && exponent < Number::maxExponent)
{ {
mantissa /= 10; mantissa /= 10;
@@ -842,8 +931,11 @@ to_string(Number const& amount)
} }
std::string ret = negative ? "-" : ""; std::string ret = negative ? "-" : "";
ret.append(std::to_string(mantissa)); ret.append(std::to_string(mantissa));
ret.append(1, 'e'); if (exponent != 0)
ret.append(std::to_string(exponent)); {
ret.append(1, 'e');
ret.append(std::to_string(exponent));
}
return ret; return ret;
} }
@@ -925,20 +1017,11 @@ power(Number const& f, unsigned n)
return r; 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(Number f, unsigned d) Number::root(MantissaRange const& range, Number f, unsigned d)
{ {
constexpr Number zero = Number{}; constexpr Number zero = Number{};
auto const one = Number::one(); auto const one = Number::one(range);
if (f == one || d == 1) if (f == one || d == 1)
return f; return f;
@@ -955,21 +1038,28 @@ root(Number f, unsigned d)
if (f == zero) if (f == zero)
return f; return f;
// Scale f into the range (0, 1) such that f's exponent is a multiple of d auto const [e, di] = [&]() {
auto e = f.exponent_ + Number::mantissaLog() + 1; auto const [negative, mantissa, exponent] = f.toInternal(range);
auto const di = static_cast<int>(d);
auto ex = [e = e, di = di]() // Euclidean remainder of e/d
{
int k = (e >= 0 ? e : e - (di - 1)) / di;
int k2 = e - k * di;
if (k2 == 0)
return 0;
return di - k2;
}();
e += ex;
f = f.shiftExponent(-e); // f /= 10^e;
XRPL_ASSERT_PARTS(f.isnormal(), "xrpl::root(Number, unsigned)", "f is normalized"); // 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 k = (e >= 0 ? e : e - (di - 1)) / di;
int 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);
}();
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");
bool neg = false; bool neg = false;
if (f < zero) if (f < zero)
{ {
@@ -1002,15 +1092,32 @@ root(Number f, unsigned d)
// return r * 10^(e/d) to reverse scaling // return r * 10^(e/d) to reverse scaling
auto const result = r.shiftExponent(e / di); auto const result = r.shiftExponent(e / di);
XRPL_ASSERT_PARTS(result.isnormal(), "xrpl::root(Number, unsigned)", "result is normalized"); XRPL_ASSERT_PARTS(result.isnormal(range), "xrpl::root(Number, unsigned)", "result is normalized");
return result; 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::range_.get();
return Number::root(range, f, d);
}
Number Number
root2(Number f) root2(Number f)
{ {
auto const& range = Number::range_.get();
constexpr Number zero = Number{}; constexpr Number zero = Number{};
auto const one = Number::one(); auto const one = Number::one(range);
if (f == one) if (f == one)
return f; return f;
@@ -1019,12 +1126,18 @@ root2(Number f)
if (f == zero) if (f == zero)
return f; return f;
// Scale f into the range (0, 1) such that f's exponent is a multiple of d auto const e = [&]() {
auto e = f.exponent_ + Number::mantissaLog() + 1; auto const [negative, mantissa, exponent] = f.toInternal(range);
if (e % 2 != 0)
++e; // Scale f into the range (0, 1) such that f's exponent is a
f = f.shiftExponent(-e); // f /= 10^e; // multiple of d
XRPL_ASSERT_PARTS(f.isnormal(), "xrpl::root2(Number)", "f is normalized"); 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");
// Quadratic least squares curve fit of f^(1/d) in the range [0, 1] // Quadratic least squares curve fit of f^(1/d) in the range [0, 1]
auto const D = 105; auto const D = 105;
@@ -1046,7 +1159,7 @@ root2(Number f)
// return r * 10^(e/2) to reverse scaling // return r * 10^(e/2) to reverse scaling
auto const result = r.shiftExponent(e / 2); auto const result = r.shiftExponent(e / 2);
XRPL_ASSERT_PARTS(result.isnormal(), "xrpl::root2(Number)", "result is normalized"); XRPL_ASSERT_PARTS(result.isnormal(range), "xrpl::root2(Number)", "result is normalized");
return result; return result;
} }
@@ -1056,8 +1169,10 @@ root2(Number f)
Number Number
power(Number const& f, unsigned n, unsigned d) power(Number const& f, unsigned n, unsigned d)
{ {
auto const& range = Number::range_.get();
constexpr Number zero = Number{}; constexpr Number zero = Number{};
auto const one = Number::one(); auto const one = Number::one(range);
if (f == one) if (f == one)
return f; return f;
@@ -1079,7 +1194,7 @@ power(Number const& f, unsigned n, unsigned d)
d /= g; d /= g;
if ((n % 2) == 1 && (d % 2) == 0 && f < zero) if ((n % 2) == 1 && (d % 2) == 0 && f < zero)
throw std::overflow_error("Number::power nan"); throw std::overflow_error("Number::power nan");
return root(power(f, n), d); return Number::root(range, power(f, n), d);
} }
} // namespace xrpl } // namespace xrpl

View File

@@ -32,9 +32,10 @@ public:
test_limits() test_limits()
{ {
auto const scale = Number::getMantissaScale(); auto const scale = Number::getMantissaScale();
testcase << "test_limits " << to_string(scale);
bool caught = false;
auto const minMantissa = Number::minMantissa(); auto const minMantissa = Number::minMantissa();
testcase << "test_limits " << to_string(scale) << ", " << minMantissa;
bool caught = false;
try try
{ {
Number x = Number{false, minMantissa * 10, 32768, Number::normalized{}}; Number x = Number{false, minMantissa * 10, 32768, Number::normalized{}};
@@ -58,8 +59,9 @@ public:
__LINE__); __LINE__);
test(Number{false, minMantissa, -32769, Number::normalized{}}, Number{}, __LINE__); test(Number{false, minMantissa, -32769, Number::normalized{}}, Number{}, __LINE__);
test( test(
// Use 1501 to force rounding up
Number{false, minMantissa, 32000, Number::normalized{}} * 1'000 + Number{false, minMantissa, 32000, Number::normalized{}} * 1'000 +
Number{false, 1'500, 32000, Number::normalized{}}, Number{false, 1'501, 32000, Number::normalized{}},
Number{false, minMantissa + 2, 32003, Number::normalized{}}, Number{false, minMantissa + 2, 32003, Number::normalized{}},
__LINE__); __LINE__);
// 9,223,372,036,854,775,808 // 9,223,372,036,854,775,808
@@ -159,8 +161,8 @@ public:
{Number{true, 9'999'999'999'999'999'999ULL, -37, Number::normalized{}}, {Number{true, 9'999'999'999'999'999'999ULL, -37, Number::normalized{}},
Number{1'000'000'000'000'000'000, -18}, Number{1'000'000'000'000'000'000, -18},
Number{false, 9'999'999'999'999'999'990ULL, -19, Number::normalized{}}}, Number{false, 9'999'999'999'999'999'990ULL, -19, Number::normalized{}}},
{Number{Number::maxRep}, Number{6, -1}, Number{Number::maxRep / 10, 1}}, {Number{Number::largestMantissa}, Number{6, -1}, Number{Number::largestMantissa / 10, 1}},
{Number{Number::maxRep - 1}, Number{1, 0}, Number{Number::maxRep}}, {Number{Number::largestMantissa - 1}, Number{1, 0}, Number{Number::largestMantissa}},
// Test extremes // Test extremes
{ {
// Each Number operand rounds up, so the actual mantissa is // Each Number operand rounds up, so the actual mantissa is
@@ -170,11 +172,18 @@ public:
Number{2, 19}, Number{2, 19},
}, },
{ {
// Does not round. Mantissas are going to be > maxRep, so if // Does not round. Mantissas are going to be >
// added together as uint64_t's, the result will overflow. // largestMantissa, so if added together as uint64_t's, the
// With addition using uint128_t, there's no problem. After // result will overflow. With addition using uint128_t,
// normalizing, the resulting mantissa ends up less than // there's no problem. After normalizing, the resulting
// maxRep. // mantissa ends up less than largestMantissa.
Number{false, Number::largestMantissa, 0, Number::normalized{}},
Number{false, Number::largestMantissa, 0, Number::normalized{}},
Number{false, Number::largestMantissa * 2, 0, Number::normalized{}},
},
{
// These mantissas round down, so adding them together won't
// have any consequences.
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, 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{}}, Number{false, 1'999'999'999'999'999'998ULL, 1, Number::normalized{}},
@@ -261,12 +270,14 @@ public:
{Number{1'000'000'000'000'000'001, -18}, {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, -18},
Number{1'000'000'000'000'000'000, -36}}, Number{1'000'000'000'000'000'000, -36}},
{Number{Number::maxRep}, Number{6, -1}, Number{Number::maxRep - 1}}, {Number{Number::largestMantissa}, Number{6, -1}, Number{Number::largestMantissa - 1}},
{Number{false, Number::maxRep + 1, 0, Number::normalized{}}, {Number{false, Number::largestMantissa + 1, 0, Number::normalized{}},
Number{1, 0}, Number{1, 0},
Number{Number::maxRep / 10 + 1, 1}}, Number{Number::largestMantissa / 10 + 1, 1}},
{Number{false, Number::maxRep + 1, 0, Number::normalized{}}, Number{3, 0}, Number{Number::maxRep}}, {Number{false, Number::largestMantissa + 1, 0, Number::normalized{}},
{power(2, 63), Number{3, 0}, Number{Number::maxRep}}, Number{3, 0},
Number{Number::largestMantissa}},
{power(2, 63), Number{3, 0}, Number{Number::largestMantissa}},
}); });
auto test = [this](auto const& c) { auto test = [this](auto const& c) {
for (auto const& [x, y, z] : c) for (auto const& [x, y, z] : c)
@@ -289,14 +300,15 @@ public:
auto const scale = Number::getMantissaScale(); auto const scale = Number::getMantissaScale();
testcase << "test_mul " << to_string(scale); testcase << "test_mul " << to_string(scale);
using Case = std::tuple<Number, Number, Number>; // Case: Factor 1, Factor 2, Expected product, Line number
using Case = std::tuple<Number, Number, Number, int>;
auto test = [this](auto const& c) { auto test = [this](auto const& c) {
for (auto const& [x, y, z] : c) for (auto const& [x, y, z, line] : c)
{ {
auto const result = x * y; auto const result = x * y;
std::stringstream ss; std::stringstream ss;
ss << x << " * " << y << " = " << result << ". Expected: " << z; ss << x << " * " << y << " = " << result << ". Expected: " << z;
BEAST_EXPECTS(result == z, ss.str()); BEAST_EXPECTS(result == z, ss.str() + " line: " + std::to_string(line));
} }
}; };
auto tests = [&](auto const& cSmall, auto const& cLarge) { auto tests = [&](auto const& cSmall, auto const& cLarge) {
@@ -306,48 +318,83 @@ public:
test(cLarge); test(cLarge);
}; };
auto const maxMantissa = Number::maxMantissa(); auto const maxMantissa = Number::maxMantissa();
auto const maxInternalMantissa =
static_cast<std::uint64_t>(static_cast<std::int64_t>(power(10, Number::mantissaLog()))) * 10 - 1;
saveNumberRoundMode save{Number::setround(Number::to_nearest)}; saveNumberRoundMode save{Number::setround(Number::to_nearest)};
{ {
auto const cSmall = std::to_array<Case>({ auto const cSmall = std::to_array<Case>({
{Number{7}, Number{8}, Number{56}}, {Number{7}, Number{8}, Number{56}, __LINE__},
{Number{1414213562373095, -15}, Number{1414213562373095, -15}, Number{2000000000000000, -15}}, {Number{1414213562373095, -15}, Number{1414213562373095, -15}, Number{2000000000000000, -15}, __LINE__},
{Number{-1414213562373095, -15}, Number{1414213562373095, -15}, Number{-2000000000000000, -15}}, {Number{-1414213562373095, -15},
{Number{-1414213562373095, -15}, Number{-1414213562373095, -15}, Number{2000000000000000, -15}}, Number{1414213562373095, -15},
{Number{3214285714285706, -15}, Number{3111111111111119, -15}, Number{1000000000000000, -14}}, Number{-2000000000000000, -15},
{Number{1000000000000000, -32768}, Number{1000000000000000, -32768}, Number{0}}, __LINE__},
{Number{-1414213562373095, -15},
Number{-1414213562373095, -15},
Number{2000000000000000, -15},
__LINE__},
{Number{3214285714285706, -15}, Number{3111111111111119, -15}, Number{1000000000000000, -14}, __LINE__},
{Number{1000000000000000, -32768}, Number{1000000000000000, -32768}, Number{0}, __LINE__},
// Maximum mantissa range // 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}}, {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__},
}); });
auto const cLarge = std::to_array<Case>({ auto const cLarge = std::to_array<Case>({
// Note that items with extremely large mantissas need to be // Note that items with extremely large mantissas need to be
// calculated, because otherwise they overflow uint64. Items // calculated, because otherwise they overflow uint64. Items
// from C with larger mantissa // from C with larger mantissa
{Number{7}, Number{8}, Number{56}}, {Number{7}, Number{8}, Number{56}, __LINE__},
{Number{1414213562373095, -15}, Number{1414213562373095, -15}, Number{1999999999999999862, -18}}, {Number{1414213562373095, -15},
{Number{-1414213562373095, -15}, Number{1414213562373095, -15}, Number{-1999999999999999862, -18}}, Number{1414213562373095, -15},
{Number{-1414213562373095, -15}, Number{-1414213562373095, -15}, Number{1999999999999999862, -18}}, Number{1999999999999999862, -18},
__LINE__},
{Number{-1414213562373095, -15},
Number{1414213562373095, -15},
Number{-1999999999999999862, -18},
__LINE__},
{Number{-1414213562373095, -15},
Number{-1414213562373095, -15},
Number{1999999999999999862, -18},
__LINE__},
{Number{3214285714285706, -15}, {Number{3214285714285706, -15},
Number{3111111111111119, -15}, Number{3111111111111119, -15},
Number{false, 9'999'999'999'999'999'579ULL, -18, Number::normalized{}}}, Number{false, 9'999'999'999'999'999'579ULL, -18, Number::normalized{}},
{Number{1000000000000000000, -32768}, Number{1000000000000000000, -32768}, Number{0}}, __LINE__},
{Number{1000000000000000000, -32768}, Number{1000000000000000000, -32768}, Number{0}, __LINE__},
// Items from cSmall expanded for the larger mantissa, // Items from cSmall expanded for the larger mantissa,
// except duplicates. Sadly, it looks like sqrt(2)^2 != 2 // except duplicates. Sadly, it looks like sqrt(2)^2 != 2
// with higher precision // with higher precision
{Number{1414213562373095049, -18}, Number{1414213562373095049, -18}, Number{2000000000000000001, -18}}, {Number{1414213562373095049, -18},
Number{1414213562373095049, -18},
Number{2000000000000000001, -18},
__LINE__},
{Number{-1414213562373095048, -18}, {Number{-1414213562373095048, -18},
Number{1414213562373095048, -18}, Number{1414213562373095048, -18},
Number{-1999999999999999998, -18}}, Number{-1999999999999999998, -18},
__LINE__},
{Number{-1414213562373095048, -18}, {Number{-1414213562373095048, -18},
Number{-1414213562373095049, -18}, Number{-1414213562373095049, -18},
Number{1999999999999999999, -18}}, Number{1999999999999999999, -18},
{Number{3214285714285714278, -18}, Number{3111111111111111119, -18}, Number{10, 0}}, __LINE__},
// Maximum mantissa range - rounds up to 1e19 {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{false, maxMantissa, 0, Number::normalized{}}, {Number{false, maxMantissa, 0, Number::normalized{}},
Number{false, maxMantissa, 0, Number::normalized{}}, Number{false, maxMantissa, 0, Number::normalized{}},
Number{1, 38}}, Number{85'070'591'730'234'615'85, 19},
__LINE__},
// Maximum int64 range // Maximum int64 range
{Number{Number::maxRep, 0}, Number{Number::maxRep, 0}, Number{85'070'591'730'234'615'85, 19}}, {Number{Number::largestMantissa, 0},
Number{Number::largestMantissa, 0},
Number{85'070'591'730'234'615'85, 19},
__LINE__},
}); });
tests(cSmall, cLarge); tests(cSmall, cLarge);
} }
@@ -355,44 +402,78 @@ public:
testcase << "test_mul " << to_string(Number::getMantissaScale()) << " towards_zero"; testcase << "test_mul " << to_string(Number::getMantissaScale()) << " towards_zero";
{ {
auto const cSmall = std::to_array<Case>( auto const cSmall = std::to_array<Case>(
{{Number{7}, Number{8}, Number{56}}, {{Number{7}, Number{8}, Number{56}, __LINE__},
{Number{1414213562373095, -15}, Number{1414213562373095, -15}, Number{1999999999999999, -15}}, {Number{1414213562373095, -15},
{Number{-1414213562373095, -15}, Number{1414213562373095, -15}, Number{-1999999999999999, -15}}, Number{1414213562373095, -15},
{Number{-1414213562373095, -15}, Number{-1414213562373095, -15}, Number{1999999999999999, -15}}, Number{1999999999999999, -15},
{Number{3214285714285706, -15}, Number{3111111111111119, -15}, Number{9999999999999999, -15}}, __LINE__},
{Number{1000000000000000, -32768}, Number{1000000000000000, -32768}, Number{0}}}); {Number{-1414213562373095, -15},
Number{1414213562373095, -15},
Number{-1999999999999999, -15},
__LINE__},
{Number{-1414213562373095, -15},
Number{-1414213562373095, -15},
Number{1999999999999999, -15},
__LINE__},
{Number{3214285714285706, -15},
Number{3111111111111119, -15},
Number{9999999999999999, -15},
__LINE__},
{Number{1000000000000000, -32768}, Number{1000000000000000, -32768}, Number{0}, __LINE__}});
auto const cLarge = std::to_array<Case>( auto const cLarge = std::to_array<Case>(
// Note that items with extremely large mantissas need to be // Note that items with extremely large mantissas need to be
// calculated, because otherwise they overflow uint64. Items // calculated, because otherwise they overflow uint64. Items
// from C with larger mantissa // from C with larger mantissa
{ {
{Number{7}, Number{8}, Number{56}}, {Number{7}, Number{8}, Number{56}, __LINE__},
{Number{1414213562373095, -15}, Number{1414213562373095, -15}, Number{1999999999999999861, -18}}, {Number{1414213562373095, -15},
{Number{-1414213562373095, -15}, Number{1414213562373095, -15}, Number{-1999999999999999861, -18}}, Number{1414213562373095, -15},
{Number{-1414213562373095, -15}, Number{-1414213562373095, -15}, Number{1999999999999999861, -18}}, Number{1999999999999999861, -18},
__LINE__},
{Number{-1414213562373095, -15},
Number{1414213562373095, -15},
Number{-1999999999999999861, -18},
__LINE__},
{Number{-1414213562373095, -15},
Number{-1414213562373095, -15},
Number{1999999999999999861, -18},
__LINE__},
{Number{3214285714285706, -15}, {Number{3214285714285706, -15},
Number{3111111111111119, -15}, Number{3111111111111119, -15},
Number{false, 9999999999999999579ULL, -18, Number::normalized{}}}, Number{false, 9999999999999999579ULL, -18, Number::normalized{}},
{Number{1000000000000000000, -32768}, Number{1000000000000000000, -32768}, Number{0}}, __LINE__},
{Number{1000000000000000000, -32768}, Number{1000000000000000000, -32768}, Number{0}, __LINE__},
// Items from cSmall expanded for the larger mantissa, // Items from cSmall expanded for the larger mantissa,
// except duplicates. Sadly, it looks like sqrt(2)^2 != 2 // except duplicates. Sadly, it looks like sqrt(2)^2 != 2
// with higher precision // with higher precision
{Number{1414213562373095049, -18}, Number{1414213562373095049, -18}, Number{2, 0}}, {Number{1414213562373095049, -18}, Number{1414213562373095049, -18}, Number{2, 0}, __LINE__},
{Number{-1414213562373095048, -18}, {Number{-1414213562373095048, -18},
Number{1414213562373095048, -18}, Number{1414213562373095048, -18},
Number{-1999999999999999997, -18}}, Number{-1999999999999999997, -18},
__LINE__},
{Number{-1414213562373095048, -18}, {Number{-1414213562373095048, -18},
Number{-1414213562373095049, -18}, Number{-1414213562373095049, -18},
Number{1999999999999999999, -18}}, Number{1999999999999999999, -18},
{Number{3214285714285714278, -18}, Number{3111111111111111119, -18}, Number{10, 0}}, __LINE__},
// Maximum mantissa range - rounds down to maxMantissa/10e1 {Number{3214285714285714278, -18}, Number{3111111111111111119, -18}, Number{10, 0}, __LINE__},
// Maximum internal mantissa range - rounds down to
// maxMantissa/10e1
// 99'999'999'999'999'999'800'000'000'000'000'000'100 // 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{false, maxMantissa, 0, Number::normalized{}}, Number{false, maxMantissa, 0, Number::normalized{}},
Number{false, maxMantissa / 10 - 1, 20, Number::normalized{}}}, Number{85'070'591'730'234'615'84, 19},
__LINE__},
// Maximum int64 range // Maximum int64 range
// 85'070'591'730'234'615'847'396'907'784'232'501'249 // 85'070'591'730'234'615'847'396'907'784'232'501'249
{Number{Number::maxRep, 0}, Number{Number::maxRep, 0}, Number{85'070'591'730'234'615'84, 19}}, {Number{Number::largestMantissa, 0},
Number{Number::largestMantissa, 0},
Number{85'070'591'730'234'615'84, 19},
__LINE__},
}); });
tests(cSmall, cLarge); tests(cSmall, cLarge);
} }
@@ -400,44 +481,78 @@ public:
testcase << "test_mul " << to_string(Number::getMantissaScale()) << " downward"; testcase << "test_mul " << to_string(Number::getMantissaScale()) << " downward";
{ {
auto const cSmall = std::to_array<Case>( auto const cSmall = std::to_array<Case>(
{{Number{7}, Number{8}, Number{56}}, {{Number{7}, Number{8}, Number{56}, __LINE__},
{Number{1414213562373095, -15}, Number{1414213562373095, -15}, Number{1999999999999999, -15}}, {Number{1414213562373095, -15},
{Number{-1414213562373095, -15}, Number{1414213562373095, -15}, Number{-2000000000000000, -15}}, Number{1414213562373095, -15},
{Number{-1414213562373095, -15}, Number{-1414213562373095, -15}, Number{1999999999999999, -15}}, Number{1999999999999999, -15},
{Number{3214285714285706, -15}, Number{3111111111111119, -15}, Number{9999999999999999, -15}}, __LINE__},
{Number{1000000000000000, -32768}, Number{1000000000000000, -32768}, Number{0}}}); {Number{-1414213562373095, -15},
Number{1414213562373095, -15},
Number{-2000000000000000, -15},
__LINE__},
{Number{-1414213562373095, -15},
Number{-1414213562373095, -15},
Number{1999999999999999, -15},
__LINE__},
{Number{3214285714285706, -15},
Number{3111111111111119, -15},
Number{9999999999999999, -15},
__LINE__},
{Number{1000000000000000, -32768}, Number{1000000000000000, -32768}, Number{0}, __LINE__}});
auto const cLarge = std::to_array<Case>( auto const cLarge = std::to_array<Case>(
// Note that items with extremely large mantissas need to be // Note that items with extremely large mantissas need to be
// calculated, because otherwise they overflow uint64. Items // calculated, because otherwise they overflow uint64. Items
// from C with larger mantissa // from C with larger mantissa
{ {
{Number{7}, Number{8}, Number{56}}, {Number{7}, Number{8}, Number{56}, __LINE__},
{Number{1414213562373095, -15}, Number{1414213562373095, -15}, Number{1999999999999999861, -18}}, {Number{1414213562373095, -15},
{Number{-1414213562373095, -15}, Number{1414213562373095, -15}, Number{-1999999999999999862, -18}}, Number{1414213562373095, -15},
{Number{-1414213562373095, -15}, Number{-1414213562373095, -15}, Number{1999999999999999861, -18}}, Number{1999999999999999861, -18},
__LINE__},
{Number{-1414213562373095, -15},
Number{1414213562373095, -15},
Number{-1999999999999999862, -18},
__LINE__},
{Number{-1414213562373095, -15},
Number{-1414213562373095, -15},
Number{1999999999999999861, -18},
__LINE__},
{Number{3214285714285706, -15}, {Number{3214285714285706, -15},
Number{3111111111111119, -15}, Number{3111111111111119, -15},
Number{false, 9'999'999'999'999'999'579ULL, -18, Number::normalized{}}}, Number{false, 9'999'999'999'999'999'579ULL, -18, Number::normalized{}},
{Number{1000000000000000000, -32768}, Number{1000000000000000000, -32768}, Number{0}}, __LINE__},
{Number{1000000000000000000, -32768}, Number{1000000000000000000, -32768}, Number{0}, __LINE__},
// Items from cSmall expanded for the larger mantissa, // Items from cSmall expanded for the larger mantissa,
// except duplicates. Sadly, it looks like sqrt(2)^2 != 2 // except duplicates. Sadly, it looks like sqrt(2)^2 != 2
// with higher precision // with higher precision
{Number{1414213562373095049, -18}, Number{1414213562373095049, -18}, Number{2, 0}}, {Number{1414213562373095049, -18}, Number{1414213562373095049, -18}, Number{2, 0}, __LINE__},
{Number{-1414213562373095048, -18}, {Number{-1414213562373095048, -18},
Number{1414213562373095048, -18}, Number{1414213562373095048, -18},
Number{-1999999999999999998, -18}}, Number{-1999999999999999998, -18},
__LINE__},
{Number{-1414213562373095048, -18}, {Number{-1414213562373095048, -18},
Number{-1414213562373095049, -18}, Number{-1414213562373095049, -18},
Number{1999999999999999999, -18}}, Number{1999999999999999999, -18},
{Number{3214285714285714278, -18}, Number{3111111111111111119, -18}, Number{10, 0}}, __LINE__},
// Maximum mantissa range - rounds down to maxMantissa/10e1 {Number{3214285714285714278, -18}, Number{3111111111111111119, -18}, Number{10, 0}, __LINE__},
// Maximum internal mantissa range - rounds down to
// maxMantissa/10-1
// 99'999'999'999'999'999'800'000'000'000'000'000'100 // 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 mantissa range - same as int64
{Number{false, maxMantissa, 0, Number::normalized{}}, {Number{false, maxMantissa, 0, Number::normalized{}},
Number{false, maxMantissa, 0, Number::normalized{}}, Number{false, maxMantissa, 0, Number::normalized{}},
Number{false, maxMantissa / 10 - 1, 20, Number::normalized{}}}, Number{85'070'591'730'234'615'84, 19},
__LINE__},
// Maximum int64 range // Maximum int64 range
// 85'070'591'730'234'615'847'396'907'784'232'501'249 // 85'070'591'730'234'615'847'396'907'784'232'501'249
{Number{Number::maxRep, 0}, Number{Number::maxRep, 0}, Number{85'070'591'730'234'615'84, 19}}, {Number{Number::largestMantissa, 0},
Number{Number::largestMantissa, 0},
Number{85'070'591'730'234'615'84, 19},
__LINE__},
}); });
tests(cSmall, cLarge); tests(cSmall, cLarge);
} }
@@ -445,44 +560,80 @@ public:
testcase << "test_mul " << to_string(Number::getMantissaScale()) << " upward"; testcase << "test_mul " << to_string(Number::getMantissaScale()) << " upward";
{ {
auto const cSmall = std::to_array<Case>( auto const cSmall = std::to_array<Case>(
{{Number{7}, Number{8}, Number{56}}, {{Number{7}, Number{8}, Number{56}, __LINE__},
{Number{1414213562373095, -15}, Number{1414213562373095, -15}, Number{2000000000000000, -15}}, {Number{1414213562373095, -15},
{Number{-1414213562373095, -15}, Number{1414213562373095, -15}, Number{-1999999999999999, -15}}, Number{1414213562373095, -15},
{Number{-1414213562373095, -15}, Number{-1414213562373095, -15}, Number{2000000000000000, -15}}, Number{2000000000000000, -15},
{Number{3214285714285706, -15}, Number{3111111111111119, -15}, Number{1000000000000000, -14}}, __LINE__},
{Number{1000000000000000, -32768}, Number{1000000000000000, -32768}, Number{0}}}); {Number{-1414213562373095, -15},
Number{1414213562373095, -15},
Number{-1999999999999999, -15},
__LINE__},
{Number{-1414213562373095, -15},
Number{-1414213562373095, -15},
Number{2000000000000000, -15},
__LINE__},
{Number{3214285714285706, -15},
Number{3111111111111119, -15},
Number{1000000000000000, -14},
__LINE__},
{Number{1000000000000000, -32768}, Number{1000000000000000, -32768}, Number{0}, __LINE__}});
auto const cLarge = std::to_array<Case>( auto const cLarge = std::to_array<Case>(
// Note that items with extremely large mantissas need to be // Note that items with extremely large mantissas need to be
// calculated, because otherwise they overflow uint64. Items // calculated, because otherwise they overflow uint64. Items
// from C with larger mantissa // from C with larger mantissa
{ {
{Number{7}, Number{8}, Number{56}}, {Number{7}, Number{8}, Number{56}, __LINE__},
{Number{1414213562373095, -15}, Number{1414213562373095, -15}, Number{1999999999999999862, -18}}, {Number{1414213562373095, -15},
{Number{-1414213562373095, -15}, Number{1414213562373095, -15}, Number{-1999999999999999861, -18}}, Number{1414213562373095, -15},
{Number{-1414213562373095, -15}, Number{-1414213562373095, -15}, Number{1999999999999999862, -18}}, Number{1999999999999999862, -18},
{Number{3214285714285706, -15}, Number{3111111111111119, -15}, Number{999999999999999958, -17}}, __LINE__},
{Number{1000000000000000000, -32768}, Number{1000000000000000000, -32768}, Number{0}}, {Number{-1414213562373095, -15},
Number{1414213562373095, -15},
Number{-1999999999999999861, -18},
__LINE__},
{Number{-1414213562373095, -15},
Number{-1414213562373095, -15},
Number{1999999999999999862, -18},
__LINE__},
{Number{3214285714285706, -15},
Number{3111111111111119, -15},
Number{999999999999999958, -17},
__LINE__},
{Number{1000000000000000000, -32768}, Number{1000000000000000000, -32768}, Number{0}, __LINE__},
// Items from cSmall expanded for the larger mantissa, // Items from cSmall expanded for the larger mantissa,
// except duplicates. Sadly, it looks like sqrt(2)^2 != 2 // except duplicates. Sadly, it looks like sqrt(2)^2 != 2
// with higher precision // with higher precision
{Number{1414213562373095049, -18}, {Number{1414213562373095049, -18},
Number{1414213562373095049, -18}, Number{1414213562373095049, -18},
Number{2000000000000000001, -18}}, Number{2000000000000000001, -18},
__LINE__},
{Number{-1414213562373095048, -18}, {Number{-1414213562373095048, -18},
Number{1414213562373095048, -18}, Number{1414213562373095048, -18},
Number{-1999999999999999997, -18}}, Number{-1999999999999999997, -18},
{Number{-1414213562373095048, -18}, Number{-1414213562373095049, -18}, Number{2, 0}}, __LINE__},
{Number{-1414213562373095048, -18}, Number{-1414213562373095049, -18}, Number{2, 0}, __LINE__},
{Number{3214285714285714278, -18}, {Number{3214285714285714278, -18},
Number{3111111111111111119, -18}, Number{3111111111111111119, -18},
Number{1000000000000000001, -17}}, Number{1000000000000000001, -17},
// Maximum mantissa range - rounds up to minMantissa*10 __LINE__},
// 1e19*1e19=1e38 // 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{false, maxMantissa, 0, Number::normalized{}}, {Number{false, maxMantissa, 0, Number::normalized{}},
Number{false, maxMantissa, 0, Number::normalized{}}, Number{false, maxMantissa, 0, Number::normalized{}},
Number{1, 38}}, Number{85'070'591'730'234'615'85, 19},
__LINE__},
// Maximum int64 range // Maximum int64 range
// 85'070'591'730'234'615'847'396'907'784'232'501'249 // 85'070'591'730'234'615'847'396'907'784'232'501'249
{Number{Number::maxRep, 0}, Number{Number::maxRep, 0}, Number{85'070'591'730'234'615'85, 19}}, {Number{Number::largestMantissa, 0},
Number{Number::largestMantissa, 0},
Number{85'070'591'730'234'615'85, 19},
__LINE__},
}); });
tests(cSmall, cLarge); tests(cSmall, cLarge);
} }
@@ -697,6 +848,9 @@ public:
}; };
*/ */
auto const maxInternalMantissa =
static_cast<std::uint64_t>(static_cast<std::int64_t>(power(10, Number::mantissaLog()))) * 10 - 1;
auto const cSmall = std::to_array<Case>( auto const cSmall = std::to_array<Case>(
{{Number{2}, 2, Number{1414213562373095049, -18}}, {{Number{2}, 2, Number{1414213562373095049, -18}},
{Number{2'000'000}, 2, Number{1414213562373095049, -15}}, {Number{2'000'000}, 2, Number{1414213562373095049, -15}},
@@ -708,14 +862,14 @@ public:
{Number{0}, 5, Number{0}}, {Number{0}, 5, Number{0}},
{Number{5625, -4}, 2, Number{75, -2}}}); {Number{5625, -4}, 2, Number{75, -2}}});
auto const cLarge = std::to_array<Case>({ auto const cLarge = std::to_array<Case>({
{Number{false, Number::maxMantissa() - 9, -1, Number::normalized{}}, {Number{false, maxInternalMantissa - 9, -1, Number::normalized{}},
2, 2,
Number{false, 999'999'999'999'999'999, -9, Number::normalized{}}}, Number{false, 999'999'999'999'999'999, -9, Number::normalized{}}},
{Number{false, Number::maxMantissa() - 9, 0, Number::normalized{}}, {Number{false, maxInternalMantissa - 9, 0, Number::normalized{}},
2, 2,
Number{false, 3'162'277'660'168'379'330, -9, Number::normalized{}}}, Number{false, 3'162'277'660'168'379'330, -9, Number::normalized{}}},
{Number{Number::maxRep}, 2, Number{false, 3'037'000'499'976049692, -9, Number::normalized{}}}, {Number{Number::largestMantissa}, 2, Number{false, 3'037'000'499'976049692, -9, Number::normalized{}}},
{Number{Number::maxRep}, 4, Number{false, 55'108'98747006743627, -14, Number::normalized{}}}, {Number{Number::largestMantissa}, 4, Number{false, 55'108'98747006743627, -14, Number::normalized{}}},
}); });
test(cSmall); test(cSmall);
if (Number::getMantissaScale() != MantissaRange::small) if (Number::getMantissaScale() != MantissaRange::small)
@@ -762,6 +916,8 @@ public:
} }
}; };
auto const maxInternalMantissa = power(10, Number::mantissaLog()) * 10 - 1;
auto const cSmall = std::to_array<Number>({ auto const cSmall = std::to_array<Number>({
Number{2}, Number{2},
Number{2'000'000}, Number{2'000'000},
@@ -771,7 +927,10 @@ public:
Number{5, -1}, Number{5, -1},
Number{0}, Number{0},
Number{5625, -4}, Number{5625, -4},
Number{Number::maxRep}, Number{Number::largestMantissa},
maxInternalMantissa,
Number{Number::minMantissa(), 0, Number::unchecked{}},
Number{Number::maxMantissa(), 0, Number::unchecked{}},
}); });
test(cSmall); test(cSmall);
bool caught = false; bool caught = false;
@@ -1113,16 +1272,16 @@ public:
case MantissaRange::large: case MantissaRange::large:
// Test the edges // Test the edges
// ((exponent < -(28)) || (exponent > -(8))))) // ((exponent < -(28)) || (exponent > -(8)))))
test(Number::min(), "1e-32750"); test(Number::min(), "922337203685477581e-32768");
test(Number::max(), "9223372036854775807e32768"); test(Number::max(), "9223372036854775807e32768");
test(Number::lowest(), "-9223372036854775807e32768"); test(Number::lowest(), "-9223372036854775807e32768");
{ {
NumberRoundModeGuard mg(Number::towards_zero); NumberRoundModeGuard mg(Number::towards_zero);
auto const maxMantissa = Number::maxMantissa(); auto const maxMantissa = Number::maxMantissa();
BEAST_EXPECT(maxMantissa == 9'999'999'999'999'999'999ULL); BEAST_EXPECT(maxMantissa == 9'223'372'036'854'775'807ULL);
test(Number{false, maxMantissa, 0, Number::normalized{}}, "9999999999999999990"); test(Number{false, maxMantissa, 0, Number::normalized{}}, "9223372036854775807");
test(Number{true, maxMantissa, 0, Number::normalized{}}, "-9999999999999999990"); test(Number{true, maxMantissa, 0, Number::normalized{}}, "-9223372036854775807");
test(Number{std::numeric_limits<std::int64_t>::max(), 0}, "9223372036854775807"); test(Number{std::numeric_limits<std::int64_t>::max(), 0}, "9223372036854775807");
test(-(Number{std::numeric_limits<std::int64_t>::max(), 0}), "-9223372036854775807"); test(-(Number{std::numeric_limits<std::int64_t>::max(), 0}), "-9223372036854775807");
@@ -1300,7 +1459,7 @@ public:
Number const initalXrp{INITIAL_XRP}; Number const initalXrp{INITIAL_XRP};
BEAST_EXPECT(initalXrp.exponent() > 0); BEAST_EXPECT(initalXrp.exponent() > 0);
Number const maxInt64{Number::maxRep}; Number const maxInt64{Number::largestMantissa};
BEAST_EXPECT(maxInt64.exponent() > 0); BEAST_EXPECT(maxInt64.exponent() > 0);
// 85'070'591'730'234'615'865'843'651'857'942'052'864 - 38 digits // 85'070'591'730'234'615'865'843'651'857'942'052'864 - 38 digits
BEAST_EXPECT((power(maxInt64, 2) == Number{85'070'591'730'234'62, 22})); BEAST_EXPECT((power(maxInt64, 2) == Number{85'070'591'730'234'62, 22}));
@@ -1317,20 +1476,198 @@ public:
Number const initalXrp{INITIAL_XRP}; Number const initalXrp{INITIAL_XRP};
BEAST_EXPECT(initalXrp.exponent() <= 0); BEAST_EXPECT(initalXrp.exponent() <= 0);
Number const maxInt64{Number::maxRep}; Number const maxInt64{Number::largestMantissa};
BEAST_EXPECT(maxInt64.exponent() <= 0); BEAST_EXPECT(maxInt64.exponent() <= 0);
// 85'070'591'730'234'615'847'396'907'784'232'501'249 - 38 digits // 85'070'591'730'234'615'847'396'907'784'232'501'249 - 38 digits
BEAST_EXPECT((power(maxInt64, 2) == Number{85'070'591'730'234'615'85, 19})); BEAST_EXPECT((power(maxInt64, 2) == Number{85'070'591'730'234'615'85, 19}));
NumberRoundModeGuard mg(Number::towards_zero); NumberRoundModeGuard mg(Number::towards_zero);
auto const maxMantissa = Number::maxMantissa(); {
Number const max = Number{false, maxMantissa, 0, Number::normalized{}}; auto const maxInternalMantissa =
BEAST_EXPECT(max.mantissa() == maxMantissa / 10); static_cast<std::uint64_t>(static_cast<std::int64_t>(power(10, Number::mantissaLog()))) * 10 - 1;
BEAST_EXPECT(max.exponent() == 1);
// 99'999'999'999'999'999'800'000'000'000'000'000'100 - also 38 // Rounds down to fit under 2^63
// digits Number const max = Number{false, maxInternalMantissa, 0, Number::normalized{}};
BEAST_EXPECT((power(max, 2) == Number{false, maxMantissa / 10 - 1, 20, Number::normalized{}})); // No alterations by the accessors
BEAST_EXPECT(max.mantissa() == maxInternalMantissa / 10);
BEAST_EXPECT(max.exponent() == 1);
// 99'999'999'999'999'999'800'000'000'000'000'000'100 - also 38
// digits
BEAST_EXPECT((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
BEAST_EXPECT(max.mantissa() == maxMantissa);
BEAST_EXPECT(max.exponent() == 0);
// 85'070'591'730'234'615'847'396'907'784'232'501'249 - also 38
// digits
BEAST_EXPECT((power(max, 2) == Number{false, 85'070'591'730'234'615'84, 19, Number::normalized{}}));
}
}
}
void
testNormalizeToRange()
{
// Test edge-cases of normalizeToRange
auto const scale = Number::getMantissaScale();
testcase << "normalizeToRange " << to_string(scale);
auto test = [this](
Number const& n,
auto const rangeMin,
auto const rangeMax,
auto const expectedMantissa,
auto const expectedExponent,
auto const line) {
auto const normalized = n.normalizeToRange(rangeMin, rangeMax);
BEAST_EXPECTS(
normalized.first == expectedMantissa,
"Number " + to_string(n) + " scaled to " + std::to_string(rangeMax) +
". Expected mantissa:" + std::to_string(expectedMantissa) +
", got: " + std::to_string(normalized.first) + " @ " + std::to_string(line));
BEAST_EXPECTS(
normalized.second == expectedExponent,
"Number " + to_string(n) + " scaled to " + std::to_string(rangeMax) +
". Expected exponent:" + std::to_string(expectedExponent) +
", got: " + std::to_string(normalized.second) + " @ " + std::to_string(line));
};
std::int64_t constexpr iRangeMin = 100;
std::int64_t constexpr iRangeMax = 999;
std::uint64_t constexpr uRangeMin = 100;
std::uint64_t constexpr uRangeMax = 999;
constexpr static MantissaRange largeRange{MantissaRange::large};
std::int64_t constexpr iBigMin = largeRange.min;
std::int64_t constexpr iBigMax = largeRange.max;
auto const testSuite = [&](Number const& n,
auto const expectedSmallMantissa,
auto const expectedSmallExponent,
auto const expectedLargeMantissa,
auto const expectedLargeExponent,
auto const line) {
test(n, iRangeMin, iRangeMax, expectedSmallMantissa, expectedSmallExponent, line);
test(n, iBigMin, iBigMax, 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, uRangeMin, uRangeMax, expectedSmallMantissa, expectedSmallExponent, line);
test(n, largeRange.min, largeRange.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::largestMantissa, 0, Number::normalized{}};
if (scale == MantissaRange::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::largestMantissa, 0, __LINE__);
}
{
// Biggest valid mantissa + 1
Number const n{Number::largestMantissa + 1, 0, Number::normalized{}};
if (scale == MantissaRange::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, 922'337'203'685'477'581, 1, __LINE__);
}
{
// Biggest valid mantissa + 2
Number const n{Number::largestMantissa + 2, 0, Number::normalized{}};
if (scale == MantissaRange::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, 922'337'203'685'477'581, 1, __LINE__);
}
{
// Biggest valid mantissa + 3
Number const n{Number::largestMantissa + 3, 0, Number::normalized{}};
if (scale == MantissaRange::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, 922'337'203'685'477'581, 1, __LINE__);
}
{
// int64 min
Number const n{std::numeric_limits<std::int64_t>::min(), 0};
if (scale == MantissaRange::small)
testSuite(n, -922, 16, -922'337'203'685'477'600, 1, __LINE__);
else
testSuite(n, -922, 16, -922'337'203'685'477'581, 1, __LINE__);
}
{
// int64 min + 1
Number const n{std::numeric_limits<std::int64_t>::min() + 1, 0};
if (scale == MantissaRange::small)
testSuite(n, -922, 16, -922'337'203'685'477'600, 1, __LINE__);
else
testSuite(n, -922, 16, -9'223'372'036'854'775'807, 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>::min()) + 1,
0,
Number::normalized{}};
if (scale == MantissaRange::small)
testSuite(n, -922, 16, -922'337'203'685'477'600, 1, __LINE__);
else
testSuite(n, -922, 16, -922'337'203'685'477'581, 1, __LINE__);
} }
} }
@@ -1361,6 +1698,7 @@ public:
test_truncate(); test_truncate();
testRounding(); testRounding();
testInt64(); testInt64();
testNormalizeToRange();
} }
} }
}; };