Files
rippled/src/libxrpl/protocol/IOUAmount.cpp
2026-02-19 23:30:00 +00:00

313 lines
8.1 KiB
C++

#include <xrpl/protocol/IOUAmount.h>
// Do not remove. Forces IOUAmount.h to stay first, to verify it can compile
// without any hidden dependencies
#include <xrpl/basics/LocalValue.h>
#include <xrpl/basics/Number.h>
#include <xrpl/basics/contract.h>
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/protocol/STAmount.h>
#include <boost/multiprecision/cpp_int.hpp>
#include <algorithm>
#include <cstdint>
#include <iterator>
#include <limits>
#include <stdexcept>
#include <string>
#include <vector>
namespace xrpl {
namespace {
// Use a static inside a function to help prevent order-of-initialization issues
LocalValue<bool>&
getStaticSTNumberSwitchover()
{
static LocalValue<bool> r{true};
return r;
}
} // namespace
bool
getSTNumberSwitchover()
{
return *getStaticSTNumberSwitchover();
}
void
setSTNumberSwitchover(bool v)
{
*getStaticSTNumberSwitchover() = v;
}
/* The range for the mantissa when normalized */
// log(2^63,10) ~ 18.96
//
static std::int64_t constexpr minMantissa = STAmount::cMinValue;
static std::int64_t constexpr maxMantissa = STAmount::cMaxValue;
/* The range for the exponent when normalized */
static int constexpr minExponent = STAmount::cMinOffset;
static int constexpr maxExponent = STAmount::cMaxOffset;
IOUAmount
IOUAmount::fromNumber(Number const& number)
{
// Need to create a default IOUAmount and assign directly so it doesn't try
// to normalize, which calls fromNumber
IOUAmount result{};
std::tie(result.mantissa_, result.exponent_) =
number.normalizeToRange(minMantissa, maxMantissa);
return result;
}
IOUAmount
IOUAmount::minPositiveAmount()
{
return IOUAmount(minMantissa, minExponent);
}
void
IOUAmount::normalize()
{
if (mantissa_ == 0)
{
*this = beast::zero;
return;
}
if (getSTNumberSwitchover())
{
Number const v{mantissa_, exponent_};
*this = fromNumber(v);
if (exponent_ > maxExponent)
Throw<std::overflow_error>("value overflow");
if (exponent_ < minExponent)
*this = beast::zero;
return;
}
bool const negative = (mantissa_ < 0);
if (negative)
mantissa_ = -mantissa_;
while ((mantissa_ < minMantissa) && (exponent_ > minExponent))
{
mantissa_ *= 10;
--exponent_;
}
while (mantissa_ > maxMantissa)
{
if (exponent_ >= maxExponent)
Throw<std::overflow_error>("IOUAmount::normalize");
mantissa_ /= 10;
++exponent_;
}
if ((exponent_ < minExponent) || (mantissa_ < minMantissa))
{
*this = beast::zero;
return;
}
if (exponent_ > maxExponent)
Throw<std::overflow_error>("value overflow");
if (negative)
mantissa_ = -mantissa_;
}
IOUAmount::IOUAmount(Number const& other) : IOUAmount(fromNumber(other))
{
if (exponent_ > maxExponent)
Throw<std::overflow_error>("value overflow");
if (exponent_ < minExponent)
*this = beast::zero;
}
IOUAmount&
IOUAmount::operator+=(IOUAmount const& other)
{
if (other == beast::zero)
return *this;
if (*this == beast::zero)
{
*this = other;
return *this;
}
if (getSTNumberSwitchover())
{
*this = IOUAmount{Number{*this} + Number{other}};
return *this;
}
auto m = other.mantissa_;
auto e = other.exponent_;
while (exponent_ < e)
{
mantissa_ /= 10;
++exponent_;
}
while (e < exponent_)
{
m /= 10;
++e;
}
// This addition cannot overflow an std::int64_t but we may throw from
// normalize if the result isn't representable.
mantissa_ += m;
if (mantissa_ >= -10 && mantissa_ <= 10)
{
*this = beast::zero;
return *this;
}
normalize();
return *this;
}
std::string
to_string(IOUAmount const& amount)
{
return to_string(Number{amount});
}
IOUAmount
mulRatio(IOUAmount const& amt, std::uint32_t num, std::uint32_t den, bool roundUp)
{
using namespace boost::multiprecision;
if (!den)
Throw<std::runtime_error>("division by zero");
// A vector with the value 10^index for indexes from 0 to 29
// The largest intermediate value we expect is 2^96, which
// is less than 10^29
static auto const powerTable = [] {
std::vector<uint128_t> result;
result.reserve(30); // 2^96 is largest intermediate result size
uint128_t cur(1);
for (int i = 0; i < 30; ++i)
{
result.push_back(cur);
cur *= 10;
};
return result;
}();
// Return floor(log10(v))
// Note: Returns -1 for v == 0
static auto log10Floor = [](uint128_t const& v) {
// Find the index of the first element >= the requested element, the
// index is the log of the element in the log table.
auto const l = std::lower_bound(powerTable.begin(), powerTable.end(), v);
int index = std::distance(powerTable.begin(), l);
// If we're not equal, subtract to get the floor
if (*l != v)
--index;
return index;
};
// Return ceil(log10(v))
static auto log10Ceil = [](uint128_t const& v) {
// Find the index of the first element >= the requested element, the
// index is the log of the element in the log table.
auto const l = std::lower_bound(powerTable.begin(), powerTable.end(), v);
return int(std::distance(powerTable.begin(), l));
};
static auto const fl64 = log10Floor(std::numeric_limits<std::int64_t>::max());
bool const neg = amt.mantissa() < 0;
uint128_t const den128(den);
// a 32 value * a 64 bit value and stored in a 128 bit value. This will
// never overflow
uint128_t const mul = uint128_t(neg ? -amt.mantissa() : amt.mantissa()) * uint128_t(num);
auto low = mul / den128;
uint128_t rem(mul - low * den128);
int exponent = amt.exponent();
if (rem)
{
// Mathematically, the result is low + rem/den128. However, since this
// uses integer division rem/den128 will be zero. Scale the result so
// low does not overflow the largest amount we can store in the mantissa
// and (rem/den128) is as large as possible. Scale by multiplying low
// and rem by 10 and subtracting one from the exponent. We could do this
// with a loop, but it's more efficient to use logarithms.
auto const roomToGrow = fl64 - log10Ceil(low);
if (roomToGrow > 0)
{
exponent -= roomToGrow;
low *= powerTable[roomToGrow];
rem *= powerTable[roomToGrow];
}
auto const addRem = rem / den128;
low += addRem;
rem = rem - addRem * den128;
}
// The largest result we can have is ~2^95, which overflows the 64 bit
// result we can store in the mantissa. Scale result down by dividing by ten
// and adding one to the exponent until the low will fit in the 64-bit
// mantissa. Use logarithms to avoid looping.
bool hasRem = bool(rem);
auto const mustShrink = log10Ceil(low) - fl64;
if (mustShrink > 0)
{
uint128_t const sav(low);
exponent += mustShrink;
low /= powerTable[mustShrink];
if (!hasRem)
hasRem = bool(sav - low * powerTable[mustShrink]);
}
std::int64_t mantissa = low.convert_to<std::int64_t>();
// normalize before rounding
if (neg)
mantissa *= -1;
IOUAmount result(mantissa, exponent);
if (hasRem)
{
// handle rounding
if (roundUp && !neg)
{
if (!result)
{
return IOUAmount::minPositiveAmount();
}
// This addition cannot overflow because the mantissa is already
// normalized
return IOUAmount(result.mantissa() + 1, result.exponent());
}
if (!roundUp && neg)
{
if (!result)
{
return IOUAmount(-minMantissa, minExponent);
}
// This subtraction cannot underflow because `result` is not zero
return IOUAmount(result.mantissa() - 1, result.exponent());
}
}
return result;
}
} // namespace xrpl