Compare commits

..

37 Commits

Author SHA1 Message Date
Ed Hennis
1c4c998ae1 Merge branch 'develop' into ximinez/fix/validator-cache 2026-07-09 19:50:05 -04:00
Ed Hennis
5d2faef541 Merge remote-tracking branch 'XRPLF/develop' into ximinez/fix/validator-cache
* XRPLF/develop: (158 commits)
  chore: Use std::ranges where possible (7634)
  ci: Use macOS 26 Tahoe with apple-clang 21 (7601)
  build: Mark sec256k1 and mpt-crypto as transitive headers (7658)
  chore: Add a script to nicely format clang-tidy output (7650)
  chore: Enable most bugprone checks (7643)
  feat: Confidential Transfer for MPT (5860)
  fix: Use trustline balance direction to validate IOU PaymentMint/PaymentBurn (7584)
  fix: Unify freeze checks for pseudo-account deposit/withdraw (7382)
  fix: Block delegate tx from being queued (7640)
  chore: Enable groups of clang-tidy checks by default (7637)
  ci: Better determine when we need to run full clang-tidy (7635)
  refactor: Retire NFTokenReserve fix (7367)
  refactor: Retire Clawback amendment (7353)
  refactor: Rename (mostly keylet) functions to more closely match the docs (7059)
  build: Switch to a new conan XRPLF remote, again (7638)
  chore: Revert "build: Switch to a new conan XRPLF remote (7622)" (7623)
  build: Switch to a new conan XRPLF remote (7622)
  build: Align xrpld RPM packaging with DEB package (7529)
  chore: Use clang-tidy v22 new features (7427)
  build: Patch nix binaries in CMake (7539)
  ...
2026-06-30 16:48:26 -04:00
Ed Hennis
5d2be06748 Merge branch 'develop' into ximinez/fix/validator-cache 2026-05-19 16:53:46 -04:00
Ed Hennis
2f5b17c2cb Merge branch 'develop' into ximinez/fix/validator-cache 2026-05-19 10:15:25 -04:00
Ed Hennis
a963e4a389 Merge branch 'develop' into ximinez/fix/validator-cache 2026-05-19 05:16:02 -04:00
Ed Hennis
962f3ee744 Merge branch 'develop' into ximinez/fix/validator-cache 2026-05-15 21:43:29 -04:00
Ed Hennis
2a7499a415 Merge branch 'develop' into ximinez/fix/validator-cache 2026-05-14 20:39:19 -04:00
Ed Hennis
ae8c44b606 Merge branch 'develop' into ximinez/fix/validator-cache 2026-05-13 22:32:02 -04:00
Ed Hennis
1edd6fe4bf Merge branch 'develop' into ximinez/fix/validator-cache 2026-05-13 12:03:49 -04:00
Ed Hennis
2edf310a6f Merge branch 'develop' into ximinez/fix/validator-cache 2026-05-12 19:18:57 -04:00
Ed Hennis
7af68be818 Merge branch 'develop' into ximinez/fix/validator-cache 2026-05-12 16:26:19 -04:00
Ed Hennis
19fc5c48dd Merge branch 'develop' into ximinez/fix/validator-cache 2026-05-11 13:34:22 -04:00
Ed Hennis
2f43a1901d Merge branch 'develop' into ximinez/fix/validator-cache 2026-05-07 18:10:13 -04:00
Ed Hennis
6a0440bf6d Merge branch 'develop' into ximinez/fix/validator-cache 2026-05-07 14:18:39 -04:00
Ed Hennis
cf398d05aa Merge branch 'develop' into ximinez/fix/validator-cache 2026-05-07 13:28:42 -04:00
Ed Hennis
59a8e30a3e Merge branch 'develop' into ximinez/fix/validator-cache 2026-05-06 22:34:33 -04:00
Ed Hennis
8160921783 Merge branch 'develop' into ximinez/fix/validator-cache 2026-05-06 14:18:12 -04:00
Ed Hennis
6dc01196b4 Merge branch 'develop' into ximinez/fix/validator-cache 2026-05-05 16:46:54 -04:00
Ed Hennis
cdbbbdd27f Merge branch 'develop' into ximinez/fix/validator-cache 2026-05-05 10:50:44 -04:00
Ed Hennis
3d30c09031 Merge branch 'develop' into ximinez/fix/validator-cache 2026-05-05 00:14:58 -04:00
Ed Hennis
e59ca23487 Merge branch 'develop' into ximinez/fix/validator-cache 2026-05-01 13:59:15 -04:00
Ed Hennis
72c700c3e0 Merge branch 'develop' into ximinez/fix/validator-cache 2026-04-28 16:23:38 -04:00
Ed Hennis
4589fcbcfc Merge branch 'develop' into ximinez/fix/validator-cache 2026-04-25 14:45:54 -04:00
Ed Hennis
86d840f53d Merge branch 'develop' into ximinez/fix/validator-cache 2026-04-23 15:56:11 -04:00
Ed Hennis
741b61cdf3 Merge branch 'develop' into ximinez/fix/validator-cache 2026-04-22 23:40:28 -04:00
Ed Hennis
6bb0989c9f Merge branch 'develop' into ximinez/fix/validator-cache 2026-04-22 14:49:12 -04:00
Ed Hennis
9120506613 Merge branch 'develop' into ximinez/fix/validator-cache 2026-04-22 13:10:44 -04:00
Ed Hennis
3b3de96bd4 Merge branch 'develop' into ximinez/fix/validator-cache 2026-04-21 18:48:39 -04:00
Ed Hennis
c9ab6ab25f Merge remote-tracking branch 'XRPLF/develop' into ximinez/fix/validator-cache
* XRPLF/develop:
  chore: Remove empty Taker.h (6984)
  chore: Enable clang-tidy modernize checks (6975)
  ci: Upload clang-tidy git diff (6983)
  fix: Add rounding to Vault invariants (6217) (6955)
