mirror of
https://github.com/XRPLF/rippled.git
synced 2026-10-22 11:37:07 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a86a90edd7 | ||
|
|
ed13af878e | ||
|
|
3c1505a29d |
No files matched your search
@@ -45,6 +45,9 @@ public:
|
|||||||
|
|
||||||
static int const cMinOffset = -96;
|
static int const cMinOffset = -96;
|
||||||
static int const cMaxOffset = 80;
|
static int const cMaxOffset = 80;
|
||||||
|
// The -100 is used to allow 0 to sort less than small positive values
|
||||||
|
// which will have a large negative exponent.
|
||||||
|
static int const cZeroOffset = -100;
|
||||||
|
|
||||||
// Maximum native value supported by the code
|
// Maximum native value supported by the code
|
||||||
constexpr static std::uint64_t cMinValue = 1'000'000'000'000'000ull;
|
constexpr static std::uint64_t cMinValue = 1'000'000'000'000'000ull;
|
||||||
@@ -524,7 +527,11 @@ STAmount::fromNumber(A const& a, Number const& number)
|
|||||||
|
|
||||||
auto const [mantissa, exponent] = working.normalizeToRange(cMinValue, cMaxValue);
|
auto const [mantissa, exponent] = working.normalizeToRange(cMinValue, cMaxValue);
|
||||||
|
|
||||||
return STAmount{asset, mantissa, exponent, negative};
|
// normalizeToRange produces values in canonical mantissa range [cMinValue, cMaxValue],
|
||||||
|
// but may produce out-of-range exponents for overflow/underflow cases.
|
||||||
|
// Use the regular constructor - canonicalize() will detect already-normalized mantissa
|
||||||
|
// and skip redundant scaling loops, while still handling overflow/underflow.
|
||||||
|
return STAmount{asset, static_cast<std::uint64_t>(mantissa), exponent, negative};
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void
|
inline void
|
||||||
@@ -537,9 +544,7 @@ STAmount::negate()
|
|||||||
inline void
|
inline void
|
||||||
STAmount::clear()
|
STAmount::clear()
|
||||||
{
|
{
|
||||||
// The -100 is used to allow 0 to sort less than a small positive values
|
mOffset = integral() ? 0 : cZeroOffset;
|
||||||
// which have a negative exponent.
|
|
||||||
mOffset = integral() ? 0 : -100;
|
|
||||||
mValue = 0;
|
mValue = 0;
|
||||||
mIsNegative = false;
|
mIsNegative = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,7 +38,10 @@ Number::getround()
|
|||||||
Number::rounding_mode
|
Number::rounding_mode
|
||||||
Number::setround(rounding_mode mode)
|
Number::setround(rounding_mode mode)
|
||||||
{
|
{
|
||||||
return std::exchange(mode_, mode);
|
auto const old = mode_;
|
||||||
|
if (old != mode)
|
||||||
|
mode_ = mode;
|
||||||
|
return old;
|
||||||
}
|
}
|
||||||
|
|
||||||
MantissaRange::mantissa_scale
|
MantissaRange::mantissa_scale
|
||||||
@@ -52,6 +55,8 @@ Number::setMantissaScale(MantissaRange::mantissa_scale scale)
|
|||||||
{
|
{
|
||||||
if (scale != MantissaRange::small && scale != MantissaRange::large)
|
if (scale != MantissaRange::small && scale != MantissaRange::large)
|
||||||
LogicError("Unknown mantissa scale");
|
LogicError("Unknown mantissa scale");
|
||||||
|
if (range_.get().scale == scale)
|
||||||
|
return;
|
||||||
range_ = scale == MantissaRange::small ? smallRange : largeRange;
|
range_ = scale == MantissaRange::small ? smallRange : largeRange;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -866,34 +866,44 @@ STAmount::canonicalize()
|
|||||||
|
|
||||||
if (mValue == 0)
|
if (mValue == 0)
|
||||||
{
|
{
|
||||||
mOffset = -100;
|
mOffset = cZeroOffset;
|
||||||
mIsNegative = false;
|
mIsNegative = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
while ((mValue < cMinValue) && (mOffset > cMinOffset))
|
// Fast path: if mantissa is already in canonical range, skip scaling loops.
|
||||||
|
// This handles values from normalizeToRange that only need overflow/underflow checks.
|
||||||
|
bool const mantissaCanonical = (mValue >= cMinValue) && (mValue <= cMaxValue);
|
||||||
|
|
||||||
|
if (!mantissaCanonical)
|
||||||
{
|
{
|
||||||
mValue *= 10;
|
// Mantissa needs normalization
|
||||||
--mOffset;
|
while ((mValue < cMinValue) && (mOffset > cMinOffset))
|
||||||
}
|
{
|
||||||
|
mValue *= 10;
|
||||||
while (mValue > cMaxValue)
|
--mOffset;
|
||||||
{
|
}
|
||||||
if (mOffset >= cMaxOffset)
|
|
||||||
Throw<std::runtime_error>("value overflow");
|
while (mValue > cMaxValue)
|
||||||
|
{
|
||||||
mValue /= 10;
|
if (mOffset >= cMaxOffset)
|
||||||
++mOffset;
|
Throw<std::runtime_error>("value overflow");
|
||||||
|
|
||||||
|
mValue /= 10;
|
||||||
|
++mOffset;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check for underflow (applies whether we scaled or not)
|
||||||
if ((mOffset < cMinOffset) || (mValue < cMinValue))
|
if ((mOffset < cMinOffset) || (mValue < cMinValue))
|
||||||
{
|
{
|
||||||
mValue = 0;
|
mValue = 0;
|
||||||
mIsNegative = false;
|
mIsNegative = false;
|
||||||
mOffset = -100;
|
mOffset = cZeroOffset;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check for overflow (applies whether we scaled or not)
|
||||||
if (mOffset > cMaxOffset)
|
if (mOffset > cMaxOffset)
|
||||||
Throw<std::runtime_error>("value overflow");
|
Throw<std::runtime_error>("value overflow");
|
||||||
|
|
||||||
@@ -903,7 +913,7 @@ STAmount::canonicalize()
|
|||||||
XRPL_ASSERT(
|
XRPL_ASSERT(
|
||||||
(mValue == 0) || ((mOffset >= cMinOffset) && (mOffset <= cMaxOffset)),
|
(mValue == 0) || ((mOffset >= cMinOffset) && (mOffset <= cMaxOffset)),
|
||||||
"xrpl::STAmount::canonicalize : offset inside range");
|
"xrpl::STAmount::canonicalize : offset inside range");
|
||||||
XRPL_ASSERT((mValue != 0) || (mOffset != -100), "xrpl::STAmount::canonicalize : value or offset set");
|
XRPL_ASSERT((mValue != 0) || (mOffset == cZeroOffset), "xrpl::STAmount::canonicalize : value or offset set");
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
#include <test/jtx.h>
|
#include <test/jtx.h>
|
||||||
|
|
||||||
|
#include <xrpl/basics/Number.h>
|
||||||
#include <xrpl/basics/random.h>
|
#include <xrpl/basics/random.h>
|
||||||
#include <xrpl/beast/unit_test.h>
|
#include <xrpl/beast/unit_test.h>
|
||||||
|
#include <xrpl/protocol/IOUAmount.h>
|
||||||
#include <xrpl/protocol/STAmount.h>
|
#include <xrpl/protocol/STAmount.h>
|
||||||
#include <xrpl/protocol/XRPAmount.h>
|
#include <xrpl/protocol/XRPAmount.h>
|
||||||
|
|
||||||
@@ -1143,6 +1145,148 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
testNumberConversion()
|
||||||
|
{
|
||||||
|
testcase("Number to STAmount conversions");
|
||||||
|
|
||||||
|
Issue const usd{Currency(0x5553440000000000), AccountID(0x4985601)};
|
||||||
|
NumberSO stNumberSO{true};
|
||||||
|
|
||||||
|
// Test zero conversion
|
||||||
|
{
|
||||||
|
Number const zero{};
|
||||||
|
STAmount const result{usd, zero};
|
||||||
|
BEAST_EXPECT(result.mantissa() == 0);
|
||||||
|
BEAST_EXPECT(result.exponent() == STAmount::cZeroOffset);
|
||||||
|
BEAST_EXPECT(!result.negative());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test positive zero
|
||||||
|
{
|
||||||
|
Number const zero{0, 0};
|
||||||
|
STAmount const result{usd, zero};
|
||||||
|
BEAST_EXPECT(result.mantissa() == 0);
|
||||||
|
BEAST_EXPECT(result.exponent() == STAmount::cZeroOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test negative zero (should become positive zero)
|
||||||
|
{
|
||||||
|
Number const negZero{-0, 0};
|
||||||
|
STAmount const result{usd, negZero};
|
||||||
|
BEAST_EXPECT(result.mantissa() == 0);
|
||||||
|
BEAST_EXPECT(!result.negative());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test minimum positive IOU amount
|
||||||
|
{
|
||||||
|
Number const minPos{STAmount::cMinValue, STAmount::cMinOffset};
|
||||||
|
STAmount const result{usd, minPos};
|
||||||
|
BEAST_EXPECT(result.mantissa() == STAmount::cMinValue);
|
||||||
|
BEAST_EXPECT(result.exponent() == STAmount::cMinOffset);
|
||||||
|
BEAST_EXPECT(!result.negative());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test maximum positive IOU amount
|
||||||
|
{
|
||||||
|
Number const maxPos{STAmount::cMaxValue, STAmount::cMaxOffset};
|
||||||
|
STAmount const result{usd, maxPos};
|
||||||
|
BEAST_EXPECT(result.mantissa() == STAmount::cMaxValue);
|
||||||
|
BEAST_EXPECT(result.exponent() == STAmount::cMaxOffset);
|
||||||
|
BEAST_EXPECT(!result.negative());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test negative amounts
|
||||||
|
{
|
||||||
|
Number const neg{-static_cast<std::int64_t>(STAmount::cMinValue), STAmount::cMinOffset};
|
||||||
|
STAmount const result{usd, neg};
|
||||||
|
BEAST_EXPECT(result.mantissa() == STAmount::cMinValue);
|
||||||
|
BEAST_EXPECT(result.exponent() == STAmount::cMinOffset);
|
||||||
|
BEAST_EXPECT(result.negative());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test value requiring scale up (mantissa too small)
|
||||||
|
{
|
||||||
|
Number const small{1000000000000000ull / 10, -95}; // Will scale up
|
||||||
|
STAmount const result{usd, small};
|
||||||
|
BEAST_EXPECT(result.mantissa() >= STAmount::cMinValue);
|
||||||
|
BEAST_EXPECT(result.mantissa() <= STAmount::cMaxValue);
|
||||||
|
BEAST_EXPECT(result.exponent() >= STAmount::cMinOffset);
|
||||||
|
BEAST_EXPECT(result.exponent() <= STAmount::cMaxOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test value requiring scale down (mantissa too large)
|
||||||
|
{
|
||||||
|
Number const large{9999999999999999ull * 10, 79}; // Will scale down
|
||||||
|
STAmount const result{usd, large};
|
||||||
|
BEAST_EXPECT(result.mantissa() >= STAmount::cMinValue);
|
||||||
|
BEAST_EXPECT(result.mantissa() <= STAmount::cMaxValue);
|
||||||
|
BEAST_EXPECT(result.exponent() >= STAmount::cMinOffset);
|
||||||
|
BEAST_EXPECT(result.exponent() <= STAmount::cMaxOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test boundary mantissa values
|
||||||
|
{
|
||||||
|
Number const atMin{STAmount::cMinValue, 0};
|
||||||
|
STAmount const result{usd, atMin};
|
||||||
|
BEAST_EXPECT(result.mantissa() == STAmount::cMinValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Number const atMax{STAmount::cMaxValue, 0};
|
||||||
|
STAmount const result{usd, atMax};
|
||||||
|
BEAST_EXPECT(result.mantissa() == STAmount::cMaxValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test typical amounts
|
||||||
|
{
|
||||||
|
Number const typical{1234567890123456ull, -10};
|
||||||
|
STAmount const result{usd, typical};
|
||||||
|
BEAST_EXPECT(result.mantissa() >= STAmount::cMinValue);
|
||||||
|
BEAST_EXPECT(result.mantissa() <= STAmount::cMaxValue);
|
||||||
|
BEAST_EXPECT(result.exponent() >= STAmount::cMinOffset);
|
||||||
|
BEAST_EXPECT(result.exponent() <= STAmount::cMaxOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test round-trip conversion (Number -> STAmount -> Number)
|
||||||
|
{
|
||||||
|
Number const original{5000000000000000ull, 5};
|
||||||
|
STAmount const st{usd, original};
|
||||||
|
Number const recovered{st};
|
||||||
|
BEAST_EXPECT(original == recovered);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test various exponents
|
||||||
|
for (int exp = STAmount::cMinOffset; exp <= STAmount::cMaxOffset; exp += 10)
|
||||||
|
{
|
||||||
|
Number const n{STAmount::cMinValue, exp};
|
||||||
|
STAmount const result{usd, n};
|
||||||
|
BEAST_EXPECT(result.mantissa() >= STAmount::cMinValue);
|
||||||
|
BEAST_EXPECT(result.mantissa() <= STAmount::cMaxValue);
|
||||||
|
BEAST_EXPECT(result.exponent() >= STAmount::cMinOffset);
|
||||||
|
BEAST_EXPECT(result.exponent() <= STAmount::cMaxOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test both mantissa scales (if applicable)
|
||||||
|
{
|
||||||
|
// Small mantissa scale test
|
||||||
|
NumberMantissaScaleGuard guard{MantissaRange::small};
|
||||||
|
Number const n{5000000000000000ull, 5};
|
||||||
|
STAmount const result{usd, n};
|
||||||
|
BEAST_EXPECT(result.mantissa() >= STAmount::cMinValue);
|
||||||
|
BEAST_EXPECT(result.mantissa() <= STAmount::cMaxValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// Large mantissa scale test
|
||||||
|
NumberMantissaScaleGuard guard{MantissaRange::large};
|
||||||
|
Number const n{5000000000000000ull, 5};
|
||||||
|
STAmount const result{usd, n};
|
||||||
|
BEAST_EXPECT(result.mantissa() >= STAmount::cMinValue);
|
||||||
|
BEAST_EXPECT(result.mantissa() <= STAmount::cMaxValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//--------------------------------------------------------------------------
|
//--------------------------------------------------------------------------
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -1157,6 +1301,7 @@ public:
|
|||||||
testParseJson();
|
testParseJson();
|
||||||
testConvertXRP();
|
testConvertXRP();
|
||||||
testConvertIOU();
|
testConvertIOU();
|
||||||
|
testNumberConversion();
|
||||||
testCanAddXRP();
|
testCanAddXRP();
|
||||||
testCanAddIOU();
|
testCanAddIOU();
|
||||||
testCanAddMPT();
|
testCanAddMPT();
|
||||||
|
|||||||
Reference in new issue
Block a user