mirror of
https://github.com/XRPLF/rippled.git
synced 2026-06-04 09:16:47 +00:00
Compare commits
73 Commits
gregtatcam
...
ximinez/nu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
84c9dbafbe | ||
|
|
51902cd6b4 | ||
|
|
c84939ccea | ||
|
|
9e8c3caef4 | ||
|
|
35bee87909 | ||
|
|
261508a0ec | ||
|
|
9f872f2179 | ||
|
|
d1af39d9dd | ||
|
|
f6a26ca34f | ||
|
|
c0569037f8 | ||
|
|
be9ae88d48 | ||
|
|
cd21d74538 | ||
|
|
2fdfd2b420 | ||
|
|
06a3f76ccd | ||
|
|
dadf4d737d | ||
|
|
7b66b42713 | ||
|
|
18ac8a0583 | ||
|
|
de2efa5cb9 | ||
|
|
8dcd88e83c | ||
|
|
5333422402 | ||
|
|
4ec049e727 | ||
|
|
ae9c72bb7c | ||
|
|
5abecb9fcb | ||
|
|
12670b0c3f | ||
|
|
1e7876a03c | ||
|
|
e851e80de0 | ||
|
|
a963035f76 | ||
|
|
8ab904de57 | ||
|
|
100ec464d9 | ||
|
|
e89e6f50e8 | ||
|
|
27456fa439 | ||
|
|
d6844311c0 | ||
|
|
fbee0349f5 | ||
|
|
84ca271d95 | ||
|
|
75dfc65f5f | ||
|
|
48b1716e6f | ||
|
|
4ab886bcbc | ||
|
|
7f64c337d8 | ||
|
|
61bdd6fb78 | ||
|
|
8e06e78f11 | ||
|
|
42fda85fbc | ||
|
|
3a4b92b050 | ||
|
|
aea19df3c1 | ||
|
|
8b56749ca3 | ||
|
|
71cf996fc6 | ||
|
|
4c7ea64b6c | ||
|
|
c8947c6f75 | ||
|
|
09ae5b719f | ||
|
|
09f2d06dd4 | ||
|
|
6964013941 | ||
|
|
70c6e01d7e | ||
|
|
ddfb7ee69c | ||
|
|
b69b9242e2 | ||
|
|
cd2fcf0a5e | ||
|
|
69656d6b67 | ||
|
|
46b946b22e | ||
|
|
ae03b30f29 | ||
|
|
4c7c019add | ||
|
|
47f30c913d | ||
|
|
d7d5b83f6d | ||
|
|
e22938d69f | ||
|
|
7c9a56ff24 | ||
|
|
5a40416673 | ||
|
|
30334cd1f4 | ||
|
|
5558e1b522 | ||
|
|
cd0f49a003 | ||
|
|
22d2703ce8 | ||
|
|
d03274b731 | ||
|
|
1b6047afe1 | ||
|
|
175a04160d | ||
|
|
b050c151f8 | ||
|
|
a2b21d75ce | ||
|
|
b40d2a8e7d |
@@ -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;
|
||||
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.
|
||||
*
|
||||
@@ -141,7 +147,7 @@ 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)};
|
||||
CuspRoundingFix const cuspRoundingFix{isCuspFixEnabled(scale)};
|
||||
|
||||
static MantissaRange const&
|
||||
getMantissaRange(MantissaScale scale);
|
||||
@@ -319,6 +325,9 @@ public:
|
||||
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);
|
||||
static constexpr internalrep kMaxRepUp =
|
||||
((static_cast<internalrep>(std::numeric_limits<rep>::max()) / 10) + 1) * 10;
|
||||
static_assert(kMaxRepUp == 9'223'372'036'854'775'810ULL);
|
||||
|
||||
// May need to make unchecked private
|
||||
struct Unchecked
|
||||
@@ -560,7 +569,7 @@ private:
|
||||
int& exponent,
|
||||
internalrep const& minMantissa,
|
||||
internalrep const& maxMantissa,
|
||||
MantissaRange::CuspRoundingFix cuspRoundingFixEnabled);
|
||||
MantissaRange::CuspRoundingFix cuspRoundingFix);
|
||||
|
||||
template <class T>
|
||||
friend void
|
||||
@@ -570,7 +579,7 @@ private:
|
||||
int& exponent,
|
||||
MantissaRange::rep const& minMantissa,
|
||||
MantissaRange::rep const& maxMantissa,
|
||||
MantissaRange::CuspRoundingFix cuspRoundingFixEnabled,
|
||||
MantissaRange::CuspRoundingFix cuspRoundingFix,
|
||||
bool dropped);
|
||||
|
||||
[[nodiscard]] bool
|
||||
|
||||
@@ -65,7 +65,7 @@ MantissaRange::getRanges()
|
||||
static_assert(kRange.log == 15);
|
||||
static_assert(kRange.min < Number::kMaxRep);
|
||||
static_assert(kRange.max < Number::kMaxRep);
|
||||
static_assert(kRange.cuspRoundingFixEnabled == CuspRoundingFix::Disabled);
|
||||
static_assert(kRange.cuspRoundingFix == CuspRoundingFix::Disabled);
|
||||
}
|
||||
{
|
||||
[[maybe_unused]]
|
||||
@@ -76,7 +76,7 @@ MantissaRange::getRanges()
|
||||
static_assert(kRange.log == 18);
|
||||
static_assert(kRange.min < Number::kMaxRep);
|
||||
static_assert(kRange.max > Number::kMaxRep);
|
||||
static_assert(kRange.cuspRoundingFixEnabled == CuspRoundingFix::Disabled);
|
||||
static_assert(kRange.cuspRoundingFix == CuspRoundingFix::Disabled);
|
||||
}
|
||||
{
|
||||
[[maybe_unused]]
|
||||
@@ -87,7 +87,7 @@ MantissaRange::getRanges()
|
||||
static_assert(kRange.log == 18);
|
||||
static_assert(kRange.min < Number::kMaxRep);
|
||||
static_assert(kRange.max > Number::kMaxRep);
|
||||
static_assert(kRange.cuspRoundingFixEnabled == CuspRoundingFix::Enabled);
|
||||
static_assert(kRange.cuspRoundingFix == CuspRoundingFix::Enabled);
|
||||
}
|
||||
return map;
|
||||
}();
|
||||
@@ -206,12 +206,6 @@ public:
|
||||
void
|
||||
doDropDigit(T& mantissa, int& exponent) noexcept;
|
||||
|
||||
// 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
|
||||
round() const noexcept;
|
||||
|
||||
// Modify the result to the correctly rounded value
|
||||
template <UnsignedMantissa T>
|
||||
void
|
||||
@@ -221,25 +215,51 @@ public:
|
||||
int& exponent,
|
||||
internalrep const& minMantissa,
|
||||
internalrep const& maxMantissa,
|
||||
MantissaRange::CuspRoundingFix cuspRoundingFixEnabled,
|
||||
MantissaRange::CuspRoundingFix cuspRoundingFix,
|
||||
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,
|
||||
internalrep const& minMantissa);
|
||||
|
||||
// Modify the result to the correctly rounded value
|
||||
void
|
||||
doRound(rep& drops, std::string location) const;
|
||||
doRound(rep& drops, MantissaRange::CuspRoundingFix cuspRoundingFix, std::string location);
|
||||
|
||||
private:
|
||||
template <class T>
|
||||
void
|
||||
pushOverflow(T const& mantissa, MantissaRange::CuspRoundingFix cuspRoundingFix);
|
||||
|
||||
enum class Round {
|
||||
Down = -1,
|
||||
Even = 0,
|
||||
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]]
|
||||
Round
|
||||
round() const noexcept;
|
||||
|
||||
void
|
||||
doPush(unsigned d) noexcept;
|
||||
|
||||
template <UnsignedMantissa T>
|
||||
void
|
||||
bringIntoRange(bool& negative, T& mantissa, int& exponent, internalrep const& minMantissa);
|
||||
bringIntoRange(
|
||||
bool& negative,
|
||||
T& mantissa,
|
||||
int& exponent,
|
||||
internalrep const& minMantissa,
|
||||
MantissaRange::CuspRoundingFix cuspRoundingFix);
|
||||
};
|
||||
|
||||
inline void
|
||||
@@ -310,45 +330,78 @@ Number::Guard::doDropDigit<uint128_t>(uint128_t& mantissa, int& exponent) noexce
|
||||
++exponent;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void
|
||||
Number::Guard::pushOverflow(T const& mantissa, MantissaRange::CuspRoundingFix cuspRoundingFix)
|
||||
{
|
||||
XRPL_ASSERT(mantissa <= kMaxRepUp, "xrpl::Number::Guard::doRoundUp : valid mantissa");
|
||||
if (cuspRoundingFix != MantissaRange::CuspRoundingFix::Disabled && mantissa > kMaxRep &&
|
||||
mantissa < kMaxRepUp)
|
||||
{
|
||||
// Special case rounding rules for the values between kMaxRep and kMaxRepUp.
|
||||
// Scale the spread between kMaxRep and kMaxRepUp from 0 to 9, and push it onto the guard as
|
||||
// if it was a digit that got removed, but don't remove it. This method is future-proof in
|
||||
// case the number of mantissa bits ever changes. Effects:
|
||||
// * For round to nearest
|
||||
// * if the mantissa is below the midpoint, it'll round "down" to kMaxRepUp
|
||||
// * if above the midpoint, it'll round "down" to kMaxRep
|
||||
// * if can never be exactly at the midpoint, because kMaxRepUp is always even, and
|
||||
// kMaxRep is always odd, so don't worry about it.
|
||||
// * For round upward, will round up to kMaxRepUp for positive values, down for negative.
|
||||
// * For round downward, does the opposite of upward.
|
||||
// * For round toward zero, always rounds down.
|
||||
auto constexpr spread = kMaxRepUp - kMaxRep;
|
||||
static_assert(spread < 10 && spread >= 0);
|
||||
|
||||
auto const diff = mantissa - kMaxRep;
|
||||
auto const digit = (diff * 10) / spread;
|
||||
XRPL_ASSERT(digit > 0 && digit < 10, "xrpld::Number::Guard::xxxx : valid overflow digit");
|
||||
|
||||
// Don't remove the digit from the mantissa, but add it to the guard as if it was.
|
||||
push(digit);
|
||||
}
|
||||
}
|
||||
|
||||
// Returns:
|
||||
// -1 if Guard is less than half
|
||||
// 0 if Guard is exactly half
|
||||
// 1 if Guard is greater than half
|
||||
int
|
||||
Number::Guard::round() const noexcept
|
||||
// Down if Guard is less than half
|
||||
// Even if Guard is exactly half
|
||||
// Up if Guard is greater than half
|
||||
Number::Guard::Round
|
||||
Number::Guard::round()
|
||||
const noexcept
|
||||
{
|
||||
auto mode = Number::getround();
|
||||
|
||||
if (mode == RoundingMode::TowardsZero)
|
||||
return -1;
|
||||
return Round::Down;
|
||||
|
||||
if (mode == RoundingMode::Downward)
|
||||
{
|
||||
if (sbit_)
|
||||
{
|
||||
if (digits_ > 0 || xbit_)
|
||||
return 1;
|
||||
return Round::Up;
|
||||
}
|
||||
return -1;
|
||||
return Round::Down;
|
||||
}
|
||||
|
||||
if (mode == RoundingMode::Upward)
|
||||
{
|
||||
if (sbit_)
|
||||
return -1;
|
||||
return Round::Down;
|
||||
if (digits_ > 0 || xbit_)
|
||||
return 1;
|
||||
return -1;
|
||||
return Round::Up;
|
||||
return Round::Down;
|
||||
}
|
||||
|
||||
// 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>
|
||||
@@ -357,16 +410,19 @@ Number::Guard::bringIntoRange(
|
||||
bool& negative,
|
||||
T& mantissa,
|
||||
int& exponent,
|
||||
internalrep const& minMantissa)
|
||||
internalrep const& minMantissa,
|
||||
MantissaRange::CuspRoundingFix cuspRoundingFix)
|
||||
{
|
||||
// Bring mantissa back into the minMantissa / maxMantissa range AFTER
|
||||
// rounding
|
||||
if (mantissa < minMantissa)
|
||||
if (mantissa < minMantissa &&
|
||||
(cuspRoundingFix == MantissaRange::CuspRoundingFix::Disabled || mantissa != 0))
|
||||
{
|
||||
mantissa *= 10;
|
||||
--exponent;
|
||||
}
|
||||
if (exponent < kMinExponent)
|
||||
if (exponent < kMinExponent ||
|
||||
(cuspRoundingFix != MantissaRange::CuspRoundingFix::Disabled && mantissa == 0))
|
||||
{
|
||||
static constexpr Number kZero = Number{};
|
||||
|
||||
@@ -384,16 +440,18 @@ Number::Guard::doRoundUp(
|
||||
int& exponent,
|
||||
internalrep const& minMantissa,
|
||||
internalrep const& maxMantissa,
|
||||
MantissaRange::CuspRoundingFix cuspRoundingFixEnabled,
|
||||
MantissaRange::CuspRoundingFix cuspRoundingFix,
|
||||
std::string location)
|
||||
{
|
||||
auto r = round();
|
||||
if (r == 1 || (r == 0 && (mantissa & 1) == 1))
|
||||
pushOverflow(mantissa, cuspRoundingFix);
|
||||
|
||||
auto const r = round();
|
||||
if (r == Round::Up || (r == Round::Even && (mantissa & 1) == 1))
|
||||
{
|
||||
auto const safeToIncrement = [&maxMantissa](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".
|
||||
@@ -409,6 +467,8 @@ Number::Guard::doRoundUp(
|
||||
// be impossible to recurse more than once, because once the mantissa is divided by
|
||||
// 10, it will be _well_ under maxMantissa and kMaxRep, so adding 1 will have no
|
||||
// chance of bringing it back over.
|
||||
if (cuspRoundingFix != MantissaRange::CuspRoundingFix::Disabled && mantissa > kMaxRep && mantissa < kMaxRepUp)
|
||||
mantissa = kMaxRepUp;
|
||||
doDropDigit(mantissa, exponent);
|
||||
XRPL_ASSERT_PARTS(
|
||||
safeToIncrement(mantissa),
|
||||
@@ -420,7 +480,7 @@ Number::Guard::doRoundUp(
|
||||
exponent,
|
||||
minMantissa,
|
||||
maxMantissa,
|
||||
cuspRoundingFixEnabled,
|
||||
cuspRoundingFix,
|
||||
location);
|
||||
return;
|
||||
}
|
||||
@@ -440,7 +500,11 @@ Number::Guard::doRoundUp(
|
||||
}
|
||||
}
|
||||
}
|
||||
bringIntoRange(negative, mantissa, exponent, minMantissa);
|
||||
else if (cuspRoundingFix != MantissaRange::CuspRoundingFix::Disabled && mantissa > kMaxRep)
|
||||
{
|
||||
mantissa = kMaxRep;
|
||||
}
|
||||
bringIntoRange(negative, mantissa, exponent, minMantissa, cuspRoundingFix);
|
||||
if (exponent > kMaxExponent)
|
||||
Throw<std::overflow_error>(std::string(location));
|
||||
}
|
||||
@@ -453,8 +517,10 @@ Number::Guard::doRoundDown(
|
||||
int& exponent,
|
||||
internalrep const& minMantissa)
|
||||
{
|
||||
// Do not pushOverflow here.
|
||||
|
||||
auto r = round();
|
||||
if (r == 1 || (r == 0 && (mantissa & 1) == 1))
|
||||
if (r == Round::Up || (r == Round::Even && (mantissa & 1) == 1))
|
||||
{
|
||||
--mantissa;
|
||||
if (mantissa < minMantissa)
|
||||
@@ -463,15 +529,17 @@ Number::Guard::doRoundDown(
|
||||
--exponent;
|
||||
}
|
||||
}
|
||||
bringIntoRange(negative, mantissa, exponent, minMantissa);
|
||||
bringIntoRange(negative, mantissa, exponent, minMantissa, cuspRoundingFix);
|
||||
}
|
||||
|
||||
// Modify the result to the correctly rounded value
|
||||
void
|
||||
Number::Guard::doRound(rep& drops, std::string location) const
|
||||
Number::Guard::doRound(rep& drops, MantissaRange::CuspRoundingFix cuspRoundingFix, std::string location)
|
||||
{
|
||||
pushOverflow(drops, cuspRoundingFix);
|
||||
|
||||
auto r = round();
|
||||
if (r == 1 || (r == 0 && (drops & 1) == 1))
|
||||
if (r == Round::Up || (r == Round::Even && (drops & 1) == 1))
|
||||
{
|
||||
if (drops >= kMaxRep)
|
||||
{
|
||||
@@ -486,6 +554,12 @@ Number::Guard::doRound(rep& drops, std::string location) const
|
||||
}
|
||||
++drops;
|
||||
}
|
||||
else if (cuspRoundingFix != MantissaRange::CuspRoundingFix::Disabled && drops > kMaxRep)
|
||||
{
|
||||
// This will probably be impossible because this function is not called by mutating
|
||||
// functions, so the Number will already be normalized.
|
||||
drops = kMaxRep;
|
||||
}
|
||||
if (isNegative())
|
||||
drops = -drops;
|
||||
}
|
||||
@@ -530,12 +604,14 @@ 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;
|
||||
static constexpr auto kMaxExponent = Number::kMaxExponent;
|
||||
static constexpr auto kMaxRep = Number::kMaxRep;
|
||||
auto const kRepLimit = cuspRoundingFix == MantissaRange::CuspRoundingFix::Disabled
|
||||
? Number::kMaxRep
|
||||
: Number::kMaxRepUp;
|
||||
|
||||
using Guard = Number::Guard;
|
||||
|
||||
@@ -585,7 +661,7 @@ doNormalize(
|
||||
// 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 (m > kRepLimit)
|
||||
{
|
||||
if (exponent >= kMaxExponent)
|
||||
throw std::overflow_error("Number::normalize 1.5");
|
||||
@@ -595,7 +671,7 @@ doNormalize(
|
||||
// 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");
|
||||
XRPL_ASSERT_PARTS(m <= kRepLimit, "xrpl::doNormalize", "intermediate mantissa fits in limit");
|
||||
mantissa = m;
|
||||
|
||||
g.doRoundUp(
|
||||
@@ -604,7 +680,7 @@ doNormalize(
|
||||
exponent,
|
||||
minMantissa,
|
||||
maxMantissa,
|
||||
cuspRoundingFixEnabled,
|
||||
cuspRoundingFix,
|
||||
"Number::normalize 2");
|
||||
XRPL_ASSERT_PARTS(
|
||||
mantissa >= minMantissa && mantissa <= maxMantissa,
|
||||
@@ -620,13 +696,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 +713,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 +730,15 @@ 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);
|
||||
}
|
||||
|
||||
// Copy the number, but set a new exponent. Because the mantissa doesn't change,
|
||||
@@ -720,45 +793,77 @@ Number::operator+=(Number const& y)
|
||||
uint128_t ym = y.mantissa_;
|
||||
auto ye = y.exponent_;
|
||||
Guard g;
|
||||
|
||||
auto const& range = kRange.get();
|
||||
|
||||
// Bring the exponents of both values into agreement, so the mantissas are on the same scale
|
||||
// and can be added directly together
|
||||
// expandM / expandE: First try to expand the mantissa and bring the exponent down
|
||||
// shringM / shrinkE: Then shrink the mantissa and bring the exponent up, if necessary
|
||||
auto const adjust = [&g, &range](
|
||||
uint128_t& expandM, int& expandE, uint128_t& shrinkM, int& shrinkE) {
|
||||
constexpr uint128_t kSafeLimit = kPowerOfTenImpl<uint128_t, detail::kUint128Digits>[37];
|
||||
|
||||
if (range.cuspRoundingFix != MantissaRange::CuspRoundingFix::Disabled)
|
||||
{
|
||||
while (shrinkE < expandE && shrinkM % 10 == 0)
|
||||
{
|
||||
g.doDropDigit(shrinkM, shrinkE);
|
||||
}
|
||||
|
||||
// We've got 128 bits of mantissa to work with here. Don't throw away data unless we
|
||||
// have to
|
||||
while (shrinkE < expandE && expandE > kMinExponent && expandM < kSafeLimit)
|
||||
{
|
||||
expandM *= 10;
|
||||
--expandE;
|
||||
}
|
||||
}
|
||||
|
||||
while (shrinkE < expandE)
|
||||
{
|
||||
g.doDropDigit(shrinkM, shrinkE);
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
|
||||
adjust(xm, xe, ym, ye);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
if (xn == yn)
|
||||
{
|
||||
xm += ym;
|
||||
if (xm > maxMantissa || xm > kMaxRep)
|
||||
if (range.cuspRoundingFix != MantissaRange::CuspRoundingFix::Disabled)
|
||||
{
|
||||
g.doDropDigit(xm, xe);
|
||||
while (xm > maxMantissa || xm > kMaxRepUp)
|
||||
{
|
||||
g.doDropDigit(xm, xe);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (xm > maxMantissa || xm > kMaxRep)
|
||||
{
|
||||
g.doDropDigit(xm, xe);
|
||||
}
|
||||
}
|
||||
g.doRoundUp(
|
||||
xn,
|
||||
xm,
|
||||
xe,
|
||||
minMantissa,
|
||||
maxMantissa,
|
||||
cuspRoundingFixEnabled,
|
||||
"Number::addition overflow");
|
||||
xn, xm, xe, minMantissa, maxMantissa, cuspRoundingFix, "Number::addition overflow");
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -779,6 +884,19 @@ Number::operator+=(Number const& y)
|
||||
--xe;
|
||||
}
|
||||
g.doRoundDown(xn, xm, xe, minMantissa);
|
||||
if (range.cuspRoundingFix != MantissaRange::CuspRoundingFix::Disabled && xm != 0)
|
||||
{
|
||||
// make a new guard
|
||||
Guard g;
|
||||
if (xn)
|
||||
g.setNegative();
|
||||
while (xm > maxMantissa || xm > kMaxRepUp)
|
||||
{
|
||||
g.doDropDigit(xm, xe);
|
||||
}
|
||||
g.doRoundUp(
|
||||
xn, xm, xe, minMantissa, maxMantissa, cuspRoundingFix, "Number::addition overflow");
|
||||
}
|
||||
}
|
||||
|
||||
negative_ = xn;
|
||||
@@ -825,9 +943,11 @@ 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;
|
||||
auto const kRepLimit =
|
||||
cuspRoundingFix == MantissaRange::CuspRoundingFix::Disabled ? kMaxRep : kMaxRepUp;
|
||||
|
||||
while (zm > maxMantissa || zm > kMaxRep)
|
||||
while (zm > maxMantissa || zm > kRepLimit)
|
||||
{
|
||||
g.doDropDigit(zm, ze);
|
||||
}
|
||||
@@ -840,7 +960,7 @@ Number::operator*=(Number const& y)
|
||||
xe,
|
||||
minMantissa,
|
||||
maxMantissa,
|
||||
cuspRoundingFixEnabled,
|
||||
cuspRoundingFix,
|
||||
"Number::multiplication overflow : exponent is " + std::to_string(xe));
|
||||
negative_ = zn;
|
||||
mantissa_ = xm;
|
||||
@@ -882,7 +1002,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
|
||||
@@ -1014,14 +1134,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;
|
||||
@@ -1033,6 +1153,8 @@ Number::operator/=(Number const& y)
|
||||
Number::
|
||||
operator rep() const
|
||||
{
|
||||
auto const& range = kRange.get();
|
||||
|
||||
rep drops = mantissa();
|
||||
int offset = exponent();
|
||||
Guard g;
|
||||
@@ -1053,7 +1175,7 @@ operator rep() const
|
||||
throw std::overflow_error("Number::operator rep() overflow");
|
||||
drops *= 10;
|
||||
}
|
||||
g.doRound(drops, "Number::operator rep() rounding overflow");
|
||||
g.doRound(drops, range.cuspRoundingFix, "Number::operator rep() rounding overflow");
|
||||
}
|
||||
return drops;
|
||||
}
|
||||
|
||||
@@ -43,6 +43,15 @@ class Number_test : public beast::unit_test::Suite
|
||||
return out;
|
||||
}
|
||||
|
||||
static BigInt
|
||||
toBigInt(Number const& n)
|
||||
{
|
||||
BigInt v = n.mantissa();
|
||||
for (int i = 0; i < n.exponent(); ++i)
|
||||
v *= 10;
|
||||
return v;
|
||||
}
|
||||
|
||||
using dec = boost::multiprecision::cpp_dec_float_50;
|
||||
|
||||
template <class T = dec>
|
||||
@@ -169,28 +178,37 @@ 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}}});
|
||||
BEAST_EXPECT(Number::getround() == Number::RoundingMode::ToNearest);
|
||||
|
||||
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
|
||||
@@ -198,45 +216,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
|
||||
@@ -244,6 +274,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
|
||||
@@ -254,21 +285,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)
|
||||
@@ -308,21 +343,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
|
||||
@@ -330,49 +372,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)
|
||||
@@ -1644,9 +1700,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 storedValue = toBigInt(product);
|
||||
|
||||
BigInt const signedDifference = storedValue - exactProduct;
|
||||
|
||||
@@ -1869,6 +1923,129 @@ public:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
testcase << "normalization cusp: ToNearest and Downward disagree " << to_string(scale);
|
||||
|
||||
constexpr auto kMaxRep = Number::kMaxRep;
|
||||
|
||||
// Both ToNearest and Downward should round to `below`
|
||||
auto const actual = static_cast<std::uint64_t>(kMaxRep) + 1;
|
||||
Number const below{static_cast<std::int64_t>(kMaxRep), 0};
|
||||
Number const above{
|
||||
false, static_cast<std::uint64_t>(kMaxRep) + 3, 0, Number::Unchecked{}};
|
||||
|
||||
auto construct = [](Number::RoundingMode mode) {
|
||||
NumberRoundModeGuard const roundGuard{mode};
|
||||
return Number(false, actual, 0, Number::Normalized{});
|
||||
};
|
||||
Number const upward = construct(Number::RoundingMode::Upward);
|
||||
|
||||
Number const toNearest = construct(Number::RoundingMode::ToNearest);
|
||||
|
||||
Number const downward = construct(Number::RoundingMode::Downward);
|
||||
|
||||
log << "\n"
|
||||
<< " actual = " << actual << " (kMaxRep + 1)\n"
|
||||
<< " below = " << below << " (kMaxRep, distance 1)\n"
|
||||
<< " above = " << above << " (kMaxRep + 3, distance 2)\n"
|
||||
<< " Upward = " << upward << "\n"
|
||||
<< " ToNearest = " << toNearest << "\n"
|
||||
<< " Downward = " << downward << "\n\n";
|
||||
log.flush();
|
||||
|
||||
switch (scale)
|
||||
{
|
||||
case MantissaRange::MantissaScale::Small:
|
||||
// With the small mantissa, everything rounds up
|
||||
|
||||
// Upward round UP
|
||||
BEAST_EXPECT(upward > above);
|
||||
|
||||
// ToNearest rounds UP when the DOWN neighbor is strictly closer
|
||||
BEAST_EXPECT(toNearest > above);
|
||||
BEAST_EXPECT(toNearest == below);
|
||||
|
||||
// Downward undershoots: it returns a value below `below`
|
||||
BEAST_EXPECT(downward < below);
|
||||
|
||||
// Both should have given the same answer, but they differ
|
||||
BEAST_EXPECT(toNearest > downward);
|
||||
|
||||
break;
|
||||
|
||||
case MantissaRange::MantissaScale::LargeLegacy:
|
||||
// Upward round UP
|
||||
BEAST_EXPECT(upward == above);
|
||||
|
||||
// ToNearest rounds UP when the DOWN neighbor is strictly closer
|
||||
BEAST_EXPECT(toNearest == above);
|
||||
BEAST_EXPECT(toNearest > below);
|
||||
|
||||
// Downward undershoots: it returns a value below `below`
|
||||
BEAST_EXPECT(downward < below);
|
||||
|
||||
// Both should have given the same answer, but they differ
|
||||
BEAST_EXPECT(toNearest > downward);
|
||||
|
||||
break;
|
||||
default:
|
||||
// Upward round UP
|
||||
BEAST_EXPECT(upward == above);
|
||||
|
||||
// ToNearest rounds UP when the DOWN neighbor is strictly closer
|
||||
BEAST_EXPECT(toNearest != above);
|
||||
BEAST_EXPECT(toNearest == below);
|
||||
|
||||
// Downward undershoots: it returns a value below `below`
|
||||
BEAST_EXPECT(downward == below);
|
||||
|
||||
// Both should have given the same answer, but they differ
|
||||
BEAST_EXPECT(toNearest == downward);
|
||||
}
|
||||
}
|
||||
{
|
||||
testcase << "operator+ TowardsZero rounds away from zero " << to_string(scale);
|
||||
|
||||
Number const a{1LL, 20};
|
||||
Number const b{-1'000'000'000'000'000'001LL};
|
||||
|
||||
BEAST_EXPECT(toBigInt(a) == BigInt{"100000000000000000000"});
|
||||
if (scale != MantissaRange::MantissaScale::Small)
|
||||
BEAST_EXPECT(toBigInt(b) == BigInt{"-1000000000000000001"});
|
||||
else
|
||||
BEAST_EXPECT(toBigInt(b) == BigInt{"-1000000000000000000"});
|
||||
|
||||
Number sum;
|
||||
{
|
||||
NumberRoundModeGuard const roundGuard{Number::RoundingMode::TowardsZero};
|
||||
sum = a + b;
|
||||
}
|
||||
|
||||
BigInt const exact = toBigInt(a) + toBigInt(b);
|
||||
BigInt const stored = toBigInt(sum);
|
||||
BigInt const diff = stored - exact;
|
||||
|
||||
log << "\n a = " << a << "\n b = " << b
|
||||
<< "\n exact a + b = " << exact.str() << "\n TowardsZero = " << stored.str()
|
||||
<< "\n difference = " << diff.str() << "\n";
|
||||
log.flush();
|
||||
|
||||
if (scale == MantissaRange::MantissaScale::Small)
|
||||
{
|
||||
BEAST_EXPECT(stored == exact);
|
||||
}
|
||||
else if (scale == MantissaRange::MantissaScale::LargeLegacy)
|
||||
{
|
||||
BEAST_EXPECT(stored > exact);
|
||||
}
|
||||
else
|
||||
{
|
||||
BEAST_EXPECT(stored < exact);
|
||||
BEAST_EXPECT(diff < 0);
|
||||
BEAST_EXPECT(-diff < pow10<BigInt>(sum.exponent()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
Reference in New Issue
Block a user