Step 2: Add the ability to change the mantissa range

- Update tests. Unfinished.
- TODO: Finish Number tests. Use both modes for STNumber tests. Move
  mantissa_scale into MantissaRange.
This commit is contained in:
Ed Hennis
2025-11-14 02:32:50 -05:00
parent 2eca3dca89
commit 606e3ec0b7
3 changed files with 912 additions and 272 deletions

View File

@@ -234,8 +234,11 @@ public:
static rounding_mode
setround(rounding_mode mode);
enum mantissa_scale { small, large };
static mantissa_scale
getMantissaScale();
static void
setLargeMantissa(bool large);
setMantissaScale(mantissa_scale scale);
inline static internalrep
minMantissa()
@@ -291,6 +294,7 @@ private:
// The range for the mantissa when normalized.
// Use reference_wrapper to avoid making copies, and prevent accidentally
// changing the values inside the range.
static thread_local mantissa_scale scale_;
static thread_local std::reference_wrapper<MantissaRange const> range_;
void
@@ -536,6 +540,32 @@ public:
operator=(NumberRoundModeGuard const&) = delete;
};
// Sets the new scale and restores the old scale when it leaves scope. Since
// Number doesn't have that facility, we'll build it here.
//
// This class may only end up needed in tests
class NumberMantissaScaleGuard
{
Number::mantissa_scale saved_;
public:
explicit NumberMantissaScaleGuard(Number::mantissa_scale scale) noexcept
: saved_{Number::getMantissaScale()}
{
Number::setMantissaScale(scale);
}
~NumberMantissaScaleGuard()
{
Number::setMantissaScale(saved_);
}
NumberMantissaScaleGuard(NumberMantissaScaleGuard const&) = delete;
NumberMantissaScaleGuard&
operator=(NumberMantissaScaleGuard const&) = delete;
};
} // namespace ripple
#endif // XRPL_BASICS_NUMBER_H_INCLUDED

View File

@@ -36,6 +36,7 @@ namespace ripple {
thread_local Number::rounding_mode Number::mode_ = Number::to_nearest;
// TODO: Once the Rules switching is implemented, default to largeRange
thread_local Number::mantissa_scale Number::scale_ = small; // large;
thread_local std::reference_wrapper<MantissaRange const> Number::range_ =
smallRange; // largeRange;
@@ -51,6 +52,22 @@ Number::setround(rounding_mode mode)
return std::exchange(mode_, mode);
}
Number::mantissa_scale
Number::getMantissaScale()
{
return scale_;
}
void
Number::setMantissaScale(mantissa_scale scale)
{
// scale_ and range_ MUST stay in lockstep
if (scale != mantissa_scale::small && scale != mantissa_scale::large)
LogicError("Unknown mantissa scale");
scale_ = scale;
range_ = scale == mantissa_scale::small ? smallRange : largeRange;
}
// Guard
// The Guard class is used to tempoarily add extra digits of
@@ -524,14 +541,61 @@ Number::operator/=(Number const& y)
// Shift by 10^17 gives greatest precision while not overflowing
// uint128_t or the cast back to int64_t
// TODO: Can/should this be made bigger for largeRange?
// log(2^127,10) ~ 38.2
// largeRange.log = 18
// f can be up to 10^(37-18) = 10^19 safely
constexpr uint128_t f = 100'000'000'000'000'000;
static_assert(f == smallRange.min * 100);
// log(2^128,10) ~ 38.5
// largeRange.log = 18, fits in 10^19
// f can be up to 10^(38-19) = 10^19 safely
bool small = Number::scale_ == Number::small;
uint128_t const f =
small ? 100'000'000'000'000'000 : 10'000'000'000'000'000'000;
XRPL_ASSERT_PARTS(
f >= Number::minMantissa() * 10,
"Number::operator/=",
"factor expected size");
// unsigned denominator
uint128_t const dmu{dm};
// correctionFactor can be anything between 10 and f, depending on how much
// extra precision we want to only use for rounding.
uint128_t const correctionFactor = 1'000;
auto const numerator = uint128_t(nm) * f;
static_assert(smallRange.log == 15);
mantissa_ = uint128_t(nm) * f / uint128_t(dm);
exponent_ = ne - de - 17;
static_assert(largeRange.log == 18);
mantissa_ = numerator / dmu;
exponent_ = ne - de - (small ? 17 : 19);
if (!small)
{
// Virtually multiply numerator by correctionFactor. Since that would
// overflow, we'll do that part separately.
// The math for this would work for small mantissas, but we need to
// preserve existing behavior.
//
// Consider:
// ((numerator * correctionFactor) / dmu) / correctionFactor
// = ((numerator / dmu) * correctionFactor) / correctionFactor)
//
// But that assumes infinite precision. With integer math, this is
// equivalent to
//
// = ((numerator / dmu * correctionFactor)
// + ((numerator % dmu) * correctionFactor) / dmu) / correctionFactor
//
// We have already set `mantissa_ = numerator / dmu`. Now we
// compute `remainder = numerator % dmu`, and if it is
// nonzero, we do the rest of the arithmetic. If it's zero, we can skip
// it.
auto const remainder = (numerator % dmu);
if (remainder != 0)
{
mantissa_ *= correctionFactor;
auto const correction = remainder * correctionFactor / dmu;
mantissa_ += correction;
// divide by 1000 by moving the exponent, so we don't lose the
// integer value we just computed
exponent_ -= 3;
}
}
mantissa_ *= np * dp;
normalize();
return *this;
@@ -617,8 +681,10 @@ to_string(Number const& amount)
XRPL_ASSERT(
exponent + 43 > 0, "ripple::to_string(Number) : minimum exponent");
ptrdiff_t const pad_prefix = Number::mantissaLog() + 12;
ptrdiff_t const pad_suffix = Number::mantissaLog() + 8;
auto const mantissaLog = Number::mantissaLog();
ptrdiff_t const pad_prefix = mantissaLog + 12;
ptrdiff_t const pad_suffix = mantissaLog + 8;
std::string const raw_value(std::to_string(mantissa));
std::string val;
@@ -628,7 +694,7 @@ to_string(Number const& amount)
val.append(raw_value);
val.append(pad_suffix, '0');
ptrdiff_t const offset(exponent + pad_prefix + 16);
ptrdiff_t const offset(exponent + pad_prefix + mantissaLog + 1);
auto pre_from(val.begin());
auto const pre_to(val.begin() + offset);

File diff suppressed because it is too large Load Diff