2026-04-21 18:46:58 -04:00
Ed Hennis
fb0605cfd3 Merge branch 'develop' into ximinez/fix/validator-cache 2026-04-20 17:49:47 -04:00
Ed Hennis
156553bb5e Merge branch 'develop' into ximinez/fix/validator-cache 2026-04-20 15:45:04 -04:00
Ed Hennis
781b56849b Merge branch 'develop' into ximinez/fix/validator-cache 2026-04-20 11:39:06 -04:00
Ed Hennis
278c02bebb Merge branch 'develop' into ximinez/fix/validator-cache 2026-04-17 18:11:31 -04:00
Ed Hennis
1d6fedf9a2 Merge branch 'develop' into ximinez/fix/validator-cache 2026-04-16 13:44:37 -04:00
Ed Hennis
2e8de499aa Merge branch 'develop' into ximinez/fix/validator-cache 2026-04-15 19:06:29 -04:00
Ed Hennis
0bce3639a6 Merge branch 'develop' into ximinez/fix/validator-cache 2026-04-15 14:28:55 -04:00
Ed Hennis
8f329e3bc6 Use Validator List (VL) cache files in more scenarios
- If any [validator_list_keys] are not available after all
  [validator_list_sites] have had a chance to be queried, then fall
  back to loading cache files. Currently, cache files are only used if
  no sites are defined, or the request to one of them has an error. It
  does not include cases where not enough sites are defined, or if a
  site returns an invalid VL (or something else entirely).
- Resolves #5320
2026-04-13 19:46:06 -04:00
9 changed files with 482 additions and 1129 deletions

View File

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

View File

@@ -32,6 +32,10 @@ removeTokenOffersWithLimit(
Keylet const& directory,
std::size_t maxDeletableOffers);
/** Returns tesSUCCESS if NFToken has few enough offers that it can be burned */
TER
notTooManyOffers(ReadView const& view, uint256 const& nftokenID);
/** Finds the specified token in the owner's token directory. */
std::optional<STObject>
findToken(ReadView const& view, AccountID const& owner, uint256 const& nftokenID);

View File

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

View File

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

View File

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

View File

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

View File

@@ -618,6 +618,33 @@ removeTokenOffersWithLimit(ApplyView& view, Keylet const& directory, std::size_t
return deletedOffersCount;
}
TER
notTooManyOffers(ReadView const& view, uint256 const& nftokenID)
{
std::size_t totalOffers = 0;
{
Dir const buys(view, keylet::nft_buys(nftokenID));
for (auto iter = buys.begin(); iter != buys.end(); iter.next_page())
{
totalOffers += iter.page_size();
if (totalOffers > maxDeletableTokenOfferEntries)
return tefTOO_BIG;
}
}
{
Dir const sells(view, keylet::nft_sells(nftokenID));
for (auto iter = sells.begin(); iter != sells.end(); iter.next_page())
{
totalOffers += iter.page_size();
if (totalOffers > maxDeletableTokenOfferEntries)
return tefTOO_BIG;
}
}
return tesSUCCESS;
}
bool
deleteTokenOffer(ApplyView& view, SLE::ref offer)
{

View File

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

View File

@@ -160,7 +160,11 @@ ValidatorSite::load(
{
try
{
sites_.emplace_back(uri);
// This is not super efficient, but it doesn't happen often.
bool found = std::ranges::any_of(
sites_, [&uri](auto const& site) { return site.loadedResource->uri == uri; });
if (!found)
sites_.emplace_back(uri);
}
catch (std::exception const& e)
{
@@ -221,7 +225,17 @@ ValidatorSite::setTimer(
std::scoped_lock<std::mutex> const& siteLock,
std::scoped_lock<std::mutex> const& stateLock)
{
auto next = std::ranges::min_element(
if (!sites_.empty() && //
std::ranges::all_of(
sites_, [](auto const& site) { return site.lastRefreshStatus.has_value(); }))
{
// If all of the sites have been handled at least once (including
// errors and timeouts), call missingSite, which will load the cache
// files for any lists that are still unavailable.
missingSite(site_lock);
}
auto const next = std::ranges::min_element(
sites_, [](Site const& a, Site const& b) { return a.nextRefresh < b.nextRefresh; });
if (next != sites_.end())
@@ -332,7 +346,7 @@ ValidatorSite::onRequestTimeout(std::size_t siteIdx, error_code const& ec)
// processes a network error. Usually, this function runs first,
// but on extremely rare occasions, the response handler can run
// first, which will leave activeResource empty.
auto const& site = sites_[siteIdx];
auto& site = sites_[siteIdx];
if (site.activeResource)
{
JLOG(j_.warn()) << "Request for " << site.activeResource->uri << " took too long";
@@ -342,6 +356,9 @@ ValidatorSite::onRequestTimeout(std::size_t siteIdx, error_code const& ec)
JLOG(j_.error()) << "Request took too long, but a response has "
"already been processed";
}
if (!site.lastRefreshStatus)
site.lastRefreshStatus.emplace(
Site::Status{clock_type::now(), ListDisposition::invalid, "timeout"});
}
std::scoped_lock const lockState{stateMutex_};