mirror of
https://github.com/XRPLF/rippled.git
synced 2026-07-01 11:32:10 +00:00
Compare commits
49 Commits
mathbunnyr
...
ximinez/nu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ea6f08d301 | ||
|
|
9529fd97f6 | ||
|
|
72bc522fa7 | ||
|
|
027c6f6e57 | ||
|
|
394ea6b69c | ||
|
|
4195c5b1ff | ||
|
|
000cf9f489 | ||
|
|
2a5b1b4e17 | ||
|
|
4ff398ba2d | ||
|
|
6aa9031848 | ||
|
|
e090083dc7 | ||
|
|
2c074d03c7 | ||
|
|
d373cb5db8 | ||
|
|
14a43cfc35 | ||
|
|
6504042b8d | ||
|
|
cebd4ecf15 | ||
|
|
dbe34b4a29 | ||
|
|
554bb62a65 | ||
|
|
6bc267c112 | ||
|
|
a8f4d790bd | ||
|
|
bebddb0e3e | ||
|
|
b30a70bbf8 | ||
|
|
353b3ae07d | ||
|
|
b2790e2f50 | ||
|
|
29d017c2a3 | ||
|
|
764be7f20f | ||
|
|
5703ca527f | ||
|
|
a1cfa89e15 | ||
|
|
318c2c2dd3 | ||
|
|
5c62c15ad8 | ||
|
|
772e0c30f7 | ||
|
|
182ca1c12f | ||
|
|
2e97056b40 | ||
|
|
693e9015ab | ||
|
|
1162ccf7f4 | ||
|
|
07ae5fa867 | ||
|
|
6cc45297d7 | ||
|
|
b263f442be | ||
|
|
7191574499 | ||
|
|
e77b154edc | ||
|
|
184f936362 | ||
|
|
64cb53629d | ||
|
|
8ca90e7d01 | ||
|
|
0a24023797 | ||
|
|
73bd964917 | ||
|
|
48e0ca72b0 | ||
|
|
6d89fbef7a | ||
|
|
b624b2eeae | ||
|
|
f255ef6214 |
@@ -51,37 +51,43 @@ namespace detail {
|
||||
* compile time. Doing it at runtime would be pretty wasteful and
|
||||
* inefficient.
|
||||
*/
|
||||
constexpr std::size_t kInt64Digits = 20;
|
||||
consteval std::array<std::uint64_t, kInt64Digits>
|
||||
constexpr std::size_t kUint64Digits = 20;
|
||||
[[maybe_unused]] constexpr std::size_t kUint128Digits = 39;
|
||||
|
||||
template <typename T, std::size_t Digits>
|
||||
consteval std::array<T, Digits>
|
||||
buildPowersOfTen()
|
||||
{
|
||||
std::array<std::uint64_t, kInt64Digits> result{};
|
||||
std::array<T, Digits> result{};
|
||||
|
||||
std::uint64_t power = 1;
|
||||
T power = 1;
|
||||
std::size_t exponent = 0;
|
||||
// end the loop early so it doesn't overflow;
|
||||
for (; exponent < result.size() - 1; ++exponent, power *= 10)
|
||||
{
|
||||
result[exponent] = power;
|
||||
if (power > std::numeric_limits<std::uint64_t>::max() / 10)
|
||||
if (power > std::numeric_limits<T>::max() / 10)
|
||||
throw std::logic_error("Power of 10 table is too big");
|
||||
}
|
||||
result[exponent] = power;
|
||||
if (power < std::numeric_limits<std::uint64_t>::max() / 10)
|
||||
throw std::logic_error("Power of 10 table is not big enough for the uint64_t type");
|
||||
if (power < std::numeric_limits<T>::max() / 10)
|
||||
throw std::logic_error("Power of 10 table is not big enough for the given type");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
constexpr std::array<std::uint64_t, detail::kInt64Digits> kPowerOfTen = detail::buildPowersOfTen();
|
||||
template <typename T = std::uint64_t, std::size_t Digits = detail::kUint64Digits>
|
||||
constexpr std::array<T, Digits> kPowerOfTenImpl = detail::buildPowersOfTen<T, Digits>();
|
||||
|
||||
constexpr auto kPowerOfTen = kPowerOfTenImpl<std::uint64_t, detail::kUint64Digits>;
|
||||
|
||||
static_assert(kPowerOfTen[0] == 1);
|
||||
static_assert(kPowerOfTen[1] == 10);
|
||||
static_assert(kPowerOfTen[10] == 10'000'000'000);
|
||||
static_assert(
|
||||
isPowerOfTen(kPowerOfTen.back()) && *logTen(kPowerOfTen.back()) == detail::kInt64Digits - 1);
|
||||
isPowerOfTen(kPowerOfTen.back()) && *logTen(kPowerOfTen.back()) == detail::kUint64Digits - 1);
|
||||
|
||||
/** MantissaRange defines a range for the mantissa of a normalized Number.
|
||||
*
|
||||
@@ -120,17 +126,37 @@ struct MantissaRange final
|
||||
{
|
||||
using rep = std::uint64_t;
|
||||
|
||||
// NOLINTBEGIN(readability-enum-initial-value)
|
||||
// The values don't matter, except for Large
|
||||
enum class MantissaScale {
|
||||
// Small can be removed when either featureSingleAssetVault or featureLendingProtocol are
|
||||
// retired
|
||||
Small,
|
||||
// LargeLegacy can be removed when fixCleanup3_2_0 is retired
|
||||
LargeLegacy,
|
||||
Large,
|
||||
// Large320 can be removed when fixCleanup3_3_0 is retired
|
||||
Large320,
|
||||
// If Large330 is ever the only remaining "Large*" entry, it can be renamed to just "Large".
|
||||
Large330,
|
||||
// Large is a de-facto alias for "the latest", and is only here for backward compatibility
|
||||
// in the extremely unlikely case that a downstream project made use of it. Note that
|
||||
// because the behavior changed, this may still be a breaking change.
|
||||
Large = Large330,
|
||||
};
|
||||
// NOLINTEND(readability-enum-initial-value)
|
||||
|
||||
// This entire enum can be removed when fixCleanup3_2_0 is retired
|
||||
enum class CuspRoundingFix : bool {
|
||||
Disabled = false,
|
||||
Enabled = true,
|
||||
// This entire enum can be removed when the last relevant amendment is retired
|
||||
enum class CuspRoundingFix : std::uint8_t {
|
||||
// Disabled can be removed when fixCleanup3_2_0 is retired
|
||||
Disabled = 0,
|
||||
// Enabled320 can be removed when fixCleanup3_3_0 is retired
|
||||
Enabled320 = 1,
|
||||
// If we ever get to the point that there's only one entry, remove the entire enum
|
||||
Enabled330 = 2,
|
||||
// Enabled is a de-facto alias for "the latest", and is only here for backward compatibility
|
||||
// in the extremely unlikely case that a downstream project made use of it. Note that
|
||||
// because the behavior changed, this may still be a breaking change.
|
||||
Enabled = Enabled330,
|
||||
};
|
||||
|
||||
explicit constexpr MantissaRange(MantissaScale sc) : scale(sc)
|
||||
@@ -141,13 +167,27 @@ struct MantissaRange final
|
||||
int const log{getExponent(scale)};
|
||||
rep const min{getMin(scale, log)};
|
||||
rep const max{(min * 10) - 1};
|
||||
CuspRoundingFix const cuspRoundingFixEnabled{isCuspFixEnabled(scale)};
|
||||
|
||||
static MantissaRange const&
|
||||
getMantissaRange(MantissaScale scale);
|
||||
CuspRoundingFix const cuspRoundingFix{isCuspFixEnabled(scale)};
|
||||
|
||||
static std::set<MantissaScale> const&
|
||||
getAllScales();
|
||||
getAllScales()
|
||||
{
|
||||
static std::set<MantissaRange::MantissaScale> const kScales = {
|
||||
MantissaRange::MantissaScale::Small,
|
||||
MantissaRange::MantissaScale::LargeLegacy,
|
||||
MantissaRange::MantissaScale::Large320,
|
||||
MantissaRange::MantissaScale::Large330,
|
||||
};
|
||||
return kScales;
|
||||
}
|
||||
|
||||
class Access
|
||||
{
|
||||
static constexpr MantissaRange const&
|
||||
mantissaRange(MantissaScale scale);
|
||||
|
||||
friend Number;
|
||||
};
|
||||
|
||||
private:
|
||||
static constexpr int
|
||||
@@ -158,7 +198,8 @@ private:
|
||||
case MantissaScale::Small:
|
||||
return 15;
|
||||
case MantissaScale::LargeLegacy:
|
||||
case MantissaScale::Large:
|
||||
case MantissaScale::Large320:
|
||||
case MantissaScale::Large330:
|
||||
return 18;
|
||||
// LCOV_EXCL_START
|
||||
default:
|
||||
@@ -187,17 +228,16 @@ private:
|
||||
case MantissaScale::Small:
|
||||
case MantissaScale::LargeLegacy:
|
||||
return CuspRoundingFix::Disabled;
|
||||
case MantissaScale::Large:
|
||||
return CuspRoundingFix::Enabled;
|
||||
case MantissaScale::Large320:
|
||||
return CuspRoundingFix::Enabled320;
|
||||
case MantissaScale::Large330:
|
||||
return CuspRoundingFix::Enabled330;
|
||||
default:
|
||||
// If called in a constexpr context, this throw assures that the build fails if an
|
||||
// invalid scale is used.
|
||||
throw std::runtime_error("Unknown mantissa scale"); // LCOV_EXCL_LINE
|
||||
}
|
||||
}
|
||||
|
||||
static std::unordered_map<MantissaScale, MantissaRange> const&
|
||||
getRanges();
|
||||
};
|
||||
|
||||
// Like std::integral, but only 64-bit integral types.
|
||||
@@ -550,9 +590,15 @@ private:
|
||||
// changing the values inside the range.
|
||||
static thread_local std::reference_wrapper<MantissaRange const> kRange;
|
||||
|
||||
class Guard;
|
||||
|
||||
void
|
||||
normalize(MantissaRange const& range);
|
||||
|
||||
// Guard has the fields that we need, as well as MantissaRange, so if we have a guard, use that
|
||||
void
|
||||
normalize(Guard const& guard);
|
||||
|
||||
/** Normalize Number components to an arbitrary range.
|
||||
*
|
||||
* min/maxMantissa are parameters because this function is used by both
|
||||
@@ -567,7 +613,7 @@ private:
|
||||
int& exponent,
|
||||
internalrep const& minMantissa,
|
||||
internalrep const& maxMantissa,
|
||||
MantissaRange::CuspRoundingFix cuspRoundingFixEnabled);
|
||||
MantissaRange::CuspRoundingFix cuspRoundingFix);
|
||||
|
||||
template <class T>
|
||||
friend void
|
||||
@@ -577,7 +623,7 @@ private:
|
||||
int& exponent,
|
||||
MantissaRange::rep const& minMantissa,
|
||||
MantissaRange::rep const& maxMantissa,
|
||||
MantissaRange::CuspRoundingFix cuspRoundingFixEnabled,
|
||||
MantissaRange::CuspRoundingFix cuspRoundingFix,
|
||||
bool dropped);
|
||||
|
||||
[[nodiscard]] bool
|
||||
@@ -595,8 +641,6 @@ private:
|
||||
// UB, and can vary across compilers.
|
||||
static internalrep
|
||||
externalToInternal(rep mantissa);
|
||||
|
||||
class Guard;
|
||||
};
|
||||
|
||||
constexpr Number::Number(bool negative, internalrep mantissa, int exponent, Unchecked) noexcept
|
||||
@@ -858,21 +902,11 @@ squelch(Number const& x, Number const& limit) noexcept
|
||||
return x;
|
||||
}
|
||||
|
||||
inline std::string
|
||||
to_string(MantissaRange::MantissaScale const& scale)
|
||||
{
|
||||
switch (scale)
|
||||
{
|
||||
case MantissaRange::MantissaScale::Small:
|
||||
return "small";
|
||||
case MantissaRange::MantissaScale::LargeLegacy:
|
||||
return "largeLegacy";
|
||||
case MantissaRange::MantissaScale::Large:
|
||||
return "large";
|
||||
default:
|
||||
throw std::runtime_error("Bad scale");
|
||||
}
|
||||
}
|
||||
std::string
|
||||
to_string(MantissaRange::MantissaScale const& scale);
|
||||
|
||||
std::string
|
||||
to_string(Number::RoundingMode const& round);
|
||||
|
||||
class SaveNumberRoundMode
|
||||
{
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
@@ -31,74 +30,103 @@ namespace xrpl {
|
||||
|
||||
thread_local Number::RoundingMode Number::mode = Number::RoundingMode::ToNearest;
|
||||
thread_local std::reference_wrapper<MantissaRange const> Number::kRange =
|
||||
MantissaRange::getMantissaRange(MantissaRange::MantissaScale::Large);
|
||||
MantissaRange::Access::mantissaRange(MantissaRange::MantissaScale::Large330);
|
||||
|
||||
std::set<MantissaRange::MantissaScale> const&
|
||||
MantissaRange::getAllScales()
|
||||
std::string
|
||||
to_string(MantissaRange::MantissaScale const& scale)
|
||||
{
|
||||
static std::set<MantissaRange::MantissaScale> const kScales = {
|
||||
MantissaRange::MantissaScale::Small,
|
||||
MantissaRange::MantissaScale::LargeLegacy,
|
||||
MantissaRange::MantissaScale::Large,
|
||||
};
|
||||
return kScales;
|
||||
switch (scale)
|
||||
{
|
||||
case MantissaRange::MantissaScale::Small:
|
||||
return "Small";
|
||||
case MantissaRange::MantissaScale::LargeLegacy:
|
||||
return "LargeLegacy";
|
||||
case MantissaRange::MantissaScale::Large320:
|
||||
return "Large320";
|
||||
case MantissaRange::MantissaScale::Large330:
|
||||
return "Large330";
|
||||
default:
|
||||
throw std::runtime_error("Bad scale"); // LCOV_EXCL_LINE
|
||||
}
|
||||
}
|
||||
|
||||
std::unordered_map<MantissaRange::MantissaScale, MantissaRange> const&
|
||||
MantissaRange::getRanges()
|
||||
std::string
|
||||
to_string(Number::RoundingMode const& round)
|
||||
{
|
||||
static auto const kMap = []() {
|
||||
std::unordered_map<MantissaScale, MantissaRange> map;
|
||||
for (auto const scale : getAllScales())
|
||||
{
|
||||
map.emplace(scale, scale);
|
||||
}
|
||||
|
||||
// Use these constexpr declarations to do static_asserts to verify the MantissaRanges are
|
||||
// created correctly, but nothing else.
|
||||
{
|
||||
[[maybe_unused]]
|
||||
constexpr static MantissaRange kRange{MantissaRange::MantissaScale::Small};
|
||||
static_assert(isPowerOfTen(kRange.min));
|
||||
static_assert(kRange.min == 1'000'000'000'000'000LL);
|
||||
static_assert(kRange.max == 9'999'999'999'999'999LL);
|
||||
static_assert(kRange.log == 15);
|
||||
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(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::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(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::kMaxRep);
|
||||
static_assert(kRange.max > Number::kMaxRep);
|
||||
static_assert(kRange.cuspRoundingFixEnabled == CuspRoundingFix::Enabled);
|
||||
}
|
||||
return map;
|
||||
}();
|
||||
|
||||
return kMap;
|
||||
switch (round)
|
||||
{
|
||||
case Number::RoundingMode::ToNearest:
|
||||
return "ToNearest";
|
||||
case Number::RoundingMode::TowardsZero:
|
||||
return "TowardsZero";
|
||||
case Number::RoundingMode::Downward:
|
||||
return "Downward";
|
||||
case Number::RoundingMode::Upward:
|
||||
return "Upward";
|
||||
default:
|
||||
throw std::runtime_error("Bad rounding mode"); // LCOV_EXCL_LINE
|
||||
}
|
||||
}
|
||||
|
||||
MantissaRange const&
|
||||
MantissaRange::getMantissaRange(MantissaScale scale)
|
||||
constexpr MantissaRange const&
|
||||
MantissaRange::Access::mantissaRange(MantissaScale scale)
|
||||
{
|
||||
return getRanges().at(scale);
|
||||
static constexpr MantissaRange kSmall{MantissaScale::Small};
|
||||
static constexpr MantissaRange kLegacy{MantissaScale::LargeLegacy};
|
||||
static constexpr MantissaRange kLarge320{MantissaScale::Large320};
|
||||
static constexpr MantissaRange kLarge330{MantissaScale::Large330};
|
||||
|
||||
switch (scale)
|
||||
{
|
||||
case MantissaScale::Small:
|
||||
return kSmall;
|
||||
case MantissaScale::LargeLegacy:
|
||||
return kLegacy;
|
||||
case MantissaScale::Large320:
|
||||
return kLarge320;
|
||||
case MantissaScale::Large330:
|
||||
return kLarge330;
|
||||
}
|
||||
throw std::logic_error("Unknown mantissa scale");
|
||||
|
||||
// static_asserts are checked at compile time, so it doesn't matter where in the function they
|
||||
// are located. For readability of the main body, put them after it.
|
||||
|
||||
// Small
|
||||
static_assert(isPowerOfTen(kSmall.min));
|
||||
static_assert(kSmall.min == 1'000'000'000'000'000LL);
|
||||
static_assert(kSmall.max == 9'999'999'999'999'999LL);
|
||||
static_assert(kSmall.log == 15);
|
||||
static_assert(kSmall.min < Number::kMaxRep);
|
||||
static_assert(kSmall.max < Number::kMaxRep);
|
||||
static_assert(kSmall.cuspRoundingFix == CuspRoundingFix::Disabled);
|
||||
|
||||
// LargeLegacy
|
||||
static_assert(isPowerOfTen(kLegacy.min));
|
||||
static_assert(kLegacy.min == 1'000'000'000'000'000'000ULL);
|
||||
static_assert(kLegacy.max == rep(9'999'999'999'999'999'999ULL));
|
||||
static_assert(kLegacy.log == 18);
|
||||
static_assert(kLegacy.min < Number::kMaxRep);
|
||||
static_assert(kLegacy.max > Number::kMaxRep);
|
||||
static_assert(kLegacy.cuspRoundingFix == CuspRoundingFix::Disabled);
|
||||
|
||||
// Large320
|
||||
static_assert(isPowerOfTen(kLarge320.min));
|
||||
static_assert(kLarge320.min == 1'000'000'000'000'000'000ULL);
|
||||
static_assert(kLarge320.max == rep(9'999'999'999'999'999'999ULL));
|
||||
static_assert(kLarge320.log == 18);
|
||||
static_assert(kLarge320.min < Number::kMaxRep);
|
||||
static_assert(kLarge320.max > Number::kMaxRep);
|
||||
static_assert(kLarge320.cuspRoundingFix == CuspRoundingFix::Enabled320);
|
||||
|
||||
// Large330
|
||||
static_assert(isPowerOfTen(kLarge330.min));
|
||||
static_assert(kLarge330.min == 1'000'000'000'000'000'000ULL);
|
||||
static_assert(kLarge330.max == rep(9'999'999'999'999'999'999ULL));
|
||||
static_assert(kLarge330.log == 18);
|
||||
static_assert(kLarge330.min < Number::kMaxRep);
|
||||
static_assert(kLarge330.max > Number::kMaxRep);
|
||||
static_assert(kLarge330.cuspRoundingFix == CuspRoundingFix::Enabled330);
|
||||
}
|
||||
|
||||
Number::RoundingMode
|
||||
@@ -124,7 +152,7 @@ Number::setMantissaScale(MantissaRange::MantissaScale scale)
|
||||
{
|
||||
if (!MantissaRange::getAllScales().contains(scale))
|
||||
logicError("Unknown mantissa scale");
|
||||
kRange = MantissaRange::getMantissaRange(scale);
|
||||
kRange = MantissaRange::Access::mantissaRange(scale);
|
||||
}
|
||||
|
||||
// Optimization equivalent to:
|
||||
@@ -155,15 +183,38 @@ divu10(uint128_t& u)
|
||||
return r;
|
||||
}
|
||||
|
||||
// Guard
|
||||
|
||||
// The Guard class is used to temporarily add extra digits of
|
||||
// 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>;
|
||||
|
||||
/** Guard
|
||||
|
||||
The Guard class is used to temporarily add extra digits of
|
||||
precision to an operation. This enables the final result
|
||||
to be correctly rounded to the internal precision of Number.
|
||||
|
||||
At its core, the Guard really only needs three pieces of information to determine how to round:
|
||||
1. The rounding mode
|
||||
2. The last digit dropped from the mantissa (i.e. the first digit after the decimal point).
|
||||
(first byte of digits_)
|
||||
3. Whether any other non-zero digits were dropped from the mantissa. (remaining bytes of digits_
|
||||
and xbit_)
|
||||
|
||||
Upward and Downward rounding modes round the unsigned mantissa toward or away from zero
|
||||
depending on whether the sign is negative (sbit_). For positive values, Upward is away, and
|
||||
Downward is toward. For negative values, that's reversed. For simplicity, I'm going to describe
|
||||
the logic using "TowardZero" and "AwayFromZero".
|
||||
|
||||
* TowardZero is the easiest rounding mode. It always rounds down. digits_ and xbit_ are
|
||||
irrelevant.
|
||||
* AwayFromZero is almost as simple. If both "digits_" and "xbit_" are zero (0), it rounds down.
|
||||
Else it rounds up.
|
||||
* ToNearest is only a little more complicated. If the last dropped digit is < 5, then round
|
||||
down. If it is > 5, round up. If it is exactly 5, and there are _any_ other digits (the
|
||||
remainder of "digits_" or "xbit_"), round up, else round to even.
|
||||
|
||||
The current implementation stores 16 digits in "digits_" so that digits can be "pop"ped back
|
||||
out if needed during subtraction (negative addition) operations.
|
||||
*/
|
||||
class Number::Guard
|
||||
{
|
||||
std::uint64_t digits_{0}; // 16 decimal guard digits
|
||||
@@ -171,7 +222,21 @@ class Number::Guard
|
||||
std::uint8_t sbit_ : 1 {0}; // the sign of the guard digits
|
||||
|
||||
public:
|
||||
explicit Guard() = default;
|
||||
internalrep const minMantissa;
|
||||
internalrep const maxMantissa;
|
||||
MantissaRange::CuspRoundingFix const cuspRoundingFix;
|
||||
|
||||
explicit Guard(
|
||||
internalrep const& minMantissa,
|
||||
internalrep const& maxMantissa,
|
||||
MantissaRange::CuspRoundingFix cuspRoundingFix)
|
||||
: minMantissa(minMantissa), maxMantissa(maxMantissa), cuspRoundingFix(cuspRoundingFix)
|
||||
{
|
||||
}
|
||||
|
||||
explicit Guard(MantissaRange const& range) : Guard(range.min, range.max, range.cuspRoundingFix)
|
||||
{
|
||||
}
|
||||
|
||||
// set & test the sign bit
|
||||
void
|
||||
@@ -194,6 +259,10 @@ public:
|
||||
unsigned
|
||||
pop() noexcept;
|
||||
|
||||
// if true, there are no digits in the guard, including dropped digits (xbit_)
|
||||
[[nodiscard]] bool
|
||||
empty() const noexcept;
|
||||
|
||||
/** Drop a digit from the mantissa, and increment the exponent, storing the dropped digit in
|
||||
* this Guard.
|
||||
*
|
||||
@@ -206,28 +275,37 @@ public:
|
||||
void
|
||||
doDropDigit(T& mantissa, int& exponent) noexcept;
|
||||
|
||||
enum class Round {
|
||||
// The result is exact. No rounding is needed. Only used if cuspRoundingFix is Enabled330 or
|
||||
// higher.
|
||||
Exact = -2,
|
||||
// Round down. Since we use integer math, that usually means no change is needed.
|
||||
// Exceptions are for when the result is between kMaxRep and kMaxRepUp (round to kMaxRep),
|
||||
// or after subtraction where _any_ remainder will modify the result. The latter is what
|
||||
// distinguishes Exact from Down.
|
||||
Down = -1,
|
||||
// The result was exactly half-way between two integers. This will round to even.
|
||||
Even = 0,
|
||||
// Round up. Always adds 1 (or subtracts 1 in some cases if cuspRoundingFix is not
|
||||
// Enabled)
|
||||
Up = 1,
|
||||
};
|
||||
|
||||
// Indicate round direction: 1 is up, -1 is down, 0 is even
|
||||
// This enables the client to round towards nearest, and on
|
||||
// tie, round towards even.
|
||||
[[nodiscard]] int
|
||||
[[nodiscard]] Round
|
||||
round() const noexcept;
|
||||
|
||||
// Modify the result to the correctly rounded value
|
||||
template <UnsignedMantissa T>
|
||||
void
|
||||
doRoundUp(
|
||||
bool& negative,
|
||||
T& mantissa,
|
||||
int& exponent,
|
||||
internalrep const& minMantissa,
|
||||
internalrep const& maxMantissa,
|
||||
MantissaRange::CuspRoundingFix cuspRoundingFixEnabled,
|
||||
std::string location);
|
||||
doRoundUp(bool& negative, T& mantissa, int& exponent, std::string location);
|
||||
|
||||
// Modify the result to the correctly rounded value
|
||||
template <UnsignedMantissa T>
|
||||
void
|
||||
doRoundDown(bool& negative, T& mantissa, int& exponent, internalrep const& minMantissa);
|
||||
doRoundDown(bool& negative, T& mantissa, int& exponent);
|
||||
|
||||
// Modify the result to the correctly rounded value
|
||||
void
|
||||
@@ -239,7 +317,7 @@ private:
|
||||
|
||||
template <UnsignedMantissa T>
|
||||
void
|
||||
bringIntoRange(bool& negative, T& mantissa, int& exponent, internalrep const& minMantissa);
|
||||
bringIntoRange(bool& negative, T& mantissa, int& exponent);
|
||||
};
|
||||
|
||||
inline void
|
||||
@@ -289,6 +367,12 @@ Number::Guard::pop() noexcept
|
||||
return d;
|
||||
}
|
||||
|
||||
inline bool
|
||||
Number::Guard::empty() const noexcept
|
||||
{
|
||||
return digits_ == 0 && !xbit_;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void
|
||||
Number::Guard::doDropDigit(T& mantissa, int& exponent) noexcept
|
||||
@@ -314,50 +398,52 @@ Number::Guard::doDropDigit<uint128_t>(uint128_t& mantissa, int& exponent) noexce
|
||||
// -1 if Guard is less than half
|
||||
// 0 if Guard is exactly half
|
||||
// 1 if Guard is greater than half
|
||||
int
|
||||
Number::Guard::Round
|
||||
Number::Guard::round() const noexcept
|
||||
{
|
||||
auto mode = Number::getround();
|
||||
// Local "mode" shadows and has the same value as the static thread_local "Number::mode".
|
||||
// This ensures the overhead of loading the thread_local is only incurred once.
|
||||
auto const mode = Number::getround();
|
||||
|
||||
if (cuspRoundingFix >= MantissaRange::CuspRoundingFix::Enabled330 && empty())
|
||||
{
|
||||
// No remainder
|
||||
return Round::Exact;
|
||||
}
|
||||
|
||||
if (mode == RoundingMode::TowardsZero)
|
||||
return -1;
|
||||
return Round::Down;
|
||||
|
||||
if (mode == RoundingMode::Downward)
|
||||
// Also Towards Zero
|
||||
if ((mode == RoundingMode::Downward && !sbit_) || (mode == RoundingMode::Upward && sbit_))
|
||||
{
|
||||
if (sbit_)
|
||||
{
|
||||
if (digits_ > 0 || xbit_)
|
||||
return 1;
|
||||
}
|
||||
return -1;
|
||||
return Round::Down;
|
||||
}
|
||||
|
||||
if (mode == RoundingMode::Upward)
|
||||
// Away from Zero. Since we checked sbit_ in the previous block, we don't need to check it
|
||||
// again.
|
||||
if (mode == RoundingMode::Downward || mode == RoundingMode::Upward)
|
||||
{
|
||||
if (sbit_)
|
||||
return -1;
|
||||
if (digits_ > 0 || xbit_)
|
||||
return 1;
|
||||
return -1;
|
||||
if (empty())
|
||||
return Round::Down;
|
||||
return Round::Up;
|
||||
}
|
||||
|
||||
XRPL_ASSERT(
|
||||
mode == RoundingMode::ToNearest, "xrpl::Number::Guard::Round : fallthrough to ToNearest");
|
||||
// assume round to nearest if mode is not one of the predefined values
|
||||
if (digits_ > 0x5000'0000'0000'0000)
|
||||
return 1;
|
||||
return Round::Up;
|
||||
if (digits_ < 0x5000'0000'0000'0000)
|
||||
return -1;
|
||||
return Round::Down;
|
||||
if (xbit_)
|
||||
return 1;
|
||||
return 0;
|
||||
return Round::Up;
|
||||
return Round::Even;
|
||||
}
|
||||
|
||||
template <UnsignedMantissa T>
|
||||
void
|
||||
Number::Guard::bringIntoRange(
|
||||
bool& negative,
|
||||
T& mantissa,
|
||||
int& exponent,
|
||||
internalrep const& minMantissa)
|
||||
Number::Guard::bringIntoRange(bool& negative, T& mantissa, int& exponent)
|
||||
{
|
||||
// Bring mantissa back into the minMantissa / maxMantissa range AFTER
|
||||
// rounding
|
||||
@@ -378,22 +464,15 @@ Number::Guard::bringIntoRange(
|
||||
|
||||
template <UnsignedMantissa T>
|
||||
void
|
||||
Number::Guard::doRoundUp(
|
||||
bool& negative,
|
||||
T& mantissa,
|
||||
int& exponent,
|
||||
internalrep const& minMantissa,
|
||||
internalrep const& maxMantissa,
|
||||
MantissaRange::CuspRoundingFix cuspRoundingFixEnabled,
|
||||
std::string location)
|
||||
Number::Guard::doRoundUp(bool& negative, T& mantissa, int& exponent, std::string location)
|
||||
{
|
||||
auto r = round();
|
||||
if (r == 1 || (r == 0 && (mantissa & 1) == 1))
|
||||
if (r == Round::Up || (r == Round::Even && (mantissa & 1) == 1))
|
||||
{
|
||||
auto const safeToIncrement = [&maxMantissa](auto const& mantissa) {
|
||||
auto const safeToIncrement = [this](auto const& mantissa) {
|
||||
return mantissa < maxMantissa && mantissa < kMaxRep;
|
||||
};
|
||||
if (cuspRoundingFixEnabled == MantissaRange::CuspRoundingFix::Enabled)
|
||||
if (cuspRoundingFix != MantissaRange::CuspRoundingFix::Disabled)
|
||||
{
|
||||
// Ensure mantissa after incrementing fits within both the
|
||||
// min/maxMantissa range and is a valid "rep".
|
||||
@@ -414,14 +493,7 @@ Number::Guard::doRoundUp(
|
||||
safeToIncrement(mantissa),
|
||||
"xrpl::Number::Guard::doRoundUp",
|
||||
"can't recurse more than once");
|
||||
doRoundUp(
|
||||
negative,
|
||||
mantissa,
|
||||
exponent,
|
||||
minMantissa,
|
||||
maxMantissa,
|
||||
cuspRoundingFixEnabled,
|
||||
location);
|
||||
doRoundUp(negative, mantissa, exponent, location);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -440,30 +512,43 @@ Number::Guard::doRoundUp(
|
||||
}
|
||||
}
|
||||
}
|
||||
bringIntoRange(negative, mantissa, exponent, minMantissa);
|
||||
bringIntoRange(negative, mantissa, exponent);
|
||||
if (exponent > kMaxExponent)
|
||||
Throw<std::overflow_error>(std::string(location));
|
||||
}
|
||||
|
||||
template <UnsignedMantissa T>
|
||||
void
|
||||
Number::Guard::doRoundDown(
|
||||
bool& negative,
|
||||
T& mantissa,
|
||||
int& exponent,
|
||||
internalrep const& minMantissa)
|
||||
Number::Guard::doRoundDown(bool& negative, T& mantissa, int& exponent)
|
||||
{
|
||||
auto r = round();
|
||||
if (r == 1 || (r == 0 && (mantissa & 1) == 1))
|
||||
if (cuspRoundingFix >= MantissaRange::CuspRoundingFix::Enabled330)
|
||||
{
|
||||
--mantissa;
|
||||
if (mantissa < minMantissa)
|
||||
// If there was any remainder, subtract 1 from the result. This is sufficient to get the
|
||||
// best rounding.
|
||||
XRPL_ASSERT(
|
||||
r == Round::Exact || mantissa > maxMantissa,
|
||||
"xrpl::Number::Guard::doRoundDown : mantissa is expected size");
|
||||
if (r != Round::Exact)
|
||||
{
|
||||
mantissa *= 10;
|
||||
--exponent;
|
||||
--mantissa;
|
||||
}
|
||||
}
|
||||
bringIntoRange(negative, mantissa, exponent, minMantissa);
|
||||
else
|
||||
{
|
||||
// Need to preserve the incorrect behavior until the fix amendment can be retired,
|
||||
// because otherwise would risk an unplanned ledger fork.
|
||||
if (r == Round::Up || (r == Round::Even && (mantissa & 1) == 1))
|
||||
{
|
||||
--mantissa;
|
||||
if (mantissa < minMantissa)
|
||||
{
|
||||
mantissa *= 10;
|
||||
--exponent;
|
||||
}
|
||||
}
|
||||
}
|
||||
bringIntoRange(negative, mantissa, exponent);
|
||||
}
|
||||
|
||||
// Modify the result to the correctly rounded value
|
||||
@@ -471,7 +556,7 @@ void
|
||||
Number::Guard::doRound(rep& drops, std::string location) const
|
||||
{
|
||||
auto r = round();
|
||||
if (r == 1 || (r == 0 && (drops & 1) == 1))
|
||||
if (r == Round::Up || (r == Round::Even && (drops & 1) == 1))
|
||||
{
|
||||
if (drops >= kMaxRep)
|
||||
{
|
||||
@@ -530,7 +615,7 @@ doNormalize(
|
||||
int& exponent,
|
||||
MantissaRange::rep const& minMantissa,
|
||||
MantissaRange::rep const& maxMantissa,
|
||||
MantissaRange::CuspRoundingFix cuspRoundingFixEnabled,
|
||||
MantissaRange::CuspRoundingFix cuspRoundingFix,
|
||||
bool dropped)
|
||||
{
|
||||
static constexpr auto kMinExponent = Number::kMinExponent;
|
||||
@@ -553,7 +638,7 @@ doNormalize(
|
||||
m *= 10;
|
||||
--exponent;
|
||||
}
|
||||
Guard g;
|
||||
Guard g(minMantissa, maxMantissa, cuspRoundingFix);
|
||||
if (negative)
|
||||
g.setNegative();
|
||||
if (dropped)
|
||||
@@ -598,14 +683,7 @@ doNormalize(
|
||||
XRPL_ASSERT_PARTS(m <= kMaxRep, "xrpl::doNormalize", "intermediate mantissa fits in int64");
|
||||
mantissa = m;
|
||||
|
||||
g.doRoundUp(
|
||||
negative,
|
||||
mantissa,
|
||||
exponent,
|
||||
minMantissa,
|
||||
maxMantissa,
|
||||
cuspRoundingFixEnabled,
|
||||
"Number::normalize 2");
|
||||
g.doRoundUp(negative, mantissa, exponent, "Number::normalize 2");
|
||||
XRPL_ASSERT_PARTS(
|
||||
mantissa >= minMantissa && mantissa <= maxMantissa,
|
||||
"xrpl::doNormalize",
|
||||
@@ -620,13 +698,12 @@ Number::normalize<uint128_t>(
|
||||
int& exponent,
|
||||
internalrep const& minMantissa,
|
||||
internalrep const& maxMantissa,
|
||||
MantissaRange::CuspRoundingFix cuspRoundingFixEnabled)
|
||||
MantissaRange::CuspRoundingFix cuspRoundingFix)
|
||||
{
|
||||
// Not used by every compiler version, and thus not necessarily
|
||||
// counted by coverage build
|
||||
// LCOV_EXCL_START
|
||||
doNormalize(
|
||||
negative, mantissa, exponent, minMantissa, maxMantissa, cuspRoundingFixEnabled, false);
|
||||
doNormalize(negative, mantissa, exponent, minMantissa, maxMantissa, cuspRoundingFix, false);
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
@@ -638,13 +715,12 @@ Number::normalize<unsigned long long>(
|
||||
int& exponent,
|
||||
internalrep const& minMantissa,
|
||||
internalrep const& maxMantissa,
|
||||
MantissaRange::CuspRoundingFix cuspRoundingFixEnabled)
|
||||
MantissaRange::CuspRoundingFix cuspRoundingFix)
|
||||
{
|
||||
// Not used by every compiler version, and thus not necessarily
|
||||
// counted by coverage build
|
||||
// LCOV_EXCL_START
|
||||
doNormalize(
|
||||
negative, mantissa, exponent, minMantissa, maxMantissa, cuspRoundingFixEnabled, false);
|
||||
doNormalize(negative, mantissa, exponent, minMantissa, maxMantissa, cuspRoundingFix, false);
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
@@ -656,16 +732,27 @@ Number::normalize<unsigned long>(
|
||||
int& exponent,
|
||||
internalrep const& minMantissa,
|
||||
internalrep const& maxMantissa,
|
||||
MantissaRange::CuspRoundingFix cuspRoundingFixEnabled)
|
||||
MantissaRange::CuspRoundingFix cuspRoundingFix)
|
||||
{
|
||||
doNormalize(
|
||||
negative, mantissa, exponent, minMantissa, maxMantissa, cuspRoundingFixEnabled, false);
|
||||
doNormalize(negative, mantissa, exponent, minMantissa, maxMantissa, cuspRoundingFix, false);
|
||||
}
|
||||
|
||||
void
|
||||
Number::normalize(MantissaRange const& range)
|
||||
{
|
||||
normalize(negative_, mantissa_, exponent_, range.min, range.max, range.cuspRoundingFixEnabled);
|
||||
normalize(negative_, mantissa_, exponent_, range.min, range.max, range.cuspRoundingFix);
|
||||
}
|
||||
|
||||
void
|
||||
Number::normalize(Guard const& guard)
|
||||
{
|
||||
normalize(
|
||||
negative_,
|
||||
mantissa_,
|
||||
exponent_,
|
||||
guard.minMantissa,
|
||||
guard.maxMantissa,
|
||||
guard.cuspRoundingFix);
|
||||
}
|
||||
|
||||
// Copy the number, but set a new exponent. Because the mantissa doesn't change,
|
||||
@@ -719,46 +806,102 @@ Number::operator+=(Number const& y)
|
||||
bool const yn = y.negative_;
|
||||
uint128_t ym = y.mantissa_;
|
||||
auto ye = y.exponent_;
|
||||
Guard g;
|
||||
Guard g(kRange);
|
||||
|
||||
auto const& minMantissa = g.minMantissa;
|
||||
auto const& maxMantissa = g.maxMantissa;
|
||||
auto const cuspRoundingFix = g.cuspRoundingFix;
|
||||
|
||||
// Bring the exponents of both values into agreement, so the mantissas are on the same scale
|
||||
// and can be added directly together.
|
||||
|
||||
auto const upperLimit = static_cast<uint128_t>(g.minMantissa) * 1000;
|
||||
// For the "adjust" lambda
|
||||
// expandM / expandE: The values for which the mantissa will be expanded, and the exponent
|
||||
// decreased to match. Mantissa won't be expanded beyond upperLimit.
|
||||
// (37e8 == 37000e5 == 37000000e2)
|
||||
// shrinkM / shrinkE: The values for which the mantissa will be shrunk, and exponent increased
|
||||
// to match, if necessary.
|
||||
auto const adjust = [&g, &upperLimit](
|
||||
uint128_t& expandM, int& expandE, uint128_t& shrinkM, int& shrinkE) {
|
||||
// Adjust up and down until the exponents match
|
||||
if (g.cuspRoundingFix == MantissaRange::CuspRoundingFix::Enabled330)
|
||||
{
|
||||
// For Enabled330, there are three steps.
|
||||
// 1. First, shrink the mantissa of shrinkM/shrinkE while shrinkM ends in 0.
|
||||
while (shrinkE < expandE && shrinkM % 10 == 0)
|
||||
{
|
||||
g.doDropDigit(shrinkM, shrinkE);
|
||||
}
|
||||
|
||||
// 2. Then expand the mantissa of expandM/expandE, with a limit for expandM a few orders
|
||||
// of magnitude above the MantissaRange. This will leave a few extra digits for rounding
|
||||
// later, but nothing excessive.
|
||||
while (shrinkE < expandE && expandE > kMinExponent && expandM < upperLimit)
|
||||
{
|
||||
expandM *= 10;
|
||||
--expandE;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Finally, shrink the mantissa of shrinkM/shrinkE until the exponents match. Any removed
|
||||
// digits will be put into the Guard. This is the only step for non-Enabled330 modes.
|
||||
while (shrinkE < expandE)
|
||||
{
|
||||
g.doDropDigit(shrinkM, shrinkE);
|
||||
}
|
||||
};
|
||||
|
||||
// Shrink the mantissa and raise the exponent of the value with the lower exponent. Store any
|
||||
// dropped digits in the Guard.
|
||||
if (xe < ye)
|
||||
{
|
||||
if (xn)
|
||||
g.setNegative();
|
||||
do
|
||||
{
|
||||
g.doDropDigit(xm, xe);
|
||||
} while (xe < ye);
|
||||
|
||||
adjust(ym, ye, xm, xe);
|
||||
}
|
||||
else if (xe > ye)
|
||||
{
|
||||
if (yn)
|
||||
g.setNegative();
|
||||
do
|
||||
{
|
||||
g.doDropDigit(ym, ye);
|
||||
} while (xe > ye);
|
||||
}
|
||||
|
||||
auto const& range = kRange.get();
|
||||
auto const& minMantissa = range.min;
|
||||
auto const& maxMantissa = range.max;
|
||||
auto const cuspRoundingFixEnabled = range.cuspRoundingFixEnabled;
|
||||
adjust(xm, xe, ym, ye);
|
||||
}
|
||||
else if (g.cuspRoundingFix == MantissaRange::CuspRoundingFix::Enabled330)
|
||||
{
|
||||
// Both values have the same exponent.
|
||||
// Set the sign of the Guard based on the sign of the Number with the smallest
|
||||
// unsigned _mantissa_
|
||||
if ((xm < ym && xn) || (ym < xm && yn))
|
||||
g.setNegative();
|
||||
}
|
||||
|
||||
if (xn == yn)
|
||||
{
|
||||
xm += ym;
|
||||
if (xm > maxMantissa || xm > kMaxRep)
|
||||
|
||||
if (g.cuspRoundingFix >= MantissaRange::CuspRoundingFix::Enabled330)
|
||||
{
|
||||
g.doDropDigit(xm, xe);
|
||||
// Don't do any adjustments for Enabled330. Normalize will take care of it
|
||||
// Because of "adjust", the only way there can be data in the Guard is if we first grew
|
||||
// the mantissa past the maxMantissa. Since we added here, it can only get bigger.
|
||||
// If xm > maxMantissa, then doNormalize has all the data it needs from the last 3-4
|
||||
// digits, plus the "dropped" flag that will be passed in.
|
||||
// If not, then the mantissa will only need to be padded out with 0s and won't need to
|
||||
// round.
|
||||
XRPL_ASSERT(
|
||||
xm > maxMantissa || g.empty(),
|
||||
"xrpl::Number::operator+ : rounding state expected after add");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (xm > maxMantissa || xm > kMaxRep)
|
||||
{
|
||||
g.doDropDigit(xm, xe);
|
||||
}
|
||||
g.doRoundUp(xn, xm, xe, "Number::addition overflow");
|
||||
}
|
||||
g.doRoundUp(
|
||||
xn,
|
||||
xm,
|
||||
xe,
|
||||
minMantissa,
|
||||
maxMantissa,
|
||||
cuspRoundingFixEnabled,
|
||||
"Number::addition overflow");
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -772,19 +915,67 @@ Number::operator+=(Number const& y)
|
||||
xe = ye;
|
||||
xn = yn;
|
||||
}
|
||||
while (xm < minMantissa && xm * 10 <= kMaxRep)
|
||||
if (cuspRoundingFix >= MantissaRange::CuspRoundingFix::Enabled330)
|
||||
{
|
||||
xm *= 10;
|
||||
xm -= g.pop();
|
||||
--xe;
|
||||
// Because we subtracted, xm can have any number of digits from 1 up to
|
||||
// upperLimit * 10, and g can be in any state. (Note that xm can't be zero, because that
|
||||
// special case was tested earlier.)
|
||||
|
||||
// Grow xm/xe and pull digits out of the Guard until xm reaches upperLimit, but stop if
|
||||
// the Guard empties out, because no rounding will be necessary. This will ensure that
|
||||
// normalize will have enough information to make an accurate rounding decision.
|
||||
// (Normalize will pad a small mantissa back into range.) Note that if any digits were
|
||||
// lost (xbit_), the Guard will never be empty, so xm will grow larger than upperLimit.
|
||||
while (xm < upperLimit && !g.empty())
|
||||
{
|
||||
xm *= 10;
|
||||
xm -= g.pop();
|
||||
--xe;
|
||||
}
|
||||
XRPL_ASSERT(
|
||||
xm > maxMantissa || g.empty(),
|
||||
"xrpl::Number::operator+ : rounding state expected after subtract");
|
||||
}
|
||||
g.doRoundDown(xn, xm, xe, minMantissa);
|
||||
else
|
||||
{
|
||||
// Grow xm/xe and pull digits out of the Guard until it's back in the
|
||||
// minMantissa/maxMantissa range.
|
||||
while (xm < minMantissa && xm * 10 <= kMaxRep)
|
||||
{
|
||||
xm *= 10;
|
||||
xm -= g.pop();
|
||||
--xe;
|
||||
}
|
||||
}
|
||||
// Rounding down can result in decrementing xm, based on whether there is any data left in
|
||||
// the Guard (depending on cuspRoundingFix). Note that if that happens, then the Guard is
|
||||
// not empty. For Enabled330, that will also result in the "dropped" flag being passed to
|
||||
// doNormalize, which may result in the mantissa being incremented again. It doesn't matter
|
||||
// what the dropped digits are, only that they exist. This is because subtracting one
|
||||
// "overcorrects", so we know there are still trailing digits to be accounted for in the
|
||||
// rounding.
|
||||
//
|
||||
// This works because
|
||||
// 1. The rounding up will be done _after_ the mantissa is brought into range. It may not
|
||||
// be in range right now, and
|
||||
// 2. The "dropped" flag is only ever used as a tie-breaker, specifically when rounding
|
||||
// away from zero, and the dropped digits are 0, or when rounding to nearest, and
|
||||
// the dropped digits represent exactly 0.5.
|
||||
g.doRoundDown(xn, xm, xe);
|
||||
}
|
||||
|
||||
doNormalize(
|
||||
xn,
|
||||
xm,
|
||||
xe,
|
||||
minMantissa,
|
||||
maxMantissa,
|
||||
cuspRoundingFix,
|
||||
cuspRoundingFix == MantissaRange::CuspRoundingFix::Enabled330 && !g.empty());
|
||||
negative_ = xn;
|
||||
mantissa_ = static_cast<internalrep>(xm);
|
||||
exponent_ = xe;
|
||||
normalize(range);
|
||||
XRPL_ASSERT(isnormal(), "xrpl::Number::operator+= : result is normal");
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -818,14 +1009,11 @@ Number::operator*=(Number const& y)
|
||||
auto ze = xe + ye;
|
||||
auto zs = xs * ys;
|
||||
bool zn = (zs == -1);
|
||||
Guard g;
|
||||
Guard g(kRange);
|
||||
if (zn)
|
||||
g.setNegative();
|
||||
|
||||
auto const& range = kRange.get();
|
||||
auto const& minMantissa = range.min;
|
||||
auto const& maxMantissa = range.max;
|
||||
auto const cuspRoundingFixEnabled = range.cuspRoundingFixEnabled;
|
||||
auto const& maxMantissa = g.maxMantissa;
|
||||
|
||||
while (zm > maxMantissa || zm > kMaxRep)
|
||||
{
|
||||
@@ -834,19 +1022,12 @@ Number::operator*=(Number const& y)
|
||||
|
||||
xm = static_cast<internalrep>(zm);
|
||||
xe = ze;
|
||||
g.doRoundUp(
|
||||
zn,
|
||||
xm,
|
||||
xe,
|
||||
minMantissa,
|
||||
maxMantissa,
|
||||
cuspRoundingFixEnabled,
|
||||
"Number::multiplication overflow : exponent is " + std::to_string(xe));
|
||||
g.doRoundUp(zn, xm, xe, "Number::multiplication overflow : exponent is " + std::to_string(xe));
|
||||
negative_ = zn;
|
||||
mantissa_ = xm;
|
||||
exponent_ = xe;
|
||||
|
||||
normalize(range);
|
||||
normalize(g);
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -882,7 +1063,7 @@ Number::operator/=(Number const& y)
|
||||
auto const& range = kRange.get();
|
||||
auto const& minMantissa = range.min;
|
||||
auto const& maxMantissa = range.max;
|
||||
auto const cuspRoundingFixEnabled = range.cuspRoundingFixEnabled;
|
||||
auto const cuspRoundingFix = range.cuspRoundingFix;
|
||||
|
||||
// Division operates on two large integers (16-digit for small
|
||||
// mantissas, 19-digit for large) using integer math. If the values
|
||||
@@ -930,7 +1111,7 @@ Number::operator/=(Number const& y)
|
||||
// This is equivalent to if we had used an initial factor of 10^22,
|
||||
// a couple digits more than we actually need.
|
||||
//
|
||||
// Stage 3: If there is still a remainder, and the CuspRoundingFix
|
||||
// Stage 3: If there is still a remainder, and the cuspRoundingFix
|
||||
// is enabled, pass a flag indicating such to doNormalize. The Guard
|
||||
// in doNormalize will treat that flag as if non-zero digits had
|
||||
// been dropped from the mantissa when shrinking it into range.
|
||||
@@ -1014,14 +1195,14 @@ Number::operator/=(Number const& y)
|
||||
// rounding fix is enabled, flag if there is still
|
||||
// a remainder from stage 2.
|
||||
bool const useTrailingRemainder =
|
||||
cuspRoundingFixEnabled == MantissaRange::CuspRoundingFix::Enabled;
|
||||
cuspRoundingFix != MantissaRange::CuspRoundingFix::Disabled;
|
||||
if (useTrailingRemainder)
|
||||
{
|
||||
dropped = partialNumerator % dm != 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
doNormalize(zp, zm, ze, minMantissa, maxMantissa, cuspRoundingFixEnabled, dropped);
|
||||
doNormalize(zp, zm, ze, minMantissa, maxMantissa, cuspRoundingFix, dropped);
|
||||
negative_ = zp;
|
||||
mantissa_ = static_cast<internalrep>(zm);
|
||||
exponent_ = ze;
|
||||
@@ -1035,7 +1216,7 @@ operator rep() const
|
||||
{
|
||||
rep drops = mantissa();
|
||||
int offset = exponent();
|
||||
Guard g;
|
||||
Guard g(kRange);
|
||||
if (drops != 0)
|
||||
{
|
||||
if (negative_)
|
||||
|
||||
@@ -39,22 +39,30 @@ setCurrentTransactionRules(std::optional<Rules> r)
|
||||
// Push the appropriate setting, instead of having the class pull every time
|
||||
// the value is needed. That could get expensive fast.
|
||||
|
||||
// If any new conditions with new amendments are added, those amendments must also be added to
|
||||
// useRulesGuards.
|
||||
bool const enableVaultNumbers =
|
||||
!r || (r->enabled(featureSingleAssetVault) || r->enabled(featureLendingProtocol));
|
||||
bool const enableCuspRoundingFix = !r || r->enabled(fixCleanup3_2_0);
|
||||
XRPL_ASSERT(
|
||||
!r || useRulesGuards(*r) == (enableCuspRoundingFix || enableVaultNumbers),
|
||||
"setCurrentTransactionRules : rule decisions match");
|
||||
|
||||
// Declare the range this way to keep clang-tidy from complaining
|
||||
auto const range = [enableCuspRoundingFix, enableVaultNumbers]() {
|
||||
if (enableVaultNumbers)
|
||||
auto const range = [&r]() {
|
||||
// If any new conditions with new amendments are added to "enableLargeNumbers", those
|
||||
// amendments must also be added to useRulesGuards.
|
||||
bool const enableLargeNumbers =
|
||||
!r || (r->enabled(featureSingleAssetVault) || r->enabled(featureLendingProtocol));
|
||||
// If enableLargeNumbers is true, then useRulesGuard must also return true.
|
||||
// However, the reverse is not true. Other amendments can cause the rules guard to be used,
|
||||
// even though large numbers are _not_ used.
|
||||
XRPL_ASSERT(
|
||||
!r || !enableLargeNumbers || useRulesGuards(*r),
|
||||
"setCurrentTransactionRules : rule decisions match");
|
||||
|
||||
if (enableLargeNumbers)
|
||||
{
|
||||
if (enableCuspRoundingFix)
|
||||
static_assert(
|
||||
MantissaRange::MantissaScale::Large == MantissaRange::MantissaScale::Large330);
|
||||
if (!r || r->enabled(fixCleanup3_3_0))
|
||||
{
|
||||
return MantissaRange::MantissaScale::Large;
|
||||
return MantissaRange::MantissaScale::Large330;
|
||||
}
|
||||
if (r->enabled(fixCleanup3_2_0))
|
||||
{
|
||||
return MantissaRange::MantissaScale::Large320;
|
||||
}
|
||||
return MantissaRange::MantissaScale::LargeLegacy;
|
||||
}
|
||||
@@ -69,14 +77,14 @@ bool
|
||||
useRulesGuards(Rules const& rules)
|
||||
{
|
||||
// The list of amendments used here - to decide whether to create a RulesGuard - must be a
|
||||
// superset of the list used to figure out which mantissa scale to use in
|
||||
// setCurrentTransactionRules. Additional amendments can be added if desired.
|
||||
// superset of the list used to determine "enableLargeNumbers" in setCurrentTransactionRules.
|
||||
// Additional amendments can be added if desired.
|
||||
//
|
||||
// As soon as any one of these amendments is retired, this whole function can be removed, along
|
||||
// with createGuards, and any other callers, and the first set of guards can be created directly
|
||||
// at the call site, without using optional.
|
||||
return rules.enabled(fixCleanup3_2_0) || rules.enabled(featureSingleAssetVault) ||
|
||||
rules.enabled(featureLendingProtocol);
|
||||
return rules.enabled(featureSingleAssetVault) || rules.enabled(featureLendingProtocol) ||
|
||||
rules.enabled(fixCleanup3_2_0) || rules.enabled(fixCleanup3_3_0);
|
||||
}
|
||||
|
||||
void
|
||||
@@ -87,7 +95,8 @@ createGuards(
|
||||
{
|
||||
if (useRulesGuards(rules))
|
||||
{
|
||||
// raii classes for the current ledger rules.
|
||||
// raii classes for the current ledger rules. If the rules are set, the MantissaRange will
|
||||
// be updated, too.
|
||||
rulesGuard.emplace(rules);
|
||||
}
|
||||
else
|
||||
|
||||
@@ -88,7 +88,6 @@ STNumber::add(Serializer& s) const
|
||||
}
|
||||
else
|
||||
{
|
||||
#if !NDEBUG
|
||||
// There are circumstances where an already-rounded Number is
|
||||
// serialized without being touched by a transactor, and thus
|
||||
// without an asset. We can't know if it's rounded, because it could
|
||||
@@ -96,11 +95,9 @@ STNumber::add(Serializer& s) const
|
||||
// Json. Regardless, the only time we should be serializing an
|
||||
// STNumber is when the scale is large.
|
||||
XRPL_ASSERT_PARTS(
|
||||
Number::getMantissaScale() == MantissaRange::MantissaScale::LargeLegacy ||
|
||||
Number::getMantissaScale() == MantissaRange::MantissaScale::Large,
|
||||
Number::getMantissaScale() != MantissaRange::MantissaScale::Small,
|
||||
"xrpl::STNumber::add",
|
||||
"STNumber only used with large mantissa scale");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5059,11 +5059,10 @@ class Invariants_test : public beast::unit_test::Suite
|
||||
std::vector<ValidVault::DeltaInfo> values;
|
||||
};
|
||||
|
||||
for (auto const mantissaScale : {
|
||||
MantissaRange::MantissaScale::LargeLegacy,
|
||||
MantissaRange::MantissaScale::Large,
|
||||
})
|
||||
for (auto const mantissaScale : MantissaRange::getAllScales())
|
||||
{
|
||||
if (mantissaScale == MantissaRange::MantissaScale::Small)
|
||||
continue;
|
||||
NumberMantissaScaleGuard const g{mantissaScale};
|
||||
|
||||
auto makeDelta = [&vaultAsset](Number const& n) -> ValidVault::DeltaInfo {
|
||||
|
||||
@@ -46,6 +46,22 @@ class Number_test : public beast::unit_test::Suite
|
||||
return out;
|
||||
}
|
||||
|
||||
BigInt
|
||||
toBigInt(Number const& n)
|
||||
{
|
||||
BigInt v = n.mantissa();
|
||||
auto e = n.exponent();
|
||||
|
||||
for (; e > 0; --e)
|
||||
v *= 10;
|
||||
for (; e < 0; ++e)
|
||||
{
|
||||
BEAST_EXPECT(v % 10 == 0);
|
||||
v /= 10;
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
using dec = boost::multiprecision::cpp_dec_float_50;
|
||||
|
||||
template <class T = dec>
|
||||
@@ -172,28 +188,35 @@ public:
|
||||
auto const scale = Number::getMantissaScale();
|
||||
testcase << "test_add " << to_string(scale);
|
||||
|
||||
using Case = std::tuple<Number, Number, Number>;
|
||||
auto const cSmall = std::to_array<Case>(
|
||||
{{Number{1'000'000'000'000'000, -15},
|
||||
Number{6'555'555'555'555'555, -29},
|
||||
Number{1'000'000'000'000'066, -15}},
|
||||
{Number{-1'000'000'000'000'000, -15},
|
||||
Number{-6'555'555'555'555'555, -29},
|
||||
Number{-1'000'000'000'000'066, -15}},
|
||||
{Number{-1'000'000'000'000'000, -15},
|
||||
Number{6'555'555'555'555'555, -29},
|
||||
Number{-9'999'999'999'999'344, -16}},
|
||||
{Number{-6'555'555'555'555'555, -29},
|
||||
Number{1'000'000'000'000'000, -15},
|
||||
Number{9'999'999'999'999'344, -16}},
|
||||
{Number{}, Number{5}, Number{5}},
|
||||
{Number{5}, Number{}, Number{5}},
|
||||
{Number{5'555'555'555'555'555, -32768},
|
||||
Number{-5'555'555'555'555'554, -32768},
|
||||
Number{0}},
|
||||
{Number{-9'999'999'999'999'999, -31},
|
||||
Number{1'000'000'000'000'000, -15},
|
||||
Number{9'999'999'999'999'990, -16}}});
|
||||
using Case = std::tuple<Number, Number, Number, int>;
|
||||
auto const cSmall = std::to_array<Case>({
|
||||
{Number{1'000'000'000'000'000, -15},
|
||||
Number{6'555'555'555'555'555, -29},
|
||||
Number{1'000'000'000'000'066, -15},
|
||||
__LINE__},
|
||||
{Number{-1'000'000'000'000'000, -15},
|
||||
Number{-6'555'555'555'555'555, -29},
|
||||
Number{-1'000'000'000'000'066, -15},
|
||||
__LINE__},
|
||||
{Number{-1'000'000'000'000'000, -15},
|
||||
Number{6'555'555'555'555'555, -29},
|
||||
Number{-9'999'999'999'999'344, -16},
|
||||
__LINE__},
|
||||
{Number{-6'555'555'555'555'555, -29},
|
||||
Number{1'000'000'000'000'000, -15},
|
||||
Number{9'999'999'999'999'344, -16},
|
||||
__LINE__},
|
||||
{Number{}, Number{5}, Number{5}, __LINE__},
|
||||
{Number{5}, Number{}, Number{5}, __LINE__},
|
||||
{Number{5'555'555'555'555'555, -32768},
|
||||
Number{-5'555'555'555'555'554, -32768},
|
||||
Number{0},
|
||||
__LINE__},
|
||||
{Number{-9'999'999'999'999'999, -31},
|
||||
Number{1'000'000'000'000'000, -15},
|
||||
Number{9'999'999'999'999'990, -16},
|
||||
__LINE__},
|
||||
});
|
||||
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
|
||||
@@ -201,45 +224,57 @@ public:
|
||||
{
|
||||
{Number{1'000'000'000'000'000, -15},
|
||||
Number{6'555'555'555'555'555, -29},
|
||||
Number{1'000'000'000'000'065'556, -18}},
|
||||
Number{1'000'000'000'000'065'556, -18},
|
||||
__LINE__},
|
||||
{Number{-1'000'000'000'000'000, -15},
|
||||
Number{-6'555'555'555'555'555, -29},
|
||||
Number{-1'000'000'000'000'065'556, -18}},
|
||||
Number{-1'000'000'000'000'065'556, -18},
|
||||
__LINE__},
|
||||
{Number{-1'000'000'000'000'000, -15},
|
||||
Number{6'555'555'555'555'555, -29},
|
||||
Number{true, 9'999'999'999'999'344'444ULL, -19, Number::Normalized{}}},
|
||||
Number{true, 9'999'999'999'999'344'444ULL, -19, Number::Normalized{}},
|
||||
__LINE__},
|
||||
{Number{-6'555'555'555'555'555, -29},
|
||||
Number{1'000'000'000'000'000, -15},
|
||||
Number{false, 9'999'999'999'999'344'444ULL, -19, Number::Normalized{}}},
|
||||
{Number{}, Number{5}, Number{5}},
|
||||
{Number{5}, Number{}, Number{5}},
|
||||
Number{false, 9'999'999'999'999'344'444ULL, -19, Number::Normalized{}},
|
||||
__LINE__},
|
||||
{Number{}, Number{5}, Number{5}, __LINE__},
|
||||
{Number{5}, Number{}, Number{5}, __LINE__},
|
||||
{Number{5'555'555'555'555'555'000, -32768},
|
||||
Number{-5'555'555'555'555'554'000, -32768},
|
||||
Number{0}},
|
||||
Number{0},
|
||||
__LINE__},
|
||||
{Number{-9'999'999'999'999'999, -31},
|
||||
Number{1'000'000'000'000'000, -15},
|
||||
Number{9'999'999'999'999'990, -16}},
|
||||
Number{9'999'999'999'999'990, -16},
|
||||
__LINE__},
|
||||
// Items from cSmall expanded for the larger mantissa
|
||||
{Number{1'000'000'000'000'000'000, -18},
|
||||
Number{6'555'555'555'555'555'555, -35},
|
||||
Number{1'000'000'000'000'000'066, -18}},
|
||||
Number{1'000'000'000'000'000'066, -18},
|
||||
__LINE__},
|
||||
{Number{-1'000'000'000'000'000'000, -18},
|
||||
Number{-6'555'555'555'555'555'555, -35},
|
||||
Number{-1'000'000'000'000'000'066, -18}},
|
||||
Number{-1'000'000'000'000'000'066, -18},
|
||||
__LINE__},
|
||||
{Number{-1'000'000'000'000'000'000, -18},
|
||||
Number{6'555'555'555'555'555'555, -35},
|
||||
Number{true, 9'999'999'999'999'999'344ULL, -19, Number::Normalized{}}},
|
||||
Number{true, 9'999'999'999'999'999'344ULL, -19, Number::Normalized{}},
|
||||
__LINE__},
|
||||
{Number{-6'555'555'555'555'555'555, -35},
|
||||
Number{1'000'000'000'000'000'000, -18},
|
||||
Number{false, 9'999'999'999'999'999'344ULL, -19, Number::Normalized{}}},
|
||||
{Number{}, Number{5}, Number{5}},
|
||||
Number{false, 9'999'999'999'999'999'344ULL, -19, Number::Normalized{}},
|
||||
__LINE__},
|
||||
{Number{}, Number{5}, Number{5}, __LINE__},
|
||||
{Number{5'555'555'555'555'555'555, -32768},
|
||||
Number{-5'555'555'555'555'555'554, -32768},
|
||||
Number{0}},
|
||||
Number{0},
|
||||
__LINE__},
|
||||
{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::kMaxRep - 1}, Number{1, 0}, Number{Number::kMaxRep}},
|
||||
Number{false, 9'999'999'999'999'999'990ULL, -19, Number::Normalized{}},
|
||||
__LINE__},
|
||||
{Number{Number::kMaxRep - 1}, Number{1, 0}, Number{Number::kMaxRep}, __LINE__},
|
||||
// Test extremes
|
||||
{
|
||||
// Each Number operand rounds up, so the actual mantissa is
|
||||
@@ -247,6 +282,7 @@ public:
|
||||
Number{false, 9'999'999'999'999'999'999ULL, 0, Number::Normalized{}},
|
||||
Number{false, 9'999'999'999'999'999'999ULL, 0, Number::Normalized{}},
|
||||
Number{2, 19},
|
||||
__LINE__,
|
||||
},
|
||||
{
|
||||
// Does not round. Mantissas are going to be > kMaxRep, so if
|
||||
@@ -257,21 +293,25 @@ public:
|
||||
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{}},
|
||||
__LINE__,
|
||||
},
|
||||
});
|
||||
auto const cLargeLegacy = std::to_array<Case>({
|
||||
{Number{Number::kMaxRep}, Number{6, -1}, Number{Number::kMaxRep / 10, 1}},
|
||||
{Number{Number::kMaxRep}, Number{6, -1}, Number{Number::kMaxRep / 10, 1}, __LINE__},
|
||||
});
|
||||
auto const cLargeCorrected = std::to_array<Case>({
|
||||
{Number{Number::kMaxRep}, Number{6, -1}, Number{(Number::kMaxRep / 10) + 1, 1}},
|
||||
{Number{Number::kMaxRep},
|
||||
Number{6, -1},
|
||||
Number{(Number::kMaxRep / 10) + 1, 1},
|
||||
__LINE__},
|
||||
});
|
||||
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;
|
||||
std::stringstream ss;
|
||||
ss << x << " + " << y << " = " << result << ". Expected: " << z;
|
||||
BEAST_EXPECTS(result == z, ss.str());
|
||||
expect(result == z, ss.str(), __FILE__, line);
|
||||
}
|
||||
};
|
||||
if (scale == MantissaRange::MantissaScale::Small)
|
||||
@@ -311,21 +351,28 @@ public:
|
||||
auto const scale = Number::getMantissaScale();
|
||||
testcase << "test_sub " << to_string(scale);
|
||||
|
||||
using Case = std::tuple<Number, Number, Number>;
|
||||
using Case = std::tuple<Number, Number, Number, int>;
|
||||
auto const cSmall = std::to_array<Case>(
|
||||
{{Number{1'000'000'000'000'000, -15},
|
||||
Number{6'555'555'555'555'555, -29},
|
||||
Number{9'999'999'999'999'344, -16}},
|
||||
Number{9'999'999'999'999'344, -16},
|
||||
__LINE__},
|
||||
{Number{6'555'555'555'555'555, -29},
|
||||
Number{1'000'000'000'000'000, -15},
|
||||
Number{-9'999'999'999'999'344, -16}},
|
||||
{Number{1'000'000'000'000'000, -15}, Number{1'000'000'000'000'000, -15}, Number{0}},
|
||||
Number{-9'999'999'999'999'344, -16},
|
||||
__LINE__},
|
||||
{Number{1'000'000'000'000'000, -15},
|
||||
Number{1'000'000'000'000'000, -15},
|
||||
Number{0},
|
||||
__LINE__},
|
||||
{Number{1'000'000'000'000'000, -15},
|
||||
Number{1'000'000'000'000'001, -15},
|
||||
Number{-1'000'000'000'000'000, -30}},
|
||||
Number{-1'000'000'000'000'000, -30},
|
||||
__LINE__},
|
||||
{Number{1'000'000'000'000'001, -15},
|
||||
Number{1'000'000'000'000'000, -15},
|
||||
Number{1'000'000'000'000'000, -30}}});
|
||||
Number{1'000'000'000'000'000, -30},
|
||||
__LINE__}});
|
||||
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
|
||||
@@ -333,49 +380,63 @@ public:
|
||||
{
|
||||
{Number{1'000'000'000'000'000, -15},
|
||||
Number{6'555'555'555'555'555, -29},
|
||||
Number{false, 9'999'999'999'999'344'444ULL, -19, Number::Normalized{}}},
|
||||
Number{false, 9'999'999'999'999'344'444ULL, -19, Number::Normalized{}},
|
||||
__LINE__},
|
||||
{Number{6'555'555'555'555'555, -29},
|
||||
Number{1'000'000'000'000'000, -15},
|
||||
Number{true, 9'999'999'999'999'344'444ULL, -19, Number::Normalized{}}},
|
||||
{Number{1'000'000'000'000'000, -15}, Number{1'000'000'000'000'000, -15}, Number{0}},
|
||||
Number{true, 9'999'999'999'999'344'444ULL, -19, Number::Normalized{}},
|
||||
__LINE__},
|
||||
{Number{1'000'000'000'000'000, -15},
|
||||
Number{1'000'000'000'000'000, -15},
|
||||
Number{0},
|
||||
__LINE__},
|
||||
{Number{1'000'000'000'000'000, -15},
|
||||
Number{1'000'000'000'000'001, -15},
|
||||
Number{-1'000'000'000'000'000, -30}},
|
||||
Number{-1'000'000'000'000'000, -30},
|
||||
__LINE__},
|
||||
{Number{1'000'000'000'000'001, -15},
|
||||
Number{1'000'000'000'000'000, -15},
|
||||
Number{1'000'000'000'000'000, -30}},
|
||||
Number{1'000'000'000'000'000, -30},
|
||||
__LINE__},
|
||||
// Items from cSmall expanded for the larger mantissa
|
||||
{Number{1'000'000'000'000'000'000, -18},
|
||||
Number{6'555'555'555'555'555'555, -32},
|
||||
Number{false, 9'999'999'999'999'344'444ULL, -19, Number::Normalized{}}},
|
||||
Number{false, 9'999'999'999'999'344'444ULL, -19, Number::Normalized{}},
|
||||
__LINE__},
|
||||
{Number{6'555'555'555'555'555'555, -32},
|
||||
Number{1'000'000'000'000'000'000, -18},
|
||||
Number{true, 9'999'999'999'999'344'444ULL, -19, Number::Normalized{}}},
|
||||
Number{true, 9'999'999'999'999'344'444ULL, -19, Number::Normalized{}},
|
||||
__LINE__},
|
||||
{Number{1'000'000'000'000'000'000, -18},
|
||||
Number{1'000'000'000'000'000'000, -18},
|
||||
Number{0}},
|
||||
Number{0},
|
||||
__LINE__},
|
||||
{Number{1'000'000'000'000'000'000, -18},
|
||||
Number{1'000'000'000'000'000'001, -18},
|
||||
Number{-1'000'000'000'000'000'000, -36}},
|
||||
Number{-1'000'000'000'000'000'000, -36},
|
||||
__LINE__},
|
||||
{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::kMaxRep}, Number{6, -1}, Number{Number::kMaxRep - 1}},
|
||||
Number{1'000'000'000'000'000'000, -36},
|
||||
__LINE__},
|
||||
{Number{Number::kMaxRep}, Number{6, -1}, Number{Number::kMaxRep - 1}, __LINE__},
|
||||
{Number{false, Number::kMaxRep + 1, 0, Number::Normalized{}},
|
||||
Number{1, 0},
|
||||
Number{(Number::kMaxRep / 10) + 1, 1}},
|
||||
Number{(Number::kMaxRep / 10) + 1, 1},
|
||||
__LINE__},
|
||||
{Number{false, Number::kMaxRep + 1, 0, Number::Normalized{}},
|
||||
Number{3, 0},
|
||||
Number{Number::kMaxRep}},
|
||||
{power(2, 63), Number{3, 0}, Number{Number::kMaxRep}},
|
||||
Number{Number::kMaxRep},
|
||||
__LINE__},
|
||||
{power(2, 63), Number{3, 0}, Number{Number::kMaxRep}, __LINE__},
|
||||
});
|
||||
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;
|
||||
std::stringstream ss;
|
||||
ss << x << " - " << y << " = " << result << ". Expected: " << z;
|
||||
BEAST_EXPECTS(result == z, ss.str());
|
||||
expect(result == z, ss.str(), __FILE__, line);
|
||||
}
|
||||
};
|
||||
if (scale == MantissaRange::MantissaScale::Small)
|
||||
@@ -1340,8 +1401,7 @@ public:
|
||||
"9223372036854775e3");
|
||||
}
|
||||
break;
|
||||
case MantissaRange::MantissaScale::LargeLegacy:
|
||||
case MantissaRange::MantissaScale::Large:
|
||||
default:
|
||||
// Test the edges
|
||||
// ((exponent < -(28)) || (exponent > -(8)))))
|
||||
test(Number::min(), "1e-32750");
|
||||
@@ -1379,9 +1439,6 @@ public:
|
||||
test(
|
||||
-(Number{std::numeric_limits<std::int64_t>::max(), 0} + 1),
|
||||
"-9223372036854775810");
|
||||
break;
|
||||
default:
|
||||
BEAST_EXPECT(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1739,9 +1796,7 @@ public:
|
||||
BigInt const exactProduct = BigInt(kAValue) * BigInt(kBValue);
|
||||
|
||||
// What Number actually stored.
|
||||
BigInt storedValue = BigInt(product.mantissa());
|
||||
for (int i = 0; i < product.exponent(); ++i)
|
||||
storedValue *= 10;
|
||||
BigInt const storedValue = toBigInt(product);
|
||||
|
||||
BigInt const signedDifference = storedValue - exactProduct;
|
||||
|
||||
@@ -1758,7 +1813,8 @@ public:
|
||||
|
||||
switch (scale)
|
||||
{
|
||||
case MantissaRange::MantissaScale::Large:
|
||||
case MantissaRange::MantissaScale::Large320:
|
||||
case MantissaRange::MantissaScale::Large330:
|
||||
BEAST_EXPECT(signedDifference >= 0);
|
||||
BEAST_EXPECT(signedDifference < pow10<BigInt>(product.exponent()));
|
||||
BEAST_EXPECT(
|
||||
@@ -1840,7 +1896,8 @@ public:
|
||||
// Upward invariant: stored >= exact. Bug: stored < exact.
|
||||
switch (scale)
|
||||
{
|
||||
case MantissaRange::MantissaScale::Large:
|
||||
case MantissaRange::MantissaScale::Large320:
|
||||
case MantissaRange::MantissaScale::Large330:
|
||||
BEAST_EXPECT(stored >= exact);
|
||||
BEAST_EXPECT(diff < pow10(quotient.exponent()));
|
||||
break;
|
||||
@@ -1890,7 +1947,8 @@ public:
|
||||
// invariant: stored <= exact. Bug: stored > exact.
|
||||
switch (scale)
|
||||
{
|
||||
case MantissaRange::MantissaScale::Large:
|
||||
case MantissaRange::MantissaScale::Large320:
|
||||
case MantissaRange::MantissaScale::Large330:
|
||||
BEAST_EXPECT(stored <= exact);
|
||||
BEAST_EXPECT(diff > -pow10(quotient.exponent()));
|
||||
break;
|
||||
@@ -1947,7 +2005,8 @@ public:
|
||||
// invariant: stored >= exact. Bug: stored < exact.
|
||||
switch (scale)
|
||||
{
|
||||
case MantissaRange::MantissaScale::Large:
|
||||
case MantissaRange::MantissaScale::Large320:
|
||||
case MantissaRange::MantissaScale::Large330:
|
||||
BEAST_EXPECT(stored >= exact);
|
||||
BEAST_EXPECT(diff < pow10(quotient.exponent()));
|
||||
break;
|
||||
@@ -1964,6 +2023,340 @@ public:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
auto const exp = Number::mantissaLog();
|
||||
// SubCase is <offset, extraB, aString, bString>
|
||||
// * offset: offset from exp
|
||||
// * extraB: whether to include 1e"exp" in "b"
|
||||
// * aString: expected string value for "a"
|
||||
// * bString: expected string value for "b"
|
||||
// There aren't too many valid combinations for test cases here. If extraB is true,
|
||||
// offset can really only be 2, because any larger and the mantissa can't be represented
|
||||
// without loss. Offset can't be less than 2, or there's no error.
|
||||
using SubCase = std::tuple<int, bool, std::string, std::string>;
|
||||
auto const c = std::to_array<SubCase>({
|
||||
{2,
|
||||
true,
|
||||
scale == MantissaRange::MantissaScale::Small ? "100000000000000000"
|
||||
: "100000000000000000000",
|
||||
scale == MantissaRange::MantissaScale::Small ? "-1000000000000001"
|
||||
: "-1000000000000000001"},
|
||||
{2,
|
||||
false,
|
||||
scale == MantissaRange::MantissaScale::Small ? "100000000000000000"
|
||||
: "100000000000000000000",
|
||||
"-1"},
|
||||
{30,
|
||||
false,
|
||||
scale == MantissaRange::MantissaScale::Small
|
||||
? "1000000000000000000000000000000000000000000000"
|
||||
: "1000000000000000000000000000000000000000000000000",
|
||||
"-1"},
|
||||
});
|
||||
for (auto const& [offset, extraB, aString, bString] : c)
|
||||
{
|
||||
testcase << "subtraction rounding. offset: " << offset
|
||||
<< ", scale: " << to_string(scale);
|
||||
|
||||
Number const a{1LL, exp + offset};
|
||||
Number const b{-((extraB ? Number{1, exp} : kNumZero) + 1)};
|
||||
|
||||
auto const bigA = toBigInt(a);
|
||||
auto const bigB = toBigInt(b);
|
||||
|
||||
BEAST_EXPECT(bigA == BigInt{aString});
|
||||
BEAST_EXPECT(bigB == BigInt{bString});
|
||||
|
||||
auto construct = [&a, &b, this](Number::RoundingMode r) {
|
||||
NumberRoundModeGuard const roundGuard{r};
|
||||
auto const sum = a + b;
|
||||
BigInt const stored = toBigInt(sum);
|
||||
return std::make_pair(r, std::make_pair(stored, sum));
|
||||
};
|
||||
|
||||
BigInt const exact = bigA + bigB;
|
||||
|
||||
auto const sums = [&]() {
|
||||
std::map<Number::RoundingMode, std::pair<BigInt, Number>> sums;
|
||||
sums.emplace(construct(Number::RoundingMode::TowardsZero));
|
||||
sums.emplace(construct(Number::RoundingMode::Upward));
|
||||
sums.emplace(construct(Number::RoundingMode::Downward));
|
||||
sums.emplace(construct(Number::RoundingMode::ToNearest));
|
||||
return sums;
|
||||
}();
|
||||
|
||||
log << "\n a = " << a << " (" << fmt(bigA)
|
||||
<< ")\n b = " << b << " (" << fmt(bigB)
|
||||
<< ")\n exact a + b = " << fmt(exact) << "\n";
|
||||
for (auto const& [r, sum] : sums)
|
||||
{
|
||||
auto const diff = sum.first - exact;
|
||||
auto const rLabel = to_string(r);
|
||||
log << std::string(15 - rLabel.length(), ' ') << rLabel << " = "
|
||||
<< fmt(sum.first) << "\n difference = " << fmt(diff) << "\n";
|
||||
}
|
||||
log.flush();
|
||||
|
||||
auto const expectedExponent =
|
||||
offset - (scale == MantissaRange::MantissaScale::Small && extraB ? 1 : 0);
|
||||
auto const epsilon = pow10<BigInt>(expectedExponent);
|
||||
for (auto const& [r, sum] : sums)
|
||||
{
|
||||
auto diff = sum.first - exact;
|
||||
auto const rLabel = to_string(r);
|
||||
switch (scale)
|
||||
{
|
||||
case MantissaRange::MantissaScale::Small:
|
||||
case MantissaRange::MantissaScale::LargeLegacy:
|
||||
case MantissaRange::MantissaScale::Large320: {
|
||||
// Without the fix, all the results but one round up
|
||||
if (r == Number::RoundingMode::Downward)
|
||||
{
|
||||
// Downward works because the Guard sign is negative, and Downward
|
||||
// returns Up instead of Down if negative and there's a remainder,
|
||||
// whereas TowardsZero always returns Down.
|
||||
BEAST_EXPECTS(sum.first < exact, rLabel);
|
||||
BEAST_EXPECTS(diff == -(epsilon - 1), rLabel);
|
||||
}
|
||||
else
|
||||
{
|
||||
BEAST_EXPECTS(sum.first > exact, rLabel);
|
||||
BEAST_EXPECTS(diff == 1, rLabel);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
BEAST_EXPECTS(
|
||||
sum.second.exponent() <= expectedExponent,
|
||||
to_string(sum.second.exponent()));
|
||||
switch (r)
|
||||
{
|
||||
case Number::RoundingMode::Upward:
|
||||
case Number::RoundingMode::ToNearest:
|
||||
BEAST_EXPECTS(sum.first > exact, rLabel);
|
||||
BEAST_EXPECTS(diff == 1, rLabel);
|
||||
break;
|
||||
default:
|
||||
BEAST_EXPECTS(sum.first < exact, rLabel);
|
||||
BEAST_EXPECTS(diff == -(epsilon - 1), rLabel);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testNumberAddDirectedSignWrong()
|
||||
{
|
||||
auto const scale = Number::getMantissaScale();
|
||||
testcase << "operator+ directed rounding wrong for equal-exponent negative sums "
|
||||
<< to_string(scale);
|
||||
{
|
||||
// Two negative numbers with the same exponent
|
||||
Number const a{-6, Number::mantissaLog()};
|
||||
Number const b{a - 3};
|
||||
BEAST_EXPECT(a.exponent() == b.exponent() && abs(b) > abs(a));
|
||||
|
||||
BigInt const exact = toBigInt(a) + toBigInt(b);
|
||||
if (scale == MantissaRange::MantissaScale::Small)
|
||||
{
|
||||
BEAST_EXPECT(exact == BigInt{"-12000000000000003"});
|
||||
}
|
||||
else
|
||||
{
|
||||
BEAST_EXPECT(exact == BigInt{"-12000000000000000003"});
|
||||
}
|
||||
|
||||
Number down, up;
|
||||
{
|
||||
NumberRoundModeGuard const g{Number::RoundingMode::Downward};
|
||||
down = a + b;
|
||||
}
|
||||
{
|
||||
NumberRoundModeGuard const g{Number::RoundingMode::Upward};
|
||||
up = a + b;
|
||||
}
|
||||
|
||||
auto const valueDown = toBigInt(down);
|
||||
auto const valueUp = toBigInt(up);
|
||||
log << " exact = " << fmt(exact) << "\n downward = " << fmt(valueDown)
|
||||
<< " (correct rounding: <= exact)"
|
||||
<< "\n upward = " << fmt(valueUp) << " (correct rounding: >= exact)\n\n";
|
||||
log.flush();
|
||||
|
||||
if (scale == MantissaRange::MantissaScale::Large330)
|
||||
{
|
||||
BEAST_EXPECT(valueDown <= exact); // Downward should round away from zero
|
||||
BEAST_EXPECT(valueUp >= exact); // Upward should round toward 0
|
||||
}
|
||||
else
|
||||
{
|
||||
BEAST_EXPECT(valueDown > exact); // Downward rounded toward zero (too high)
|
||||
BEAST_EXPECT(valueUp < exact); // Upward rounded toward -inf (too low)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// Positive control: the same magnitudes with a positive result round
|
||||
Number const pa{6, Number::mantissaLog()};
|
||||
Number const pb{pa + 3};
|
||||
BEAST_EXPECT(pa.exponent() == pb.exponent() && abs(pb) > abs(pa));
|
||||
BigInt const pexact = toBigInt(pa) + toBigInt(pb); // 12'000'000'000'000'000'003
|
||||
|
||||
Number pdown, pup;
|
||||
{
|
||||
NumberRoundModeGuard const g{Number::RoundingMode::Downward};
|
||||
pdown = pa + pb;
|
||||
}
|
||||
{
|
||||
NumberRoundModeGuard const g{Number::RoundingMode::Upward};
|
||||
pup = pa + pb;
|
||||
}
|
||||
auto const valuePDown = toBigInt(pdown);
|
||||
auto const valuePUp = toBigInt(pup);
|
||||
log << " exact = " << fmt(pexact) << "\n downward = " << fmt(valuePDown)
|
||||
<< " (correct rounding: <= exact)"
|
||||
<< "\n upward = " << fmt(valuePUp) << " (correct rounding: >= exact)\n\n";
|
||||
log.flush();
|
||||
|
||||
BEAST_EXPECT(valuePDown <= pexact); // correct for positive results
|
||||
BEAST_EXPECT(valuePUp >= pexact);
|
||||
}
|
||||
|
||||
{
|
||||
// Mixed sign numbers with the same exponent: negative second value
|
||||
Number const a{1, Number::mantissaLog()};
|
||||
Number const b{Number{-9, Number::mantissaLog()} - 3};
|
||||
BEAST_EXPECT(a.exponent() == b.exponent() && abs(b) > abs(a));
|
||||
|
||||
BigInt const exact = toBigInt(a) + toBigInt(b);
|
||||
if (scale == MantissaRange::MantissaScale::Small)
|
||||
{
|
||||
BEAST_EXPECT(exact == BigInt{"-8000000000000003"});
|
||||
}
|
||||
else
|
||||
{
|
||||
BEAST_EXPECT(exact == BigInt{"-8000000000000000003"});
|
||||
}
|
||||
|
||||
Number down, up;
|
||||
{
|
||||
NumberRoundModeGuard const g{Number::RoundingMode::Downward};
|
||||
down = a + b;
|
||||
}
|
||||
{
|
||||
NumberRoundModeGuard const g{Number::RoundingMode::Upward};
|
||||
up = a + b;
|
||||
}
|
||||
|
||||
auto const valueDown = toBigInt(down);
|
||||
auto const valueUp = toBigInt(up);
|
||||
log << " exact = " << fmt(exact) << "\n downward = " << fmt(valueDown)
|
||||
<< " (correct rounding: <= exact)"
|
||||
<< "\n upward = " << fmt(valueUp) << " (correct rounding: >= exact)\n\n";
|
||||
log.flush();
|
||||
|
||||
BEAST_EXPECT(valueDown <= exact); // Downward should round away from zero
|
||||
BEAST_EXPECT(valueUp >= exact); // Upward should round toward 0
|
||||
}
|
||||
|
||||
{
|
||||
// Mixed sign numbers with the same exponent: negative first value
|
||||
Number const a{-1, Number::mantissaLog()};
|
||||
Number const b{Number{9, Number::mantissaLog()} + 3};
|
||||
BEAST_EXPECT(a.exponent() == b.exponent() && abs(b) > abs(a));
|
||||
|
||||
BigInt const exact = toBigInt(a) + toBigInt(b);
|
||||
if (scale == MantissaRange::MantissaScale::Small)
|
||||
{
|
||||
BEAST_EXPECT(exact == BigInt{"8000000000000003"});
|
||||
}
|
||||
else
|
||||
{
|
||||
BEAST_EXPECT(exact == BigInt{"8000000000000000003"});
|
||||
}
|
||||
|
||||
Number down, up;
|
||||
{
|
||||
NumberRoundModeGuard const g{Number::RoundingMode::Downward};
|
||||
down = a + b;
|
||||
}
|
||||
{
|
||||
NumberRoundModeGuard const g{Number::RoundingMode::Upward};
|
||||
up = a + b;
|
||||
}
|
||||
|
||||
auto const valueDown = toBigInt(down);
|
||||
auto const valueUp = toBigInt(up);
|
||||
log << " exact = " << fmt(exact) << "\n downward = " << fmt(valueDown)
|
||||
<< " (correct rounding: <= exact)"
|
||||
<< "\n upward = " << fmt(valueUp) << " (correct rounding: >= exact)\n\n";
|
||||
log.flush();
|
||||
|
||||
BEAST_EXPECT(valueDown <= exact); // Downward should round away from zero
|
||||
BEAST_EXPECT(valueUp >= exact); // Upward should round toward 0
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testNumberAddToNearestPicksFarther()
|
||||
{
|
||||
auto const scale = Number::getMantissaScale();
|
||||
|
||||
// Case is <y, expected q>
|
||||
using Case = std::pair<Number, std::int64_t>;
|
||||
|
||||
auto const c = std::to_array<Case>({
|
||||
{Number{5'175'909'259'972'499'745LL, 22}, -1'074'951'375'311'646'003},
|
||||
{Number{1}, -1'074'956'551'220'905'975},
|
||||
{Number{1, 10}, -1'074'956'551'220'905'975},
|
||||
{Number{1, 20}, -1'074'956'551'220'905'975},
|
||||
{Number{1, 27}, -1'074'956'551'220'905'975},
|
||||
{Number{1, 28}, -1'074'956'551'220'905'974},
|
||||
{Number{1, 31}, -1'074'956'551'220'904'975},
|
||||
});
|
||||
|
||||
testcase << "operator+ ToNearest picks farther representable in cancellation "
|
||||
<< to_string(scale);
|
||||
|
||||
for (auto const& [y, expectedQ] : c)
|
||||
{
|
||||
NumberRoundModeGuard const roundGuard{Number::RoundingMode::ToNearest};
|
||||
|
||||
Number const x{-1'074'956'551'220'905'975LL, 28};
|
||||
Number const res = x + y;
|
||||
|
||||
BigInt const exact = toBigInt(x) + toBigInt(y);
|
||||
BigInt const vres = toBigInt(res);
|
||||
|
||||
BigInt ulp = 1;
|
||||
for (int i = 0; i < res.exponent(); ++i)
|
||||
ulp *= 10;
|
||||
|
||||
BigInt const q = (exact - ulp / 2) / ulp;
|
||||
Number const normalizedExact{static_cast<std::int64_t>(q), res.exponent()};
|
||||
BigInt const norm = toBigInt(normalizedExact);
|
||||
|
||||
log << " x = " << x << "\n y = " << y
|
||||
<< "\n exact = " << fmt(exact)
|
||||
<< "\n result (x + y) = " << fmt(vres)
|
||||
<< "\n normalize(exact) = " << fmt(norm) << "\n\n";
|
||||
log.flush();
|
||||
|
||||
if (scale == MantissaRange::MantissaScale::Small)
|
||||
{
|
||||
auto const comp = toBigInt(Number{expectedQ, -3});
|
||||
BEAST_EXPECTS(q == comp, fmt(q) + " != " + fmt(comp));
|
||||
}
|
||||
else
|
||||
{
|
||||
BEAST_EXPECTS(q == expectedQ, fmt(q) + " != " + fmt(BigInt(expectedQ)));
|
||||
}
|
||||
BEAST_EXPECT(normalizedExact == res);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
@@ -1972,6 +2365,7 @@ public:
|
||||
for (auto const scale : MantissaRange::getAllScales())
|
||||
{
|
||||
NumberMantissaScaleGuard const sg(scale);
|
||||
|
||||
testZero();
|
||||
testLimits();
|
||||
testToString();
|
||||
@@ -1995,6 +2389,8 @@ public:
|
||||
testInt64();
|
||||
|
||||
testUpwardRoundsDown();
|
||||
testNumberAddDirectedSignWrong();
|
||||
testNumberAddToNearestPicksFarther();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user