mirror of
https://github.com/XRPLF/rippled.git
synced 2026-07-13 18:20:22 +00:00
Merge remote-tracking branch 'origin/ximinez/number-round-maxrep' into ripple/lending-protocol-fv
This commit is contained in:
@@ -334,6 +334,8 @@ 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 = ((kMaxRep / 10) + 1) * 10;
|
||||
static_assert(kMaxRepUp == 9'223'372'036'854'775'810ULL);
|
||||
|
||||
// May need to make unchecked private
|
||||
struct Unchecked
|
||||
|
||||
@@ -287,6 +287,25 @@ public:
|
||||
void
|
||||
doDropDigit(T& mantissa, int& exponent) noexcept;
|
||||
|
||||
// Modify the result to the correctly rounded value
|
||||
template <UnsignedMantissa T>
|
||||
void
|
||||
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) const;
|
||||
|
||||
// Modify the result to the correctly rounded value
|
||||
void
|
||||
doRound(rep& drops, std::string location) const;
|
||||
|
||||
private:
|
||||
template <UnsignedMantissa T>
|
||||
void
|
||||
pushOverflow(T mantissa);
|
||||
|
||||
enum class Round {
|
||||
// The result is exact. No rounding is needed. Only used if cuspRoundingFix is Enabled330 or
|
||||
// higher.
|
||||
@@ -299,37 +318,22 @@ public:
|
||||
// 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)
|
||||
// Enabled330)
|
||||
Up = 1,
|
||||
};
|
||||
|
||||
// Indicate round direction: 1 is up, -1 is down, 0 is even
|
||||
// Indicate round direction. See Round enum above.
|
||||
// This enables the client to round towards nearest, and on
|
||||
// tie, round towards even.
|
||||
[[nodiscard]] Round
|
||||
round() const noexcept;
|
||||
|
||||
// Modify the result to the correctly rounded value
|
||||
template <UnsignedMantissa T>
|
||||
void
|
||||
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);
|
||||
|
||||
// Modify the result to the correctly rounded value
|
||||
void
|
||||
doRound(rep& drops, std::string location) const;
|
||||
|
||||
private:
|
||||
void
|
||||
doPush(unsigned d) noexcept;
|
||||
|
||||
template <UnsignedMantissa T>
|
||||
void
|
||||
bringIntoRange(bool& negative, T& mantissa, int& exponent);
|
||||
bringIntoRange(bool& negative, T& mantissa, int& exponent) const;
|
||||
};
|
||||
|
||||
inline void
|
||||
@@ -406,10 +410,76 @@ Number::Guard::doDropDigit<uint128_t>(uint128_t& mantissa, int& exponent) noexce
|
||||
++exponent;
|
||||
}
|
||||
|
||||
template <UnsignedMantissa T>
|
||||
void
|
||||
Number::Guard::pushOverflow(T mantissa)
|
||||
{
|
||||
XRPL_ASSERT(mantissa <= kMaxRepUp, "xrpl::Number::Guard::pushOverflow : valid mantissa");
|
||||
if (cuspRoundingFix >= MantissaRange::CuspRoundingFix::Enabled330 && mantissa >= kMaxRep &&
|
||||
mantissa < kMaxRepUp)
|
||||
{
|
||||
// Special case rounding rules for the values in the range [kMaxRep, kMaxRepUp).
|
||||
|
||||
auto constexpr spread = kMaxRepUp - kMaxRep;
|
||||
static_assert(spread == 3);
|
||||
|
||||
// Round in two steps.
|
||||
|
||||
// The first step uses the digits _already_ in the Guard to possibly round the mantissa up.
|
||||
// Ultimately, the purpose of this step is to capture rounding where the stored digits would
|
||||
// change the decision without those digits. (e.g. From just _below_ the midpoint to just
|
||||
// _above_ the midpoint for ToNearest, or from kMaxRep into the in-between for Upward. Make
|
||||
// an exception if the final digit is 9, because it can only get larger, and we don't want
|
||||
// to bump up to kMaxRepUp.
|
||||
if (mantissa % 10 < 9)
|
||||
{
|
||||
// Intentionally use integer math to get the largest value under the midpoint.
|
||||
auto constexpr kMidpoint = kMaxRep + (spread / 2);
|
||||
static_assert(kMidpoint == kMaxRep + 1);
|
||||
auto const r = round();
|
||||
if (r == Round::Up || (r == Round::Even && mantissa == kMidpoint))
|
||||
{
|
||||
++mantissa;
|
||||
}
|
||||
}
|
||||
|
||||
if (mantissa == kMaxRep)
|
||||
{
|
||||
// If the mantissa ends up exactly kMaxRep, there's nothing more to do here.
|
||||
return;
|
||||
}
|
||||
|
||||
// The second step scales the final digit of the update mantissa proportionally, converting
|
||||
// from (kMaxRep, kMaxRepUp) to (0 to 9]. It then pushes that scaled digit onto the guard as
|
||||
// if it was a digit that got removed, but doesn't actually remove it. This method should be
|
||||
// future-proof in case the number of mantissa bits ever changes. (Though for integer values
|
||||
// of the form 2^(2^x-1), the spread will always be the same.) Effects:
|
||||
// * For round to nearest
|
||||
// * if the updated mantissa is below the midpoint, it'll round "down" to kMaxRep
|
||||
// * if above the midpoint, it'll round "up" to kMaxRepUp
|
||||
// * it can never be exactly at the midpoint, because kMaxRepUp is always even, and
|
||||
// kMaxRep is always odd, so don't worry about that case.
|
||||
// * For round upward, will round up to kMaxRepUp for positive values, down to kMaxRep for
|
||||
// negative.
|
||||
// * For round downward, does the opposite of upward.
|
||||
// * For round toward zero, always rounds down to kMaxRep.
|
||||
|
||||
auto const diff = mantissa - kMaxRep;
|
||||
auto const digit = (diff * 10) / spread;
|
||||
XRPL_ASSERT(
|
||||
digit > 0 && digit < 10 && digit != 5,
|
||||
"xrpl::Number::Guard::pushOverflow : 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
|
||||
// Exact if Guard is _zero_, and appropriate amendments are enabled
|
||||
// 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
|
||||
{
|
||||
@@ -455,16 +525,19 @@ Number::Guard::round() const noexcept
|
||||
|
||||
template <UnsignedMantissa T>
|
||||
void
|
||||
Number::Guard::bringIntoRange(bool& negative, T& mantissa, int& exponent)
|
||||
Number::Guard::bringIntoRange(bool& negative, T& mantissa, int& exponent) const
|
||||
{
|
||||
// Bring mantissa back into the minMantissa / maxMantissa range AFTER
|
||||
// rounding
|
||||
// rounding. Mantissa should never be 0.
|
||||
XRPL_ASSERT(mantissa != 0, "xrpl::Number::Guard::bringIntoRange : valid mantissa");
|
||||
if (mantissa < minMantissa)
|
||||
{
|
||||
mantissa *= 10;
|
||||
--exponent;
|
||||
}
|
||||
if (exponent < kMinExponent)
|
||||
// mantissa should never be 0, but if it _is_ make the result kZero.
|
||||
if (exponent < kMinExponent ||
|
||||
(cuspRoundingFix >= MantissaRange::CuspRoundingFix::Enabled330 && mantissa == 0))
|
||||
{
|
||||
static constexpr Number kZero = Number{};
|
||||
|
||||
@@ -478,7 +551,9 @@ template <UnsignedMantissa T>
|
||||
void
|
||||
Number::Guard::doRoundUp(bool& negative, T& mantissa, int& exponent, std::string location)
|
||||
{
|
||||
auto r = round();
|
||||
pushOverflow(mantissa);
|
||||
|
||||
auto const r = round();
|
||||
if (r == Round::Up || (r == Round::Even && (mantissa & 1) == 1))
|
||||
{
|
||||
auto const safeToIncrement = [this](auto const& mantissa) {
|
||||
@@ -495,18 +570,29 @@ Number::Guard::doRoundUp(bool& negative, T& mantissa, int& exponent, std::string
|
||||
}
|
||||
else
|
||||
{
|
||||
// Incrementing the mantissa will require dividing, which will require rounding. So
|
||||
// _don't_ increment the mantissa. Instead, divide and round recursively. It should
|
||||
// be impossible to recurse more than once, because once the mantissa is divided by
|
||||
// 10, it will be _well_ under maxMantissa and kMaxRep, so adding 1 will have no
|
||||
// chance of bringing it back over.
|
||||
doDropDigit(mantissa, exponent);
|
||||
XRPL_ASSERT_PARTS(
|
||||
safeToIncrement(mantissa),
|
||||
"xrpl::Number::Guard::doRoundUp",
|
||||
"can't recurse more than once");
|
||||
doRoundUp(negative, mantissa, exponent, location);
|
||||
return;
|
||||
if (cuspRoundingFix >= MantissaRange::CuspRoundingFix::Enabled330 &&
|
||||
mantissa > kMaxRep && mantissa < kMaxRepUp)
|
||||
{
|
||||
// When rounding up a value in between kMaxRep, and kMaxRepUp, round to
|
||||
// kMaxRepUp. Note that the decision for this rounding is dominated by the
|
||||
// results of pushOverflow.
|
||||
mantissa = kMaxRepUp;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Incrementing the mantissa will require dividing, which will require rounding.
|
||||
// So _don't_ increment the mantissa. Instead, divide and round recursively. It
|
||||
// should be impossible to recurse more than once, because once the mantissa is
|
||||
// divided by 10, it will be _well_ under maxMantissa and kMaxRep, so adding 1
|
||||
// will have no chance of bringing it back over.
|
||||
doDropDigit(mantissa, exponent);
|
||||
XRPL_ASSERT_PARTS(
|
||||
safeToIncrement(mantissa),
|
||||
"xrpl::Number::Guard::doRoundUp",
|
||||
"can't recurse more than once");
|
||||
doRoundUp(negative, mantissa, exponent, location);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -524,6 +610,14 @@ Number::Guard::doRoundUp(bool& negative, T& mantissa, int& exponent, std::string
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (
|
||||
cuspRoundingFix >= MantissaRange::CuspRoundingFix::Enabled330 && mantissa > kMaxRep &&
|
||||
mantissa < kMaxRepUp)
|
||||
{
|
||||
// When rounding down a value in between kMaxRep, and kMaxRepUp, round to kMaxRep.
|
||||
// Note that the decision for this rounding is dominated by the results of pushOverflow.
|
||||
mantissa = kMaxRep;
|
||||
}
|
||||
bringIntoRange(negative, mantissa, exponent);
|
||||
if (exponent > kMaxExponent)
|
||||
Throw<std::overflow_error>(std::string(location));
|
||||
@@ -531,8 +625,10 @@ Number::Guard::doRoundUp(bool& negative, T& mantissa, int& exponent, std::string
|
||||
|
||||
template <UnsignedMantissa T>
|
||||
void
|
||||
Number::Guard::doRoundDown(bool& negative, T& mantissa, int& exponent)
|
||||
Number::Guard::doRoundDown(bool& negative, T& mantissa, int& exponent) const
|
||||
{
|
||||
// Do not pushOverflow here.
|
||||
|
||||
auto r = round();
|
||||
if (cuspRoundingFix >= MantissaRange::CuspRoundingFix::Enabled330)
|
||||
{
|
||||
@@ -567,6 +663,8 @@ Number::Guard::doRoundDown(bool& negative, T& mantissa, int& exponent)
|
||||
void
|
||||
Number::Guard::doRound(rep& drops, std::string location) const
|
||||
{
|
||||
// Do not pushOverflow here.
|
||||
|
||||
auto r = round();
|
||||
if (r == Round::Up || (r == Round::Even && (drops & 1) == 1))
|
||||
{
|
||||
@@ -583,6 +681,8 @@ Number::Guard::doRound(rep& drops, std::string location) const
|
||||
}
|
||||
++drops;
|
||||
}
|
||||
XRPL_ASSERT(drops >= 0, "xrpl::Number::Guard::doRound : positive magnitude");
|
||||
|
||||
if (isNegative())
|
||||
drops = -drops;
|
||||
}
|
||||
@@ -632,7 +732,9 @@ doNormalize(
|
||||
{
|
||||
static constexpr auto kMinExponent = Number::kMinExponent;
|
||||
static constexpr auto kMaxExponent = Number::kMaxExponent;
|
||||
static constexpr auto kMaxRep = Number::kMaxRep;
|
||||
auto const repLimit = cuspRoundingFix >= MantissaRange::CuspRoundingFix::Enabled330
|
||||
? Number::kMaxRepUp
|
||||
: Number::kMaxRep;
|
||||
|
||||
using Guard = Number::Guard;
|
||||
|
||||
@@ -682,17 +784,17 @@ 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 > repLimit)
|
||||
{
|
||||
if (exponent >= kMaxExponent)
|
||||
throw std::overflow_error("Number::normalize 1.5");
|
||||
g.doDropDigit(m, exponent);
|
||||
}
|
||||
// Before modification, m should be within the min/max range. After
|
||||
// modification, it must be less than kMaxRep. In other words, the original
|
||||
// value should have been no more than kMaxRep * 10.
|
||||
// (kMaxRep * 10 > maxMantissa)
|
||||
XRPL_ASSERT_PARTS(m <= kMaxRep, "xrpl::doNormalize", "intermediate mantissa fits in int64");
|
||||
// modification, it must be less than repLimit. In other words, the original
|
||||
// value should have been no more than repLimit * 10.
|
||||
// (repLimit * 10 > maxMantissa)
|
||||
XRPL_ASSERT_PARTS(m <= repLimit, "xrpl::doNormalize", "intermediate mantissa fits in limit");
|
||||
mantissa = m;
|
||||
|
||||
g.doRoundUp(negative, mantissa, exponent, "Number::normalize 2");
|
||||
@@ -824,6 +926,9 @@ Number::operator+=(Number const& y)
|
||||
auto const& maxMantissa = g.maxMantissa;
|
||||
auto const cuspRoundingFix = g.cuspRoundingFix;
|
||||
|
||||
auto const repLimit =
|
||||
cuspRoundingFix >= MantissaRange::CuspRoundingFix::Enabled330 ? kMaxRepUp : kMaxRep;
|
||||
|
||||
// Bring the exponents of both values into agreement, so the mantissas are on the same scale
|
||||
// and can be added directly together.
|
||||
|
||||
@@ -908,7 +1013,7 @@ Number::operator+=(Number const& y)
|
||||
}
|
||||
else
|
||||
{
|
||||
if (xm > maxMantissa || xm > kMaxRep)
|
||||
if (xm > maxMantissa || xm > repLimit)
|
||||
{
|
||||
g.doDropDigit(xm, xe);
|
||||
}
|
||||
@@ -952,7 +1057,7 @@ Number::operator+=(Number const& y)
|
||||
{
|
||||
// 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)
|
||||
while (xm < minMantissa && xm * 10 <= repLimit)
|
||||
{
|
||||
xm *= 10;
|
||||
xm -= g.pop();
|
||||
@@ -1026,8 +1131,10 @@ Number::operator*=(Number const& y)
|
||||
g.setNegative();
|
||||
|
||||
auto const& maxMantissa = g.maxMantissa;
|
||||
auto const repLimit =
|
||||
g.cuspRoundingFix >= MantissaRange::CuspRoundingFix::Enabled330 ? kMaxRepUp : kMaxRep;
|
||||
|
||||
while (zm > maxMantissa || zm > kMaxRep)
|
||||
while (zm > maxMantissa || zm > repLimit)
|
||||
{
|
||||
g.doDropDigit(zm, ze);
|
||||
}
|
||||
|
||||
@@ -1452,18 +1452,54 @@ class LoanBroker_test : public beast::unit_test::Suite
|
||||
env(tx2, Ter(temINVALID));
|
||||
}
|
||||
|
||||
if (Number::getMantissaScale() == MantissaRange::MantissaScale::Large330)
|
||||
{
|
||||
auto const dm = power(2, 63) - 1;
|
||||
BEAST_EXPECTS(dm > kMaxMpTokenAmount, to_string(dm));
|
||||
tx2[sfDebtMaximum] = dm;
|
||||
env(tx2, Ter(temINVALID));
|
||||
}
|
||||
// For the Large330 scale, 2^63 rounds _down_ to Number::kMaxRep
|
||||
{
|
||||
auto const dm = power(2, 63);
|
||||
BEAST_EXPECTS(dm == kMaxMpTokenAmount, to_string(dm));
|
||||
tx2[sfDebtMaximum] = dm;
|
||||
env(tx2, Ter(tesSUCCESS));
|
||||
}
|
||||
|
||||
{
|
||||
auto const dm = power(2, 63) - 1;
|
||||
BEAST_EXPECTS(dm < kMaxMpTokenAmount, to_string(dm));
|
||||
tx2[sfDebtMaximum] = dm;
|
||||
env(tx2, Ter(tesSUCCESS));
|
||||
}
|
||||
|
||||
{
|
||||
auto const dm = power(2, 63) - 3;
|
||||
BEAST_EXPECTS(dm < kMaxMpTokenAmount, to_string(dm));
|
||||
tx2[sfDebtMaximum] = dm;
|
||||
env(tx2, Ter(tesSUCCESS));
|
||||
}
|
||||
|
||||
{
|
||||
auto const dm = power(2, 63) + 3;
|
||||
BEAST_EXPECTS(dm > kMaxMpTokenAmount, to_string(dm));
|
||||
tx2[sfDebtMaximum] = dm;
|
||||
env(tx2, Ter(temINVALID));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
auto const dm = power(2, 63) - 3;
|
||||
BEAST_EXPECTS(dm == kMaxMpTokenAmount, to_string(dm));
|
||||
tx2[sfDebtMaximum] = dm;
|
||||
env(tx2, Ter(tesSUCCESS));
|
||||
// For other scales, 2^63 rounds _up_ to Number::kMaxRepUp. Subtracting 1 rounds up
|
||||
// again.
|
||||
{
|
||||
auto const dm = power(2, 63) - 1;
|
||||
BEAST_EXPECTS(dm > kMaxMpTokenAmount, to_string(dm));
|
||||
tx2[sfDebtMaximum] = dm;
|
||||
env(tx2, Ter(temINVALID));
|
||||
}
|
||||
|
||||
{
|
||||
auto const dm = power(2, 63) - 3;
|
||||
BEAST_EXPECTS(dm == kMaxMpTokenAmount, to_string(dm));
|
||||
tx2[sfDebtMaximum] = dm;
|
||||
env(tx2, Ter(tesSUCCESS));
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
@@ -5606,9 +5606,9 @@ class Vault_test : public beast::unit_test::Suite
|
||||
BEAST_EXPECT(maxInt64 == "9223372036854775807");
|
||||
|
||||
// Naming things is hard
|
||||
auto const maxInt64Plus1 = std::to_string(
|
||||
static_cast<std::uint64_t>(std::numeric_limits<std::int64_t>::max()) + 1);
|
||||
BEAST_EXPECT(maxInt64Plus1 == "9223372036854775808");
|
||||
auto const maxInt64Plus2 = std::to_string(
|
||||
static_cast<std::uint64_t>(std::numeric_limits<std::int64_t>::max()) + 2);
|
||||
BEAST_EXPECT(maxInt64Plus2 == "9223372036854775809");
|
||||
|
||||
auto const initialXRP = to_string(kInitialXrp);
|
||||
BEAST_EXPECT(initialXRP == "100000000000000000");
|
||||
@@ -5636,15 +5636,15 @@ class Vault_test : public beast::unit_test::Suite
|
||||
env(tx);
|
||||
env.close();
|
||||
|
||||
tx[sfAssetsMaximum] = maxInt64Plus1;
|
||||
tx[sfAssetsMaximum] = maxInt64Plus2;
|
||||
env(tx, Ter(tefEXCEPTION));
|
||||
env.close();
|
||||
|
||||
// This value will be rounded
|
||||
auto const insertAt = maxInt64Plus1.size() - 3;
|
||||
auto const decimalTest = maxInt64Plus1.substr(0, insertAt) + "." +
|
||||
maxInt64Plus1.substr(insertAt); // (max int64+1) / 1000
|
||||
BEAST_EXPECT(decimalTest == "9223372036854775.808");
|
||||
auto const insertAt = maxInt64Plus2.size() - 3;
|
||||
auto const decimalTest = maxInt64Plus2.substr(0, insertAt) + "." +
|
||||
maxInt64Plus2.substr(insertAt); // (max int64+2) / 1000
|
||||
BEAST_EXPECT(decimalTest == "9223372036854775.809");
|
||||
tx[sfAssetsMaximum] = decimalTest;
|
||||
auto const newKeylet = keylet::vault(owner.id(), env.seq(owner));
|
||||
env(tx);
|
||||
@@ -5688,15 +5688,15 @@ class Vault_test : public beast::unit_test::Suite
|
||||
env(tx);
|
||||
env.close();
|
||||
|
||||
tx[sfAssetsMaximum] = maxInt64Plus1;
|
||||
tx[sfAssetsMaximum] = maxInt64Plus2;
|
||||
env(tx, Ter(tefEXCEPTION));
|
||||
env.close();
|
||||
|
||||
// This value will be rounded
|
||||
auto const insertAt = maxInt64Plus1.size() - 1;
|
||||
auto const decimalTest = maxInt64Plus1.substr(0, insertAt) + "." +
|
||||
maxInt64Plus1.substr(insertAt); // (max int64+1) / 10
|
||||
BEAST_EXPECT(decimalTest == "922337203685477580.8");
|
||||
auto const insertAt = maxInt64Plus2.size() - 1;
|
||||
auto const decimalTest = maxInt64Plus2.substr(0, insertAt) + "." +
|
||||
maxInt64Plus2.substr(insertAt); // (max int64+2) / 10
|
||||
BEAST_EXPECT(decimalTest == "922337203685477580.9");
|
||||
tx[sfAssetsMaximum] = decimalTest;
|
||||
auto const newKeylet = keylet::vault(owner.id(), env.seq(owner));
|
||||
env(tx);
|
||||
@@ -5733,7 +5733,7 @@ class Vault_test : public beast::unit_test::Suite
|
||||
env(tx);
|
||||
env.close();
|
||||
|
||||
tx[sfAssetsMaximum] = maxInt64Plus1;
|
||||
tx[sfAssetsMaximum] = maxInt64Plus2;
|
||||
env(tx);
|
||||
env.close();
|
||||
|
||||
@@ -5745,10 +5745,10 @@ class Vault_test : public beast::unit_test::Suite
|
||||
|
||||
// These values will be rounded to 15 significant digits
|
||||
{
|
||||
auto const insertAt = maxInt64Plus1.size() - 1;
|
||||
auto const decimalTest = maxInt64Plus1.substr(0, insertAt) + "." +
|
||||
maxInt64Plus1.substr(insertAt); // (max int64+1) / 10
|
||||
BEAST_EXPECT(decimalTest == "922337203685477580.8");
|
||||
auto const insertAt = maxInt64Plus2.size() - 1;
|
||||
auto const decimalTest = maxInt64Plus2.substr(0, insertAt) + "." +
|
||||
maxInt64Plus2.substr(insertAt); // (max int64+2) / 10
|
||||
BEAST_EXPECT(decimalTest == "922337203685477580.9");
|
||||
tx[sfAssetsMaximum] = decimalTest;
|
||||
auto const newKeylet = keylet::vault(owner.id(), env.seq(owner));
|
||||
env(tx);
|
||||
|
||||
@@ -188,6 +188,8 @@ public:
|
||||
auto const scale = Number::getMantissaScale();
|
||||
testcase << "test_add " << to_string(scale);
|
||||
|
||||
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},
|
||||
@@ -299,12 +301,15 @@ public:
|
||||
auto const cLargeLegacy = std::to_array<Case>({
|
||||
{Number{Number::kMaxRep}, Number{6, -1}, Number{Number::kMaxRep / 10, 1}, __LINE__},
|
||||
});
|
||||
auto const cLargeCorrected = std::to_array<Case>({
|
||||
auto const cLarge320 = std::to_array<Case>({
|
||||
{Number{Number::kMaxRep},
|
||||
Number{6, -1},
|
||||
Number{(Number::kMaxRep / 10) + 1, 1},
|
||||
__LINE__},
|
||||
});
|
||||
auto const cLargeCorrected = std::to_array<Case>({
|
||||
{Number{Number::kMaxRep}, Number{6, -1}, Number{Number::kMaxRep}, __LINE__},
|
||||
});
|
||||
auto test = [this](auto const& c) {
|
||||
for (auto const& [x, y, z, line] : c)
|
||||
{
|
||||
@@ -325,6 +330,10 @@ public:
|
||||
{
|
||||
test(cLargeLegacy);
|
||||
}
|
||||
else if (scale == MantissaRange::MantissaScale::Large320)
|
||||
{
|
||||
test(cLarge320);
|
||||
}
|
||||
else
|
||||
{
|
||||
test(cLargeCorrected);
|
||||
@@ -373,7 +382,7 @@ public:
|
||||
Number{1'000'000'000'000'000, -15},
|
||||
Number{1'000'000'000'000'000, -30},
|
||||
__LINE__}});
|
||||
auto const cLarge = std::to_array<Case>(
|
||||
auto const cLargeAll = std::to_array<Case>(
|
||||
// Note that items with extremely large mantissas need to be
|
||||
// calculated, because otherwise they overflow uint64. Items from C
|
||||
// with larger mantissa
|
||||
@@ -420,16 +429,55 @@ public:
|
||||
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},
|
||||
__LINE__},
|
||||
{Number{false, Number::kMaxRep + 1, 0, Number::Normalized{}},
|
||||
Number{3, 0},
|
||||
Number{Number::kMaxRep},
|
||||
__LINE__},
|
||||
{power(2, 63), Number{3, 0}, Number{Number::kMaxRep}, __LINE__},
|
||||
});
|
||||
// Note that items with extremely large mantissas need to be
|
||||
// calculated, because otherwise they overflow uint64. Items from C
|
||||
// with larger mantissa
|
||||
auto const cLarge = std::to_array<Case>({
|
||||
// Anything larger than kMaxRep rounds up
|
||||
{Number{false, Number::kMaxRep + 1, 0, Number::Normalized{}},
|
||||
Number{1, 0},
|
||||
Number{(Number::kMaxRep / 10) + 1, 1},
|
||||
__LINE__},
|
||||
{Number{false, Number::kMaxRep + 1, 0, Number::Normalized{}},
|
||||
Number{3, 0},
|
||||
Number{Number::kMaxRep},
|
||||
__LINE__},
|
||||
{Number{false, Number::kMaxRep + 2, 0, Number::Normalized{}},
|
||||
Number{1, 0},
|
||||
Number{(Number::kMaxRep / 10) + 1, 1},
|
||||
__LINE__},
|
||||
{Number{false, Number::kMaxRep + 2, 0, Number::Normalized{}},
|
||||
Number{3, 0},
|
||||
Number{Number::kMaxRep},
|
||||
__LINE__},
|
||||
{power(2, 63), Number{3, 0}, Number{Number::kMaxRep}, __LINE__},
|
||||
});
|
||||
auto const cLarge330 = std::to_array<Case>({
|
||||
// kMaxRep + 1 is below the half-way point, so it rounds down to kMaxRep when the Number
|
||||
// is created.
|
||||
{Number{false, Number::kMaxRep + 1, 0, Number::Normalized{}},
|
||||
Number{1, 0},
|
||||
Number{Number::kMaxRep - 1},
|
||||
__LINE__},
|
||||
{Number{false, Number::kMaxRep + 1, 0, Number::Normalized{}},
|
||||
Number{3, 0},
|
||||
Number{Number::kMaxRep - 3},
|
||||
__LINE__},
|
||||
// kMaxRepUp -1 is above the half-way point, so it rounds up to kMaxRepUp when the
|
||||
// Number is created. Subtracting 1 from that rounds up again. A little non-intuitive.
|
||||
{Number{false, Number::kMaxRepUp - 1, 0, Number::Normalized{}},
|
||||
Number{1, 0},
|
||||
Number{(Number::kMaxRep / 10) + 1, 1},
|
||||
__LINE__},
|
||||
// Subtracting 3 gets back down to kMaxRep
|
||||
{Number{false, Number::kMaxRepUp - 1, 0, Number::Normalized{}},
|
||||
Number{3, 0},
|
||||
Number{Number::kMaxRep},
|
||||
__LINE__},
|
||||
// 2^63 is the same as kMaxRep+1
|
||||
{power(2, 63), Number{3, 0}, Number{Number::kMaxRep - 3}, __LINE__},
|
||||
});
|
||||
auto test = [this](auto const& c) {
|
||||
for (auto const& [x, y, z, line] : c)
|
||||
{
|
||||
@@ -439,13 +487,23 @@ public:
|
||||
expect(result == z, ss.str(), __FILE__, line);
|
||||
}
|
||||
};
|
||||
if (scale == MantissaRange::MantissaScale::Small)
|
||||
switch (scale)
|
||||
{
|
||||
test(cSmall);
|
||||
}
|
||||
else
|
||||
{
|
||||
test(cLarge);
|
||||
case MantissaRange::MantissaScale::Small:
|
||||
test(cSmall);
|
||||
break;
|
||||
case MantissaRange::MantissaScale::LargeLegacy:
|
||||
case MantissaRange::MantissaScale::Large320:
|
||||
test(cLargeAll);
|
||||
test(cLarge);
|
||||
break;
|
||||
case MantissaRange::MantissaScale::Large330:
|
||||
test(cLargeAll);
|
||||
test(cLarge330);
|
||||
break;
|
||||
default:
|
||||
BEAST_EXPECT(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1345,38 +1403,38 @@ public:
|
||||
auto const scale = Number::getMantissaScale();
|
||||
testcase << "testToString " << to_string(scale);
|
||||
|
||||
auto test = [this](Number const& n, std::string const& expected) {
|
||||
auto test = [this](Number const& n, std::string const& expected, int line) {
|
||||
auto const result = to_string(n);
|
||||
std::stringstream ss;
|
||||
ss << "to_string(" << result << "). Expected: " << expected;
|
||||
BEAST_EXPECTS(result == expected, ss.str());
|
||||
expect(result == expected, ss.str(), __FILE__, line);
|
||||
};
|
||||
|
||||
test(Number(-2, 0), "-2");
|
||||
test(Number(0, 0), "0");
|
||||
test(Number(2, 0), "2");
|
||||
test(Number(25, -3), "0.025");
|
||||
test(Number(-25, -3), "-0.025");
|
||||
test(Number(25, 1), "250");
|
||||
test(Number(-25, 1), "-250");
|
||||
test(Number(2, 20), "2e20");
|
||||
test(Number(-2, -20), "-2e-20");
|
||||
test(Number(-2, 0), "-2", __LINE__);
|
||||
test(Number(0, 0), "0", __LINE__);
|
||||
test(Number(2, 0), "2", __LINE__);
|
||||
test(Number(25, -3), "0.025", __LINE__);
|
||||
test(Number(-25, -3), "-0.025", __LINE__);
|
||||
test(Number(25, 1), "250", __LINE__);
|
||||
test(Number(-25, 1), "-250", __LINE__);
|
||||
test(Number(2, 20), "2e20", __LINE__);
|
||||
test(Number(-2, -20), "-2e-20", __LINE__);
|
||||
// Test the edges
|
||||
// ((exponent < -(25)) || (exponent > -(5)))))
|
||||
// or ((exponent < -(28)) || (exponent > -(8)))))
|
||||
test(Number(2, -10), "0.0000000002");
|
||||
test(Number(2, -11), "2e-11");
|
||||
test(Number(2, -10), "0.0000000002", __LINE__);
|
||||
test(Number(2, -11), "2e-11", __LINE__);
|
||||
|
||||
test(Number(-2, 10), "-20000000000");
|
||||
test(Number(-2, 11), "-2e11");
|
||||
test(Number(-2, 10), "-20000000000", __LINE__);
|
||||
test(Number(-2, 11), "-2e11", __LINE__);
|
||||
|
||||
switch (scale)
|
||||
{
|
||||
case MantissaRange::MantissaScale::Small:
|
||||
|
||||
test(Number::min(), "1e-32753");
|
||||
test(Number::max(), "9999999999999999e32768");
|
||||
test(Number::lowest(), "-9999999999999999e32768");
|
||||
test(Number::min(), "1e-32753", __LINE__);
|
||||
test(Number::max(), "9999999999999999e32768", __LINE__);
|
||||
test(Number::lowest(), "-9999999999999999e32768", __LINE__);
|
||||
{
|
||||
NumberRoundModeGuard const mg(Number::RoundingMode::TowardsZero);
|
||||
|
||||
@@ -1384,61 +1442,132 @@ public:
|
||||
BEAST_EXPECT(maxMantissa == 9'999'999'999'999'999);
|
||||
test(
|
||||
Number{false, (maxMantissa * 1000) + 999, -3, Number::Normalized()},
|
||||
"9999999999999999");
|
||||
"9999999999999999",
|
||||
__LINE__);
|
||||
test(
|
||||
Number{true, (maxMantissa * 1000) + 999, -3, Number::Normalized()},
|
||||
"-9999999999999999");
|
||||
"-9999999999999999",
|
||||
__LINE__);
|
||||
|
||||
test(Number{std::numeric_limits<std::int64_t>::max(), -3}, "9223372036854775");
|
||||
test(
|
||||
Number{std::numeric_limits<std::int64_t>::max(), -3},
|
||||
"9223372036854775",
|
||||
__LINE__);
|
||||
test(
|
||||
-(Number{std::numeric_limits<std::int64_t>::max(), -3}),
|
||||
"-9223372036854775");
|
||||
"-9223372036854775",
|
||||
__LINE__);
|
||||
|
||||
test(
|
||||
Number{std::numeric_limits<std::int64_t>::min(), 0}, "-9223372036854775e3");
|
||||
Number{std::numeric_limits<std::int64_t>::min(), 0},
|
||||
"-9223372036854775e3",
|
||||
__LINE__);
|
||||
test(
|
||||
-(Number{std::numeric_limits<std::int64_t>::min(), 0}),
|
||||
"9223372036854775e3");
|
||||
"9223372036854775e3",
|
||||
__LINE__);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// Test the edges
|
||||
// ((exponent < -(28)) || (exponent > -(8)))))
|
||||
test(Number::min(), "1e-32750");
|
||||
test(Number::max(), "9223372036854775807e32768");
|
||||
test(Number::lowest(), "-9223372036854775807e32768");
|
||||
test(Number::min(), "1e-32750", __LINE__);
|
||||
test(Number::max(), "9223372036854775807e32768", __LINE__);
|
||||
test(Number::lowest(), "-9223372036854775807e32768", __LINE__);
|
||||
{
|
||||
NumberRoundModeGuard const mg(Number::RoundingMode::TowardsZero);
|
||||
|
||||
auto const maxMantissa = Number::maxMantissa();
|
||||
BEAST_EXPECT(maxMantissa == 9'999'999'999'999'999'999ULL);
|
||||
test(
|
||||
Number{false, maxMantissa, 0, Number::Normalized{}}, "9999999999999999990");
|
||||
Number{false, maxMantissa, 0, Number::Normalized{}},
|
||||
"9999999999999999990",
|
||||
__LINE__);
|
||||
test(
|
||||
Number{true, maxMantissa, 0, Number::Normalized{}}, "-9999999999999999990");
|
||||
Number{true, maxMantissa, 0, Number::Normalized{}},
|
||||
"-9999999999999999990",
|
||||
__LINE__);
|
||||
|
||||
test(
|
||||
Number{std::numeric_limits<std::int64_t>::max(), 0}, "9223372036854775807");
|
||||
Number{std::numeric_limits<std::int64_t>::max(), 0},
|
||||
"9223372036854775807",
|
||||
__LINE__);
|
||||
test(
|
||||
-(Number{std::numeric_limits<std::int64_t>::max(), 0}),
|
||||
"-9223372036854775807");
|
||||
"-9223372036854775807",
|
||||
__LINE__);
|
||||
|
||||
// Because the absolute value of min is larger than max, it
|
||||
// will be scaled down to fit under max. Since we're
|
||||
// rounding towards zero, the 8 at the end is dropped.
|
||||
test(
|
||||
Number{std::numeric_limits<std::int64_t>::min(), 0},
|
||||
"-9223372036854775800");
|
||||
test(
|
||||
-(Number{std::numeric_limits<std::int64_t>::min(), 0}),
|
||||
"9223372036854775800");
|
||||
switch (scale)
|
||||
{
|
||||
case MantissaRange::MantissaScale::Large330:
|
||||
// Because the absolute value of min() is larger than max(), it
|
||||
// will be rounded down toward max()
|
||||
test(
|
||||
Number{std::numeric_limits<std::int64_t>::min(), 0},
|
||||
"-9223372036854775807",
|
||||
__LINE__);
|
||||
test(
|
||||
-(Number{std::numeric_limits<std::int64_t>::min(), 0}),
|
||||
"9223372036854775807",
|
||||
__LINE__);
|
||||
break;
|
||||
default:
|
||||
// Because the absolute value of min() is larger than max(), it
|
||||
// will be scaled down to fit under max(). Since we're
|
||||
// rounding towards zero, the 8 at the end is dropped.
|
||||
test(
|
||||
Number{std::numeric_limits<std::int64_t>::min(), 0},
|
||||
"-9223372036854775800",
|
||||
__LINE__);
|
||||
test(
|
||||
-(Number{std::numeric_limits<std::int64_t>::min(), 0}),
|
||||
"9223372036854775800",
|
||||
__LINE__);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
switch (scale)
|
||||
{
|
||||
case MantissaRange::MantissaScale::Large330:
|
||||
// Rounding to nearest, since the mantissa is below the halfway point from
|
||||
// kMaxRep to kMaxRep up, it will be rounded down to kMaxRep
|
||||
test(
|
||||
Number{std::numeric_limits<std::int64_t>::max(), 0} + 1,
|
||||
"9223372036854775807",
|
||||
__LINE__);
|
||||
test(
|
||||
-(Number{std::numeric_limits<std::int64_t>::max(), 0} + 1),
|
||||
"-9223372036854775807",
|
||||
__LINE__);
|
||||
break;
|
||||
default:
|
||||
// Rounding to nearest, since the mantissa is bigger than kMaxRep, the 8
|
||||
// will be dropped, and since that is bigger than 5, the result will be
|
||||
// rounded up from 0 to 1.
|
||||
test(
|
||||
Number{std::numeric_limits<std::int64_t>::max(), 0} + 1,
|
||||
"9223372036854775810",
|
||||
__LINE__);
|
||||
test(
|
||||
-(Number{std::numeric_limits<std::int64_t>::max(), 0} + 1),
|
||||
"-9223372036854775810",
|
||||
__LINE__);
|
||||
break;
|
||||
}
|
||||
// Rounding to nearest, will be rounded up to kMaxRepUp, but for different reasons
|
||||
// depending on the scale. If older than "Large", it rounds up for the same reason
|
||||
// "+1" rounds up. For "Large", since the mantissa is above the halfway point from
|
||||
// kMaxRep to kMaxRepUp, it will be rounded up to kMaxRepUp.
|
||||
test(
|
||||
Number{std::numeric_limits<std::int64_t>::max(), 0} + 1, "9223372036854775810");
|
||||
Number{std::numeric_limits<std::int64_t>::max(), 0} + 2,
|
||||
"9223372036854775810",
|
||||
__LINE__);
|
||||
test(
|
||||
-(Number{std::numeric_limits<std::int64_t>::max(), 0} + 1),
|
||||
"-9223372036854775810");
|
||||
-(Number{std::numeric_limits<std::int64_t>::max(), 0} + 2),
|
||||
"-9223372036854775810",
|
||||
__LINE__);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1776,7 +1905,7 @@ public:
|
||||
}
|
||||
|
||||
void
|
||||
testUpwardRoundsDown()
|
||||
testEdgeCases()
|
||||
{
|
||||
auto const scale = Number::getMantissaScale();
|
||||
{
|
||||
@@ -1800,15 +1929,14 @@ public:
|
||||
|
||||
BigInt const signedDifference = storedValue - exactProduct;
|
||||
|
||||
log << "\n"
|
||||
<< " a = " << fmt(BigInt(kAValue)) << "\n"
|
||||
log << " a = " << fmt(BigInt(kAValue)) << "\n"
|
||||
<< " b = " << fmt(BigInt(kBValue)) << "\n"
|
||||
<< " exact a*b = " << fmt(exactProduct) << "\n"
|
||||
<< " stored = " << fmt(storedValue) << "\n"
|
||||
<< " stored - exact = " << fmt(signedDifference) << "\n"
|
||||
<< " upward = " << (signedDifference >= 0 ? "held" : "VIOLATED") << "\n"
|
||||
<< " stored.mantissa = " << product.mantissa() << "\n"
|
||||
<< " stored.exponent = " << product.exponent() << "\n";
|
||||
<< " stored.exponent = " << product.exponent() << "\n\n";
|
||||
log.flush();
|
||||
|
||||
switch (scale)
|
||||
@@ -1882,15 +2010,14 @@ public:
|
||||
dec const stored = dec(quotient.mantissa()) * pow10(quotient.exponent());
|
||||
dec const diff = stored - exact;
|
||||
|
||||
log << "\n"
|
||||
<< " a = " << aValue << "\n"
|
||||
log << " a = " << aValue << "\n"
|
||||
<< " b = " << bValue << "\n"
|
||||
<< " exact a/b = " << fmt(exact) << "\n"
|
||||
<< " stored a/b = " << fmt(stored) << "\n"
|
||||
<< " stored - exact = " << fmt(diff)
|
||||
<< " (negative => Upward gave value BELOW truth)\n"
|
||||
<< " quotient.mantissa = " << quotient.mantissa() << "\n"
|
||||
<< " quotient.exponent = " << quotient.exponent() << "\n";
|
||||
<< " quotient.exponent = " << quotient.exponent() << "\n\n";
|
||||
log.flush();
|
||||
|
||||
// Upward invariant: stored >= exact. Bug: stored < exact.
|
||||
@@ -1933,15 +2060,14 @@ public:
|
||||
dec const stored = dec(quotient.mantissa()) * pow10(quotient.exponent());
|
||||
dec const diff = stored - exact;
|
||||
|
||||
log << "\n"
|
||||
<< " a = " << aValue << "\n"
|
||||
log << " a = " << aValue << "\n"
|
||||
<< " b = " << bValue << "\n"
|
||||
<< " exact a/b = " << fmt(exact) << "\n"
|
||||
<< " stored a/b = " << fmt(stored) << "\n"
|
||||
<< " stored - exact = " << fmt(diff)
|
||||
<< " (positive => Downward gave value ABOVE truth)\n"
|
||||
<< " quotient.mantissa = " << quotient.mantissa() << "\n"
|
||||
<< " quotient.exponent = " << quotient.exponent() << "\n";
|
||||
<< " quotient.exponent = " << quotient.exponent() << "\n\n";
|
||||
log.flush();
|
||||
|
||||
// invariant: stored <= exact. Bug: stored > exact.
|
||||
@@ -1991,15 +2117,14 @@ public:
|
||||
dec const stored = dec(quotient.mantissa()) * pow10(quotient.exponent());
|
||||
dec const diff = stored - exact;
|
||||
|
||||
log << "\n"
|
||||
<< " a = " << aValue << "\n"
|
||||
log << " a = " << aValue << "\n"
|
||||
<< " b = " << bValue << "\n"
|
||||
<< " exact a/b = " << fmt(exact) << "\n"
|
||||
<< " stored a/b = " << fmt(stored) << "\n"
|
||||
<< " stored - exact = " << fmt(diff)
|
||||
<< " (negative => ToNearest gave value BELOW truth)\n"
|
||||
<< " quotient.mantissa = " << quotient.mantissa() << "\n"
|
||||
<< " quotient.exponent = " << quotient.exponent() << "\n";
|
||||
<< " quotient.exponent = " << quotient.exponent() << "\n\n";
|
||||
log.flush();
|
||||
|
||||
// invariant: stored >= exact. Bug: stored < exact.
|
||||
@@ -2086,7 +2211,7 @@ public:
|
||||
return sums;
|
||||
}();
|
||||
|
||||
log << "\n a = " << a << " (" << fmt(bigA)
|
||||
log << " a = " << a << " (" << fmt(bigA)
|
||||
<< ")\n b = " << b << " (" << fmt(bigB)
|
||||
<< ")\n exact a + b = " << fmt(exact) << "\n";
|
||||
for (auto const& [r, sum] : sums)
|
||||
@@ -2096,6 +2221,7 @@ public:
|
||||
log << std::string(15 - rLabel.length(), ' ') << rLabel << " = "
|
||||
<< fmt(sum.first) << "\n difference = " << fmt(diff) << "\n";
|
||||
}
|
||||
log << "\n";
|
||||
log.flush();
|
||||
|
||||
auto const expectedExponent =
|
||||
@@ -2146,6 +2272,83 @@ public:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
testcase << "normalization cusp: ToNearest and Downward behavior " << to_string(scale);
|
||||
constexpr auto kMaxRep = Number::kMaxRep;
|
||||
|
||||
// Both ToNearest and Downward should round to `below`
|
||||
auto constexpr 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::Normalized{}};
|
||||
|
||||
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 << " 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 but Downward rounds UP, including the
|
||||
// reference values, "above" and "below"
|
||||
|
||||
BEAST_EXPECT(below == above);
|
||||
BEAST_EXPECT(upward == above);
|
||||
BEAST_EXPECT(toNearest == above);
|
||||
|
||||
BEAST_EXPECT(downward < below);
|
||||
|
||||
break;
|
||||
|
||||
case MantissaRange::MantissaScale::LargeLegacy:
|
||||
case MantissaRange::MantissaScale::Large320:
|
||||
// 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:
|
||||
// Covers "Large" and any newly added scales
|
||||
|
||||
// Upward round UP
|
||||
BEAST_EXPECT(upward == above);
|
||||
|
||||
// ToNearest rounds to the strictly closer DOWN neighbor
|
||||
BEAST_EXPECT(toNearest != above);
|
||||
BEAST_EXPECT(toNearest == below);
|
||||
|
||||
// Downward also rounds to `below`
|
||||
BEAST_EXPECT(downward == below);
|
||||
|
||||
// ToNearest rounds to downward
|
||||
BEAST_EXPECT(toNearest == downward);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
@@ -2359,6 +2562,166 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testNumberRoundCuspWithFractionalParts()
|
||||
{
|
||||
auto const scale = Number::getMantissaScale();
|
||||
|
||||
testcase << "normalization cusp: rounding behavior with fractional parts "
|
||||
<< to_string(scale);
|
||||
NumberRoundModeGuard const roundGuard{Number::RoundingMode::ToNearest};
|
||||
|
||||
Number const below{static_cast<std::int64_t>(Number::kMaxRep), 0};
|
||||
Number const above{false, Number::kMaxRepUp, 0, Number::Normalized{}};
|
||||
|
||||
log << "Below: " << below << ", Above: " << above << "\n";
|
||||
|
||||
auto const zeroPointFour = Number(4, -1);
|
||||
auto const zeroPointSix = Number(6, -1);
|
||||
auto const onePointFour = Number(14, -1);
|
||||
auto const onePointFive = Number(15, -1);
|
||||
auto const onePointSix = Number(16, -1);
|
||||
auto const twoPointFour = Number(24, -1);
|
||||
auto const twoPointSix = Number(26, -1);
|
||||
|
||||
auto const operands = std::to_array<Number>({
|
||||
zeroPointFour,
|
||||
zeroPointSix,
|
||||
onePointFour,
|
||||
onePointFive,
|
||||
onePointSix,
|
||||
twoPointFour,
|
||||
twoPointSix,
|
||||
});
|
||||
|
||||
auto const modes = std::to_array<Number::RoundingMode>({
|
||||
Number::RoundingMode::ToNearest,
|
||||
Number::RoundingMode::TowardsZero,
|
||||
Number::RoundingMode::Downward,
|
||||
Number::RoundingMode::Upward,
|
||||
});
|
||||
|
||||
// Addition cases test kMaxRep + Operand
|
||||
for (auto const& mode : modes)
|
||||
{
|
||||
for (auto const& operand : operands)
|
||||
{
|
||||
NumberRoundModeGuard const rg{mode};
|
||||
|
||||
auto const expectedValue = [&]() {
|
||||
if (scale >= MantissaRange::MantissaScale::Large330)
|
||||
{
|
||||
if (mode == Number::RoundingMode::ToNearest && operand < onePointFive)
|
||||
return below;
|
||||
if (mode == Number::RoundingMode::TowardsZero ||
|
||||
mode == Number::RoundingMode::Downward)
|
||||
return below;
|
||||
}
|
||||
if (scale == MantissaRange::MantissaScale::Large320)
|
||||
{
|
||||
if (mode == Number::RoundingMode::ToNearest)
|
||||
{
|
||||
if (operand < zeroPointSix)
|
||||
return below;
|
||||
}
|
||||
if (mode == Number::RoundingMode::TowardsZero ||
|
||||
mode == Number::RoundingMode::Downward)
|
||||
{
|
||||
if (operand >= onePointFour)
|
||||
return below - 7;
|
||||
return below;
|
||||
}
|
||||
}
|
||||
if (scale == MantissaRange::MantissaScale::LargeLegacy)
|
||||
{
|
||||
if (mode == Number::RoundingMode::ToNearest)
|
||||
{
|
||||
if (operand < zeroPointSix)
|
||||
return below;
|
||||
if (operand == zeroPointSix)
|
||||
return below - 7;
|
||||
}
|
||||
if (mode == Number::RoundingMode::TowardsZero ||
|
||||
mode == Number::RoundingMode::Downward)
|
||||
{
|
||||
if (operand >= onePointFour)
|
||||
return below - 7;
|
||||
return below;
|
||||
}
|
||||
if (mode == Number::RoundingMode::Upward && operand <= zeroPointSix)
|
||||
return below - 7;
|
||||
}
|
||||
if (scale == MantissaRange::MantissaScale::Small &&
|
||||
mode == Number::RoundingMode::Upward)
|
||||
return above + 1000;
|
||||
return above;
|
||||
}();
|
||||
|
||||
Number const actual = below + operand;
|
||||
|
||||
std::stringstream ss;
|
||||
ss << "kMaxRep + " << operand << " rounded " << to_string(mode) << " to " << actual
|
||||
<< ". Expected: " << expectedValue;
|
||||
BEAST_EXPECTS(actual == expectedValue, ss.str());
|
||||
}
|
||||
}
|
||||
|
||||
// Subtraction cases test kMaxRepUp - Operand
|
||||
for (auto const& mode : modes)
|
||||
{
|
||||
for (auto const& operand : operands)
|
||||
{
|
||||
NumberRoundModeGuard const rg{mode};
|
||||
|
||||
auto const expectedValue = [&]() {
|
||||
if (scale >= MantissaRange::MantissaScale::Large330)
|
||||
{
|
||||
if (mode == Number::RoundingMode::ToNearest && operand > onePointFive)
|
||||
return below;
|
||||
if (mode == Number::RoundingMode::TowardsZero ||
|
||||
mode == Number::RoundingMode::Downward)
|
||||
return below;
|
||||
}
|
||||
if (scale == MantissaRange::MantissaScale::LargeLegacy ||
|
||||
scale == MantissaRange::MantissaScale::Large320)
|
||||
{
|
||||
if (mode == Number::RoundingMode::ToNearest)
|
||||
{
|
||||
if (operand >= twoPointSix)
|
||||
return below;
|
||||
}
|
||||
if (mode == Number::RoundingMode::TowardsZero)
|
||||
{
|
||||
if (operand >= onePointFour)
|
||||
return below - 7;
|
||||
}
|
||||
if (mode == Number::RoundingMode::Downward)
|
||||
{
|
||||
if (operand <= onePointSix)
|
||||
return below - 7;
|
||||
return below;
|
||||
}
|
||||
}
|
||||
if (scale == MantissaRange::MantissaScale::Small)
|
||||
{
|
||||
if (mode == Number::RoundingMode::Downward)
|
||||
return below - 1000;
|
||||
if (mode == Number::RoundingMode::Upward)
|
||||
return below;
|
||||
}
|
||||
return above;
|
||||
}();
|
||||
|
||||
Number const actual = above - operand;
|
||||
|
||||
std::stringstream ss;
|
||||
ss << "kMaxRepUp - " << operand << " rounded " << to_string(mode) << " to "
|
||||
<< actual << ". Expected: " << expectedValue;
|
||||
BEAST_EXPECTS(actual == expectedValue, ss.str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
run() override
|
||||
{
|
||||
@@ -2388,9 +2751,10 @@ public:
|
||||
testRounding();
|
||||
testInt64();
|
||||
|
||||
testUpwardRoundsDown();
|
||||
testEdgeCases();
|
||||
testNumberAddDirectedSignWrong();
|
||||
testNumberAddToNearestPicksFarther();
|
||||
testNumberRoundCuspWithFractionalParts();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user