mirror of
https://github.com/XRPLF/rippled.git
synced 2026-01-30 19:45:24 +00:00
1280 lines
32 KiB
C++
1280 lines
32 KiB
C++
#include <xrpl/basics/Number.h>
|
||
// Keep Number.h first to ensure it can build without hidden dependencies
|
||
#include <xrpl/basics/contract.h>
|
||
#include <xrpl/beast/utility/instrumentation.h>
|
||
|
||
#include <algorithm>
|
||
#include <cstddef>
|
||
#include <cstdint>
|
||
#include <iterator>
|
||
#include <limits>
|
||
#include <numeric>
|
||
#include <stdexcept>
|
||
#include <string>
|
||
#include <string_view>
|
||
#include <type_traits>
|
||
#include <utility>
|
||
|
||
#ifdef _MSC_VER
|
||
#pragma message("Using boost::multiprecision::uint128_t and int128_t")
|
||
#endif
|
||
|
||
using uint128_t = ripple::detail::uint128_t;
|
||
using int128_t = ripple::detail::int128_t;
|
||
|
||
namespace xrpl {
|
||
|
||
thread_local Number::rounding_mode Number::mode_ = Number::to_nearest;
|
||
thread_local std::reference_wrapper<MantissaRange const> Number::range_ =
|
||
largeRange;
|
||
|
||
Number::rounding_mode
|
||
Number::getround()
|
||
{
|
||
return mode_;
|
||
}
|
||
|
||
Number::rounding_mode
|
||
Number::setround(rounding_mode mode)
|
||
{
|
||
return std::exchange(mode_, mode);
|
||
}
|
||
|
||
MantissaRange::mantissa_scale
|
||
Number::getMantissaScale()
|
||
{
|
||
return range_.get().scale;
|
||
}
|
||
|
||
void
|
||
Number::setMantissaScale(MantissaRange::mantissa_scale scale)
|
||
{
|
||
if (scale != MantissaRange::small && scale != MantissaRange::large)
|
||
LogicError("Unknown mantissa scale");
|
||
range_ = scale == MantissaRange::small ? smallRange : largeRange;
|
||
}
|
||
|
||
// Guard
|
||
|
||
// The Guard class is used to temporarily add extra digits of
|
||
// precision to an operation. This enables the final result
|
||
// to be correctly rounded to the internal precision of Number.
|
||
|
||
class Number::Guard
|
||
{
|
||
std::uint64_t digits_; // 16 decimal guard digits
|
||
std::uint8_t xbit_ : 1; // has a non-zero digit been shifted off the end
|
||
std::uint8_t sbit_ : 1; // the sign of the guard digits
|
||
|
||
public:
|
||
explicit Guard() : digits_{0}, xbit_{0}, sbit_{0}
|
||
{
|
||
}
|
||
|
||
// set & test the sign bit
|
||
void
|
||
set_positive() noexcept;
|
||
void
|
||
set_negative() noexcept;
|
||
bool
|
||
is_negative() const noexcept;
|
||
|
||
// add a digit
|
||
template <class T>
|
||
void
|
||
push(T d) noexcept;
|
||
|
||
// recover a digit
|
||
unsigned
|
||
pop() 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.
|
||
int
|
||
round() noexcept;
|
||
|
||
// Modify the result to the correctly rounded value
|
||
template <detail::UnsignedMantissa T>
|
||
void
|
||
doRoundUp(
|
||
bool& negative,
|
||
T& mantissa,
|
||
int& exponent,
|
||
internalrep const& minMantissa,
|
||
internalrep const& maxMantissa,
|
||
std::string_view location);
|
||
|
||
// Modify the result to the correctly rounded value
|
||
template <detail::UnsignedMantissa T>
|
||
void
|
||
doRoundDown(
|
||
bool& negative,
|
||
T& mantissa,
|
||
int& exponent,
|
||
internalrep const& minMantissa);
|
||
|
||
// Modify the result to the correctly rounded value
|
||
void
|
||
doRound(rep& drops, std::string_view location);
|
||
|
||
private:
|
||
void
|
||
doPush(unsigned d) noexcept;
|
||
|
||
template <detail::UnsignedMantissa T>
|
||
void
|
||
bringIntoRange(
|
||
bool& negative,
|
||
T& mantissa,
|
||
int& exponent,
|
||
internalrep const& minMantissa);
|
||
};
|
||
|
||
inline void
|
||
Number::Guard::set_positive() noexcept
|
||
{
|
||
sbit_ = 0;
|
||
}
|
||
|
||
inline void
|
||
Number::Guard::set_negative() noexcept
|
||
{
|
||
sbit_ = 1;
|
||
}
|
||
|
||
inline bool
|
||
Number::Guard::is_negative() const noexcept
|
||
{
|
||
return sbit_ == 1;
|
||
}
|
||
|
||
inline void
|
||
Number::Guard::doPush(unsigned d) noexcept
|
||
{
|
||
xbit_ = xbit_ || ((digits_ & 0x0000'0000'0000'000F) != 0);
|
||
digits_ >>= 4;
|
||
digits_ |= (d & 0x0000'0000'0000'000FULL) << 60;
|
||
}
|
||
|
||
template <class T>
|
||
inline void
|
||
Number::Guard::push(T d) noexcept
|
||
{
|
||
doPush(static_cast<unsigned>(d));
|
||
}
|
||
|
||
inline unsigned
|
||
Number::Guard::pop() noexcept
|
||
{
|
||
unsigned d = (digits_ & 0xF000'0000'0000'0000) >> 60;
|
||
digits_ <<= 4;
|
||
return d;
|
||
}
|
||
|
||
// 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() noexcept
|
||
{
|
||
auto mode = Number::getround();
|
||
|
||
if (mode == towards_zero)
|
||
return -1;
|
||
|
||
if (mode == downward)
|
||
{
|
||
if (sbit_)
|
||
{
|
||
if (digits_ > 0 || xbit_)
|
||
return 1;
|
||
}
|
||
return -1;
|
||
}
|
||
|
||
if (mode == upward)
|
||
{
|
||
if (sbit_)
|
||
return -1;
|
||
if (digits_ > 0 || xbit_)
|
||
return 1;
|
||
return -1;
|
||
}
|
||
|
||
// assume round to nearest if mode is not one of the predefined values
|
||
if (digits_ > 0x5000'0000'0000'0000)
|
||
return 1;
|
||
if (digits_ < 0x5000'0000'0000'0000)
|
||
return -1;
|
||
if (xbit_)
|
||
return 1;
|
||
return 0;
|
||
}
|
||
|
||
template <detail::UnsignedMantissa T>
|
||
void
|
||
Number::Guard::bringIntoRange(
|
||
bool& negative,
|
||
T& mantissa,
|
||
int& exponent,
|
||
internalrep const& minMantissa)
|
||
{
|
||
// Bring mantissa back into the minMantissa / maxMantissa range AFTER
|
||
// rounding
|
||
if (mantissa < minMantissa)
|
||
{
|
||
mantissa *= 10;
|
||
--exponent;
|
||
}
|
||
if (exponent < minExponent)
|
||
{
|
||
constexpr Number zero = Number{};
|
||
|
||
negative = false;
|
||
mantissa = zero.mantissa_;
|
||
exponent = zero.exponent_;
|
||
}
|
||
}
|
||
|
||
template <detail::UnsignedMantissa T>
|
||
void
|
||
Number::Guard::doRoundUp(
|
||
bool& negative,
|
||
T& mantissa,
|
||
int& exponent,
|
||
internalrep const& minMantissa,
|
||
internalrep const& maxMantissa,
|
||
std::string_view location)
|
||
{
|
||
auto r = round();
|
||
if (r == 1 || (r == 0 && (mantissa & 1) == 1))
|
||
{
|
||
++mantissa;
|
||
// Ensure mantissa after incrementing fits within both the
|
||
// min/maxMantissa range and is a valid "rep".
|
||
if (mantissa > maxMantissa)
|
||
{
|
||
mantissa /= 10;
|
||
++exponent;
|
||
}
|
||
}
|
||
bringIntoRange(negative, mantissa, exponent, minMantissa);
|
||
if (exponent > maxExponent)
|
||
Throw<std::overflow_error>(std::string{location});
|
||
}
|
||
|
||
template <detail::UnsignedMantissa T>
|
||
void
|
||
Number::Guard::doRoundDown(
|
||
bool& negative,
|
||
T& mantissa,
|
||
int& exponent,
|
||
internalrep const& minMantissa)
|
||
{
|
||
auto r = round();
|
||
if (r == 1 || (r == 0 && (mantissa & 1) == 1))
|
||
{
|
||
--mantissa;
|
||
if (mantissa < minMantissa)
|
||
{
|
||
mantissa *= 10;
|
||
--exponent;
|
||
}
|
||
}
|
||
bringIntoRange(negative, mantissa, exponent, minMantissa);
|
||
}
|
||
|
||
// Modify the result to the correctly rounded value
|
||
void
|
||
Number::Guard::doRound(rep& drops, std::string_view location)
|
||
{
|
||
auto r = round();
|
||
if (r == 1 || (r == 0 && (drops & 1) == 1))
|
||
{
|
||
auto const& range = range_.get();
|
||
if (drops >= range.max)
|
||
{
|
||
static_assert(sizeof(internalrep) == sizeof(rep));
|
||
// This should be impossible, because it's impossible to represent
|
||
// "maxRep + 0.6" in Number, regardless of the scale. There aren't
|
||
// enough digits available. You'd either get a mantissa of "maxRep"
|
||
// or "(maxRep + 1) / 10", neither of which will round up when
|
||
// converting to rep, though the latter might overflow _before_
|
||
// rounding.
|
||
Throw<std::overflow_error>(
|
||
std::string{location}); // LCOV_EXCL_LINE
|
||
}
|
||
++drops;
|
||
}
|
||
if (is_negative())
|
||
drops = -drops;
|
||
}
|
||
|
||
// Number
|
||
|
||
// Safely convert rep (int64) mantissa to internalrep (uint64). If the rep is
|
||
// negative, returns the positive value. This takes a little extra work because
|
||
// converting std::numeric_limits<std::int64_t>::min() flirts with UB, and can
|
||
// vary across compilers.
|
||
Number::internalrep
|
||
Number::externalToInternal(rep mantissa)
|
||
{
|
||
// If the mantissa is already positive, just return it
|
||
if (mantissa >= 0)
|
||
return mantissa;
|
||
|
||
// Cast to unsigned before negating to avoid undefined behavior
|
||
// when v == INT64_MIN (negating INT64_MIN in signed is UB)
|
||
return -static_cast<internalrep>(mantissa);
|
||
}
|
||
|
||
/** Breaks down the number into components, potentially de-normalizing it.
|
||
*
|
||
* Ensures that the mantissa always has range_.log digits.
|
||
*
|
||
*/
|
||
template <detail::UnsignedMantissa Rep>
|
||
std::tuple<bool, Rep, int>
|
||
Number::toInternal(MantissaRange const& range) const
|
||
{
|
||
auto exponent = exponent_;
|
||
bool const negative = mantissa_ < 0;
|
||
auto const sign = negative ? -1 : 1;
|
||
Rep mantissa = static_cast<Rep>(sign * mantissa_);
|
||
|
||
auto const referenceMin = range.referenceMin;
|
||
auto const minMantissa = range.min;
|
||
|
||
if (mantissa != 0 && mantissa >= minMantissa && mantissa < referenceMin)
|
||
{
|
||
// Ensure the mantissa has the correct number of digits
|
||
mantissa *= 10;
|
||
--exponent;
|
||
XRPL_ASSERT_PARTS(
|
||
mantissa >= referenceMin && mantissa < referenceMin * 10,
|
||
"ripple::Number::toInternal()",
|
||
"Number is within reference range and has 'log' digits");
|
||
}
|
||
|
||
return {negative, mantissa, exponent};
|
||
}
|
||
|
||
/** Breaks down the number into components, potentially de-normalizing it.
|
||
*
|
||
* Ensures that the mantissa always has range_.log digits.
|
||
*
|
||
*/
|
||
template <detail::UnsignedMantissa Rep>
|
||
std::tuple<bool, Rep, int>
|
||
Number::toInternal() const
|
||
{
|
||
return toInternal(range_);
|
||
}
|
||
|
||
/** Rebuilds the number from components.
|
||
*
|
||
* If "normalized" is true, the values are expected to be normalized - all
|
||
* in their valid ranges.
|
||
*
|
||
* If "normalized" is false, the values are expected to be "near
|
||
* normalized", meaning that the mantissa has to be modified at most once to
|
||
* bring it back into range.
|
||
*
|
||
*/
|
||
template <bool expectNormal, detail::UnsignedMantissa Rep>
|
||
void
|
||
Number::fromInternal(
|
||
bool negative,
|
||
Rep mantissa,
|
||
int exponent,
|
||
MantissaRange const* pRange)
|
||
{
|
||
if constexpr (std::is_same_v<
|
||
std::bool_constant<expectNormal>,
|
||
std::false_type>)
|
||
{
|
||
if (!pRange)
|
||
throw std::runtime_error("Missing range to Number::fromInternal!");
|
||
auto const& range = *pRange;
|
||
|
||
auto const maxMantissa = range.max;
|
||
auto const minMantissa = range.min;
|
||
|
||
XRPL_ASSERT_PARTS(
|
||
mantissa >= minMantissa,
|
||
"ripple::Number::fromInternal",
|
||
"mantissa large enough");
|
||
|
||
if (mantissa > maxMantissa || mantissa < minMantissa)
|
||
{
|
||
normalize(negative, mantissa, exponent, range.min, maxMantissa);
|
||
}
|
||
|
||
XRPL_ASSERT_PARTS(
|
||
mantissa >= minMantissa && mantissa <= maxMantissa,
|
||
"ripple::Number::fromInternal",
|
||
"mantissa in range");
|
||
}
|
||
|
||
auto const sign = negative ? -1 : 1;
|
||
|
||
mantissa_ = sign * static_cast<rep>(mantissa);
|
||
exponent_ = exponent;
|
||
|
||
XRPL_ASSERT_PARTS(
|
||
(pRange && isnormal(*pRange)) || isnormal(),
|
||
"ripple::Number::fromInternal",
|
||
"Number is normalized");
|
||
}
|
||
|
||
/** Rebuilds the number from components.
|
||
*
|
||
* If "normalized" is true, the values are expected to be normalized - all in
|
||
* their valid ranges.
|
||
*
|
||
* If "normalized" is false, the values are expected to be "near normalized",
|
||
* meaning that the mantissa has to be modified at most once to bring it back
|
||
* into range.
|
||
*
|
||
*/
|
||
template <bool expectNormal, detail::UnsignedMantissa Rep>
|
||
void
|
||
Number::fromInternal(bool negative, Rep mantissa, int exponent)
|
||
{
|
||
MantissaRange const* pRange = nullptr;
|
||
if constexpr (std::is_same_v<
|
||
std::bool_constant<expectNormal>,
|
||
std::false_type>)
|
||
{
|
||
pRange = &Number::range_.get();
|
||
}
|
||
|
||
fromInternal(negative, mantissa, exponent, pRange);
|
||
}
|
||
|
||
constexpr Number
|
||
Number::oneSmall()
|
||
{
|
||
return Number{
|
||
false,
|
||
Number::smallRange.referenceMin,
|
||
-Number::smallRange.log,
|
||
Number::unchecked{}};
|
||
};
|
||
|
||
constexpr Number oneSml = Number::oneSmall();
|
||
|
||
constexpr Number
|
||
Number::oneLarge()
|
||
{
|
||
return Number{
|
||
false,
|
||
Number::largeRange.referenceMin,
|
||
-Number::largeRange.log,
|
||
Number::unchecked{}};
|
||
};
|
||
|
||
constexpr Number oneLrg = Number::oneLarge();
|
||
|
||
Number
|
||
Number::one(MantissaRange const& range)
|
||
{
|
||
if (&range == &smallRange)
|
||
return oneSml;
|
||
XRPL_ASSERT(&range == &largeRange, "Number::one() : valid range");
|
||
return oneLrg;
|
||
}
|
||
|
||
Number
|
||
Number::one()
|
||
{
|
||
return one(range_);
|
||
}
|
||
|
||
// Use the member names in this static function for now so the diff is cleaner
|
||
template <class T>
|
||
void
|
||
doNormalize(
|
||
bool& negative,
|
||
T& mantissa,
|
||
int& exponent,
|
||
MantissaRange::rep const& minMantissa,
|
||
MantissaRange::rep const& maxMantissa)
|
||
{
|
||
auto constexpr minExponent = Number::minExponent;
|
||
auto constexpr maxExponent = Number::maxExponent;
|
||
|
||
using Guard = Number::Guard;
|
||
|
||
constexpr Number zero = Number{};
|
||
if (mantissa == 0 || (mantissa < minMantissa && exponent <= minExponent))
|
||
{
|
||
mantissa = zero.mantissa_;
|
||
exponent = zero.exponent_;
|
||
negative = false;
|
||
return;
|
||
}
|
||
|
||
auto m = mantissa;
|
||
while ((m < minMantissa) && (exponent > minExponent))
|
||
{
|
||
m *= 10;
|
||
--exponent;
|
||
}
|
||
Guard g;
|
||
if (negative)
|
||
g.set_negative();
|
||
while (m > maxMantissa)
|
||
{
|
||
if (exponent >= maxExponent)
|
||
throw std::overflow_error("Number::normalize 1");
|
||
g.push(m % 10);
|
||
m /= 10;
|
||
++exponent;
|
||
}
|
||
if ((exponent < minExponent) || (m == 0))
|
||
{
|
||
mantissa = zero.mantissa_;
|
||
exponent = zero.exponent_;
|
||
negative = false;
|
||
return;
|
||
}
|
||
|
||
XRPL_ASSERT_PARTS(
|
||
m <= maxMantissa,
|
||
"xrpl::doNormalize",
|
||
"intermediate mantissa fits in int64");
|
||
mantissa = m;
|
||
|
||
g.doRoundUp(
|
||
negative,
|
||
mantissa,
|
||
exponent,
|
||
minMantissa,
|
||
maxMantissa,
|
||
"Number::normalize 2");
|
||
|
||
XRPL_ASSERT_PARTS(
|
||
mantissa >= minMantissa && mantissa <= maxMantissa,
|
||
"xrpl::doNormalize",
|
||
"final mantissa fits in range");
|
||
XRPL_ASSERT_PARTS(
|
||
exponent >= minExponent && exponent <= maxExponent,
|
||
"xrpl::doNormalize",
|
||
"final exponent fits in range");
|
||
}
|
||
|
||
template <>
|
||
void
|
||
Number::normalize<uint128_t>(
|
||
bool& negative,
|
||
uint128_t& mantissa,
|
||
int& exponent,
|
||
internalrep const& minMantissa,
|
||
internalrep const& maxMantissa)
|
||
{
|
||
doNormalize(negative, mantissa, exponent, minMantissa, maxMantissa);
|
||
}
|
||
|
||
template <>
|
||
void
|
||
Number::normalize<unsigned long long>(
|
||
bool& negative,
|
||
unsigned long long& mantissa,
|
||
int& exponent,
|
||
internalrep const& minMantissa,
|
||
internalrep const& maxMantissa)
|
||
{
|
||
doNormalize(negative, mantissa, exponent, minMantissa, maxMantissa);
|
||
}
|
||
|
||
template <>
|
||
void
|
||
Number::normalize<unsigned long>(
|
||
bool& negative,
|
||
unsigned long& mantissa,
|
||
int& exponent,
|
||
internalrep const& minMantissa,
|
||
internalrep const& maxMantissa)
|
||
{
|
||
doNormalize(negative, mantissa, exponent, minMantissa, maxMantissa);
|
||
}
|
||
|
||
void
|
||
Number::normalize(MantissaRange const& range)
|
||
{
|
||
auto [negative, mantissa, exponent] = toInternal(range);
|
||
|
||
normalize(negative, mantissa, exponent, range.min, range.max);
|
||
|
||
fromInternal(negative, mantissa, exponent, &range);
|
||
}
|
||
|
||
void
|
||
Number::normalize()
|
||
{
|
||
normalize(range_);
|
||
}
|
||
|
||
// Copy the number, but set a new exponent. Because the mantissa doesn't change,
|
||
// the result will be "mostly" normalized, but the exponent could go out of
|
||
// range.
|
||
Number
|
||
Number::shiftExponent(int exponentDelta) const
|
||
{
|
||
XRPL_ASSERT_PARTS(isnormal(), "xrpl::Number::shiftExponent", "normalized");
|
||
|
||
Number result = *this;
|
||
|
||
result.exponent_ += exponentDelta;
|
||
|
||
if (result.exponent_ >= maxExponent)
|
||
throw std::overflow_error("Number::shiftExponent");
|
||
if (result.exponent_ < minExponent)
|
||
{
|
||
return Number{};
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
Number::Number(bool negative, internalrep mantissa, int exponent, normalized)
|
||
{
|
||
auto const& range = range_.get();
|
||
normalize(negative, mantissa, exponent, range.min, range.max);
|
||
fromInternal(negative, mantissa, exponent, &range);
|
||
}
|
||
|
||
Number&
|
||
Number::operator+=(Number const& y)
|
||
{
|
||
auto const& range = range_.get();
|
||
|
||
constexpr Number zero = Number{};
|
||
if (y == zero)
|
||
return *this;
|
||
if (*this == zero)
|
||
{
|
||
*this = y;
|
||
return *this;
|
||
}
|
||
if (*this == -y)
|
||
{
|
||
*this = zero;
|
||
return *this;
|
||
}
|
||
|
||
XRPL_ASSERT(
|
||
isnormal(range) && y.isnormal(range),
|
||
"xrpl::Number::operator+=(Number) : is normal");
|
||
// *n = negative
|
||
// *s = sign
|
||
// *m = mantissa
|
||
// *e = exponent
|
||
|
||
// Need to use uint128_t, because large mantissas can overflow when added
|
||
// together.
|
||
auto [xn, xm, xe] = toInternal<uint128_t>(range);
|
||
|
||
auto [yn, ym, ye] = y.toInternal<uint128_t>(range);
|
||
|
||
Guard g;
|
||
if (xe < ye)
|
||
{
|
||
if (xn)
|
||
g.set_negative();
|
||
do
|
||
{
|
||
g.push(xm % 10);
|
||
xm /= 10;
|
||
++xe;
|
||
} while (xe < ye);
|
||
}
|
||
else if (xe > ye)
|
||
{
|
||
if (yn)
|
||
g.set_negative();
|
||
do
|
||
{
|
||
g.push(ym % 10);
|
||
ym /= 10;
|
||
++ye;
|
||
} while (xe > ye);
|
||
}
|
||
|
||
auto const& minMantissa = range.min;
|
||
auto const& maxMantissa = range.max;
|
||
|
||
if (xn == yn)
|
||
{
|
||
xm += ym;
|
||
if (xm > maxMantissa)
|
||
{
|
||
g.push(xm % 10);
|
||
xm /= 10;
|
||
++xe;
|
||
}
|
||
g.doRoundUp(
|
||
xn, xm, xe, minMantissa, maxMantissa, "Number::addition overflow");
|
||
}
|
||
else
|
||
{
|
||
if (xm > ym)
|
||
{
|
||
xm = xm - ym;
|
||
}
|
||
else
|
||
{
|
||
xm = ym - xm;
|
||
xe = ye;
|
||
xn = yn;
|
||
}
|
||
while (xm < minMantissa)
|
||
{
|
||
xm *= 10;
|
||
xm -= g.pop();
|
||
--xe;
|
||
}
|
||
g.doRoundDown(xn, xm, xe, minMantissa);
|
||
}
|
||
|
||
normalize(xn, xm, xe, minMantissa, maxMantissa);
|
||
fromInternal(xn, xm, xe, &range);
|
||
return *this;
|
||
}
|
||
|
||
// Optimization equivalent to:
|
||
// auto r = static_cast<unsigned>(u % 10);
|
||
// u /= 10;
|
||
// return r;
|
||
// Derived from Hacker's Delight Second Edition Chapter 10
|
||
// by Henry S. Warren, Jr.
|
||
static inline unsigned
|
||
divu10(uint128_t& u)
|
||
{
|
||
// q = u * 0.75
|
||
auto q = (u >> 1) + (u >> 2);
|
||
// iterate towards q = u * 0.8
|
||
q += q >> 4;
|
||
q += q >> 8;
|
||
q += q >> 16;
|
||
q += q >> 32;
|
||
q += q >> 64;
|
||
// q /= 8 approximately == u / 10
|
||
q >>= 3;
|
||
// r = u - q * 10 approximately == u % 10
|
||
auto r = static_cast<unsigned>(u - ((q << 3) + (q << 1)));
|
||
// correction c is 1 if r >= 10 else 0
|
||
auto c = (r + 6) >> 4;
|
||
u = q + c;
|
||
r -= c * 10;
|
||
return r;
|
||
}
|
||
|
||
Number&
|
||
Number::operator*=(Number const& y)
|
||
{
|
||
auto const& range = range_.get();
|
||
|
||
constexpr Number zero = Number{};
|
||
if (*this == zero)
|
||
return *this;
|
||
if (y == zero)
|
||
{
|
||
*this = y;
|
||
return *this;
|
||
}
|
||
// *n = negative
|
||
// *s = sign
|
||
// *m = mantissa
|
||
// *e = exponent
|
||
|
||
auto [xn, xm, xe] = toInternal(range);
|
||
int xs = xn ? -1 : 1;
|
||
|
||
auto [yn, ym, ye] = y.toInternal(range);
|
||
int ys = yn ? -1 : 1;
|
||
|
||
auto zm = uint128_t(xm) * uint128_t(ym);
|
||
auto ze = xe + ye;
|
||
auto zs = xs * ys;
|
||
bool zn = (zs == -1);
|
||
Guard g;
|
||
if (zn)
|
||
g.set_negative();
|
||
|
||
auto const& minMantissa = range.min;
|
||
auto const& maxMantissa = range.max;
|
||
|
||
while (zm > maxMantissa)
|
||
{
|
||
// The following is optimization for:
|
||
// g.push(static_cast<unsigned>(zm % 10));
|
||
// zm /= 10;
|
||
g.push(divu10(zm));
|
||
++ze;
|
||
}
|
||
xm = static_cast<internalrep>(zm);
|
||
xe = ze;
|
||
g.doRoundUp(
|
||
zn,
|
||
xm,
|
||
xe,
|
||
minMantissa,
|
||
maxMantissa,
|
||
"Number::multiplication overflow : exponent is " + std::to_string(xe));
|
||
|
||
normalize(zn, xm, xe, minMantissa, maxMantissa);
|
||
fromInternal(zn, xm, xe, &range);
|
||
return *this;
|
||
}
|
||
|
||
Number&
|
||
Number::operator/=(Number const& y)
|
||
{
|
||
auto const& range = range_.get();
|
||
|
||
constexpr Number zero = Number{};
|
||
if (y == zero)
|
||
throw std::overflow_error("Number: divide by 0");
|
||
if (*this == zero)
|
||
return *this;
|
||
// n* = numerator
|
||
// d* = denominator
|
||
// *p = negative (positive?)
|
||
// *s = sign
|
||
// *m = mantissa
|
||
// *e = exponent
|
||
|
||
auto [np, nm, ne] = toInternal(range);
|
||
int ns = (np ? -1 : 1);
|
||
|
||
auto [dp, dm, de] = y.toInternal(range);
|
||
int ds = (dp ? -1 : 1);
|
||
|
||
auto const& minMantissa = range.min;
|
||
auto const& maxMantissa = range.max;
|
||
|
||
// 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^128,10) ~ 38.5
|
||
// largeRange.log = 18, fits in 10^19
|
||
// f can be up to 10^(38-19) = 10^19 safely
|
||
static_assert(smallRange.log == 15);
|
||
static_assert(largeRange.log == 18);
|
||
bool small = range.scale == MantissaRange::small;
|
||
uint128_t const f =
|
||
small ? 100'000'000'000'000'000 : 10'000'000'000'000'000'000ULL;
|
||
XRPL_ASSERT_PARTS(
|
||
f >= minMantissa * 10, "Number::operator/=", "factor expected size");
|
||
|
||
// unsigned denominator
|
||
auto const dmu = static_cast<uint128_t>(dm);
|
||
// correctionFactor can be anything between 10 and f, depending on how much
|
||
// extra precision we want to only use for rounding with the
|
||
// largeRange. Three digits seems like plenty, and is more than
|
||
// the smallRange uses.
|
||
uint128_t const correctionFactor = 1'000;
|
||
|
||
auto const numerator = uint128_t(nm) * f;
|
||
|
||
auto zm = numerator / dmu;
|
||
auto ze = ne - de - (small ? 17 : 19);
|
||
bool zn = (ns * ds) < 0;
|
||
if (!small)
|
||
{
|
||
// Virtually multiply numerator by correctionFactor. Since that would
|
||
// overflow in the existing uint128_t, 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)
|
||
{
|
||
zm *= correctionFactor;
|
||
auto const correction = remainder * correctionFactor / dmu;
|
||
zm += correction;
|
||
// divide by 1000 by moving the exponent, so we don't lose the
|
||
// integer value we just computed
|
||
ze -= 3;
|
||
}
|
||
}
|
||
normalize(zn, zm, ze, minMantissa, maxMantissa);
|
||
fromInternal(zn, zm, ze, &range);
|
||
XRPL_ASSERT_PARTS(
|
||
isnormal(range), "xrpl::Number::operator/=", "result is normalized");
|
||
|
||
return *this;
|
||
}
|
||
|
||
Number::operator rep() const
|
||
{
|
||
rep drops = mantissa();
|
||
int offset = exponent();
|
||
Guard g;
|
||
if (drops != 0)
|
||
{
|
||
if (drops < 0)
|
||
{
|
||
g.set_negative();
|
||
drops = -drops;
|
||
}
|
||
for (; offset < 0; ++offset)
|
||
{
|
||
g.push(drops % 10);
|
||
drops /= 10;
|
||
}
|
||
for (; offset > 0; --offset)
|
||
{
|
||
if (drops >= largeRange.min)
|
||
throw std::overflow_error("Number::operator rep() overflow");
|
||
drops *= 10;
|
||
}
|
||
g.doRound(drops, "Number::operator rep() rounding overflow");
|
||
}
|
||
return drops;
|
||
}
|
||
|
||
Number
|
||
Number::truncate() const noexcept
|
||
{
|
||
if (exponent_ >= 0 || mantissa_ == 0)
|
||
return *this;
|
||
|
||
Number ret = *this;
|
||
while (ret.exponent_ < 0 && ret.mantissa_ != 0)
|
||
{
|
||
ret.exponent_ += 1;
|
||
ret.mantissa_ /= rep(10);
|
||
}
|
||
// We are guaranteed that normalize() will never throw an exception
|
||
// because exponent is either negative or zero at this point.
|
||
ret.normalize();
|
||
return ret;
|
||
}
|
||
|
||
std::string
|
||
to_string(Number const& amount)
|
||
{
|
||
auto const& range = Number::range_.get();
|
||
|
||
// keep full internal accuracy, but make more human friendly if possible
|
||
constexpr Number zero = Number{};
|
||
if (amount == zero)
|
||
return "0";
|
||
|
||
// The mantissa must have a set number of decimal places for this to work
|
||
auto [negative, mantissa, exponent] = amount.toInternal(range);
|
||
|
||
// Use scientific notation for exponents that are too small or too large
|
||
auto const rangeLog = range.log;
|
||
if (((exponent != 0 && amount.exponent() != 0) &&
|
||
((exponent < -(rangeLog + 10)) || (exponent > -(rangeLog - 10)))))
|
||
{
|
||
// Remove trailing zeroes from the mantissa.
|
||
while (mantissa != 0 && mantissa % 10 == 0 &&
|
||
exponent < Number::maxExponent)
|
||
{
|
||
mantissa /= 10;
|
||
++exponent;
|
||
}
|
||
std::string ret = negative ? "-" : "";
|
||
ret.append(std::to_string(mantissa));
|
||
if (exponent != 0)
|
||
{
|
||
ret.append(1, 'e');
|
||
ret.append(std::to_string(exponent));
|
||
}
|
||
return ret;
|
||
}
|
||
|
||
XRPL_ASSERT(
|
||
exponent + 43 > 0, "xrpl::to_string(Number) : minimum exponent");
|
||
|
||
ptrdiff_t const pad_prefix = rangeLog + 12;
|
||
ptrdiff_t const pad_suffix = rangeLog + 8;
|
||
|
||
std::string const raw_value(std::to_string(mantissa));
|
||
std::string val;
|
||
|
||
val.reserve(raw_value.length() + pad_prefix + pad_suffix);
|
||
val.append(pad_prefix, '0');
|
||
val.append(raw_value);
|
||
val.append(pad_suffix, '0');
|
||
|
||
ptrdiff_t const offset(exponent + pad_prefix + rangeLog + 1);
|
||
|
||
auto pre_from(val.begin());
|
||
auto const pre_to(val.begin() + offset);
|
||
|
||
auto const post_from(val.begin() + offset);
|
||
auto post_to(val.end());
|
||
|
||
// Crop leading zeroes. Take advantage of the fact that there's always a
|
||
// fixed amount of leading zeroes and skip them.
|
||
if (std::distance(pre_from, pre_to) > pad_prefix)
|
||
pre_from += pad_prefix;
|
||
|
||
XRPL_ASSERT(
|
||
post_to >= post_from, "xrpl::to_string(Number) : first distance check");
|
||
|
||
pre_from = std::find_if(pre_from, pre_to, [](char c) { return c != '0'; });
|
||
|
||
// Crop trailing zeroes. Take advantage of the fact that there's always a
|
||
// fixed amount of trailing zeroes and skip them.
|
||
if (std::distance(post_from, post_to) > pad_suffix)
|
||
post_to -= pad_suffix;
|
||
|
||
XRPL_ASSERT(
|
||
post_to >= post_from,
|
||
"xrpl::to_string(Number) : second distance check");
|
||
|
||
post_to = std::find_if(
|
||
std::make_reverse_iterator(post_to),
|
||
std::make_reverse_iterator(post_from),
|
||
[](char c) { return c != '0'; })
|
||
.base();
|
||
|
||
std::string ret;
|
||
|
||
if (negative)
|
||
ret.append(1, '-');
|
||
|
||
// Assemble the output:
|
||
if (pre_from == pre_to)
|
||
ret.append(1, '0');
|
||
else
|
||
ret.append(pre_from, pre_to);
|
||
|
||
if (post_to != post_from)
|
||
{
|
||
ret.append(1, '.');
|
||
ret.append(post_from, post_to);
|
||
}
|
||
|
||
return ret;
|
||
}
|
||
|
||
// Returns f^n
|
||
// Uses a log_2(n) number of multiplications
|
||
|
||
Number
|
||
power(Number const& f, unsigned n)
|
||
{
|
||
if (n == 0)
|
||
return Number::one();
|
||
if (n == 1)
|
||
return f;
|
||
auto r = power(f, n / 2);
|
||
r *= r;
|
||
if (n % 2 != 0)
|
||
r *= f;
|
||
return r;
|
||
}
|
||
|
||
Number
|
||
Number::root(MantissaRange const& range, Number f, unsigned d)
|
||
{
|
||
constexpr Number zero = Number{};
|
||
auto const one = Number::one(range);
|
||
|
||
if (f == one || d == 1)
|
||
return f;
|
||
if (d == 0)
|
||
{
|
||
if (f == -one)
|
||
return one;
|
||
if (abs(f) < one)
|
||
return zero;
|
||
throw std::overflow_error("Number::root infinity");
|
||
}
|
||
if (f < zero && d % 2 == 0)
|
||
throw std::overflow_error("Number::root nan");
|
||
if (f == zero)
|
||
return f;
|
||
|
||
auto const [e, di] = [&]() {
|
||
auto const [negative, mantissa, exponent] = f.toInternal(range);
|
||
|
||
// Scale f into the range (0, 1) such that the scale change (e) is a
|
||
// multiple of the root (d)
|
||
auto e = exponent + range.log + 1;
|
||
auto const di = static_cast<int>(d);
|
||
auto ex = [e = e, di = di]() // Euclidean remainder of e/d
|
||
{
|
||
int k = (e >= 0 ? e : e - (di - 1)) / di;
|
||
int k2 = e - k * di;
|
||
if (k2 == 0)
|
||
return 0;
|
||
return di - k2;
|
||
}();
|
||
e += ex;
|
||
f = f.shiftExponent(-e); // f /= 10^e;
|
||
return std::make_tuple(e, di);
|
||
}();
|
||
|
||
XRPL_ASSERT_PARTS(
|
||
e % di == 0, "xrpl::root(Number, unsigned)", "e is divisible by d");
|
||
XRPL_ASSERT_PARTS(
|
||
f.isnormal(range), "xrpl::root(Number, unsigned)", "f is normalized");
|
||
bool neg = false;
|
||
if (f < zero)
|
||
{
|
||
neg = true;
|
||
f = -f;
|
||
}
|
||
|
||
// Quadratic least squares curve fit of f^(1/d) in the range [0, 1]
|
||
auto const D = ((6 * di + 11) * di + 6) * di + 1;
|
||
auto const a0 = 3 * di * ((2 * di - 3) * di + 1);
|
||
auto const a1 = 24 * di * (2 * di - 1);
|
||
auto const a2 = -30 * (di - 1) * di;
|
||
Number r = ((Number{a2} * f + Number{a1}) * f + Number{a0}) / Number{D};
|
||
if (neg)
|
||
{
|
||
f = -f;
|
||
r = -r;
|
||
}
|
||
|
||
// Newton–Raphson iteration of f^(1/d) with initial guess r
|
||
// halt when r stops changing, checking for bouncing on the last iteration
|
||
Number rm1{};
|
||
Number rm2{};
|
||
do
|
||
{
|
||
rm2 = rm1;
|
||
rm1 = r;
|
||
r = (Number(d - 1) * r + f / power(r, d - 1)) / Number(d);
|
||
} while (r != rm1 && r != rm2);
|
||
|
||
// return r * 10^(e/d) to reverse scaling
|
||
auto const result = r.shiftExponent(e / di);
|
||
XRPL_ASSERT_PARTS(
|
||
result.isnormal(range),
|
||
"xrpl::root(Number, unsigned)",
|
||
"result is normalized");
|
||
return result;
|
||
}
|
||
|
||
// Returns f^(1/d)
|
||
// Uses Newton–Raphson iterations until the result stops changing
|
||
// to find the non-negative root of the polynomial g(x) = x^d - f
|
||
|
||
// This function, and power(Number f, unsigned n, unsigned d)
|
||
// treat corner cases such as 0 roots as advised by Annex F of
|
||
// the C standard, which itself is consistent with the IEEE
|
||
// floating point standards.
|
||
|
||
Number
|
||
root(Number f, unsigned d)
|
||
{
|
||
auto const& range = Number::range_.get();
|
||
return Number::root(range, f, d);
|
||
}
|
||
|
||
Number
|
||
root2(Number f)
|
||
{
|
||
auto const& range = Number::range_.get();
|
||
constexpr Number zero = Number{};
|
||
auto const one = Number::one(range);
|
||
|
||
if (f == one)
|
||
return f;
|
||
if (f < zero)
|
||
throw std::overflow_error("Number::root nan");
|
||
if (f == zero)
|
||
return f;
|
||
|
||
auto const e = [&]() {
|
||
auto const [negative, mantissa, exponent] = f.toInternal(range);
|
||
|
||
// Scale f into the range (0, 1) such that f's exponent is a
|
||
// multiple of d
|
||
auto e = exponent + range.log + 1;
|
||
if (e % 2 != 0)
|
||
++e;
|
||
f = f.shiftExponent(-e); // f /= 10^e;
|
||
return e;
|
||
}();
|
||
XRPL_ASSERT_PARTS(
|
||
f.isnormal(range), "xrpl::root2(Number)", "f is normalized");
|
||
|
||
// Quadratic least squares curve fit of f^(1/d) in the range [0, 1]
|
||
auto const D = 105;
|
||
auto const a0 = 18;
|
||
auto const a1 = 144;
|
||
auto const a2 = -60;
|
||
Number r = ((Number{a2} * f + Number{a1}) * f + Number{a0}) / Number{D};
|
||
|
||
// Newton–Raphson iteration of f^(1/2) with initial guess r
|
||
// halt when r stops changing, checking for bouncing on the last iteration
|
||
Number rm1{};
|
||
Number rm2{};
|
||
do
|
||
{
|
||
rm2 = rm1;
|
||
rm1 = r;
|
||
r = (r + f / r) / Number(2);
|
||
} while (r != rm1 && r != rm2);
|
||
|
||
// return r * 10^(e/2) to reverse scaling
|
||
auto const result = r.shiftExponent(e / 2);
|
||
XRPL_ASSERT_PARTS(
|
||
result.isnormal(range), "xrpl::root2(Number)", "result is normalized");
|
||
|
||
return result;
|
||
}
|
||
|
||
// Returns f^(n/d)
|
||
|
||
Number
|
||
power(Number const& f, unsigned n, unsigned d)
|
||
{
|
||
auto const& range = Number::range_.get();
|
||
|
||
constexpr Number zero = Number{};
|
||
auto const one = Number::one(range);
|
||
|
||
if (f == one)
|
||
return f;
|
||
auto g = std::gcd(n, d);
|
||
if (g == 0)
|
||
throw std::overflow_error("Number::power nan");
|
||
if (d == 0)
|
||
{
|
||
if (f == -one)
|
||
return one;
|
||
if (abs(f) < one)
|
||
return zero;
|
||
// abs(f) > one
|
||
throw std::overflow_error("Number::power infinity");
|
||
}
|
||
if (n == 0)
|
||
return one;
|
||
n /= g;
|
||
d /= g;
|
||
if ((n % 2) == 1 && (d % 2) == 0 && f < zero)
|
||
throw std::overflow_error("Number::power nan");
|
||
return Number::root(range, power(f, n), d);
|
||
}
|
||
|
||
} // namespace xrpl
|