mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-21 19:45:53 +00:00
Add optional enforcement of valid integer range to Number
This commit is contained in:
@@ -13,16 +13,47 @@ class Number;
|
|||||||
std::string
|
std::string
|
||||||
to_string(Number const& amount);
|
to_string(Number const& amount);
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
constexpr bool
|
||||||
|
isPowerOfTen(T value)
|
||||||
|
{
|
||||||
|
while (value >= 10 && value % 10 == 0)
|
||||||
|
value /= 10;
|
||||||
|
return value == 1;
|
||||||
|
}
|
||||||
|
|
||||||
class Number
|
class Number
|
||||||
{
|
{
|
||||||
|
public:
|
||||||
|
/** Describes whether and how to enforce this number as an integer.
|
||||||
|
*
|
||||||
|
* - none: No enforcement. The value may vary freely. This is the default.
|
||||||
|
* - weak: If the absolute value is greater than maxIntValue, valid() will
|
||||||
|
* return false.
|
||||||
|
* - strong: Assignment operations will throw if the absolute value is above
|
||||||
|
* maxIntValue.
|
||||||
|
*/
|
||||||
|
enum EnforceInteger { none, weak, strong };
|
||||||
|
|
||||||
|
private:
|
||||||
using rep = std::int64_t;
|
using rep = std::int64_t;
|
||||||
rep mantissa_{0};
|
rep mantissa_{0};
|
||||||
int exponent_{std::numeric_limits<int>::lowest()};
|
int exponent_{std::numeric_limits<int>::lowest()};
|
||||||
|
|
||||||
|
// The enforcement setting is not serialized, and does not affect the
|
||||||
|
// ledger. If not "none", the value is checked to be within the valid
|
||||||
|
// integer range. With "strong", the checks will be made as automatic as
|
||||||
|
// possible.
|
||||||
|
EnforceInteger enforceInteger_ = none;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
// The range for the mantissa when normalized
|
// The range for the mantissa when normalized
|
||||||
constexpr static std::int64_t minMantissa = 1'000'000'000'000'000LL;
|
constexpr static rep minMantissa = 1'000'000'000'000'000LL;
|
||||||
constexpr static std::int64_t maxMantissa = 9'999'999'999'999'999LL;
|
static_assert(isPowerOfTen(minMantissa));
|
||||||
|
constexpr static rep maxMantissa = minMantissa * 10 - 1;
|
||||||
|
static_assert(maxMantissa == 9'999'999'999'999'999LL);
|
||||||
|
|
||||||
|
constexpr static rep maxIntValue = minMantissa / 10;
|
||||||
|
|
||||||
// The range for the exponent when normalized
|
// The range for the exponent when normalized
|
||||||
constexpr static int minExponent = -32768;
|
constexpr static int minExponent = -32768;
|
||||||
@@ -35,15 +66,33 @@ public:
|
|||||||
|
|
||||||
explicit constexpr Number() = default;
|
explicit constexpr Number() = default;
|
||||||
|
|
||||||
Number(rep mantissa);
|
Number(rep mantissa, EnforceInteger enforce = none);
|
||||||
explicit Number(rep mantissa, int exponent);
|
explicit Number(rep mantissa, int exponent, EnforceInteger enforce = none);
|
||||||
explicit constexpr Number(rep mantissa, int exponent, unchecked) noexcept;
|
explicit constexpr Number(rep mantissa, int exponent, unchecked) noexcept;
|
||||||
|
constexpr Number(Number const& other) = default;
|
||||||
|
constexpr Number(Number&& other) = default;
|
||||||
|
|
||||||
|
~Number() = default;
|
||||||
|
|
||||||
|
constexpr Number&
|
||||||
|
operator=(Number const& other);
|
||||||
|
constexpr Number&
|
||||||
|
operator=(Number&& other);
|
||||||
|
|
||||||
constexpr rep
|
constexpr rep
|
||||||
mantissa() const noexcept;
|
mantissa() const noexcept;
|
||||||
constexpr int
|
constexpr int
|
||||||
exponent() const noexcept;
|
exponent() const noexcept;
|
||||||
|
|
||||||
|
void
|
||||||
|
setIntegerEnforcement(EnforceInteger enforce);
|
||||||
|
|
||||||
|
EnforceInteger
|
||||||
|
integerEnforcement() const noexcept;
|
||||||
|
|
||||||
|
bool
|
||||||
|
valid() const noexcept;
|
||||||
|
|
||||||
constexpr Number
|
constexpr Number
|
||||||
operator+() const noexcept;
|
operator+() const noexcept;
|
||||||
constexpr Number
|
constexpr Number
|
||||||
@@ -184,6 +233,9 @@ public:
|
|||||||
private:
|
private:
|
||||||
static thread_local rounding_mode mode_;
|
static thread_local rounding_mode mode_;
|
||||||
|
|
||||||
|
void
|
||||||
|
checkInteger(char const* what) const;
|
||||||
|
|
||||||
void
|
void
|
||||||
normalize();
|
normalize();
|
||||||
constexpr bool
|
constexpr bool
|
||||||
@@ -197,16 +249,52 @@ inline constexpr Number::Number(rep mantissa, int exponent, unchecked) noexcept
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
inline Number::Number(rep mantissa, int exponent)
|
inline Number::Number(rep mantissa, int exponent, EnforceInteger enforce)
|
||||||
: mantissa_{mantissa}, exponent_{exponent}
|
: mantissa_{mantissa}, exponent_{exponent}, enforceInteger_(enforce)
|
||||||
{
|
{
|
||||||
normalize();
|
normalize();
|
||||||
|
|
||||||
|
checkInteger("Number::Number integer overflow");
|
||||||
}
|
}
|
||||||
|
|
||||||
inline Number::Number(rep mantissa) : Number{mantissa, 0}
|
inline Number::Number(rep mantissa, EnforceInteger enforce)
|
||||||
|
: Number{mantissa, 0, enforce}
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
constexpr Number&
|
||||||
|
Number::operator=(Number const& other)
|
||||||
|
{
|
||||||
|
if (this != &other)
|
||||||
|
{
|
||||||
|
mantissa_ = other.mantissa_;
|
||||||
|
exponent_ = other.exponent_;
|
||||||
|
enforceInteger_ = std::max(enforceInteger_, other.enforceInteger_);
|
||||||
|
|
||||||
|
checkInteger("Number::operator= integer overflow");
|
||||||
|
}
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr Number&
|
||||||
|
Number::operator=(Number&& other)
|
||||||
|
{
|
||||||
|
if (this != &other)
|
||||||
|
{
|
||||||
|
// std::move doesn't really do anything for these types, but
|
||||||
|
// this is future-proof in case the types ever change
|
||||||
|
mantissa_ = std::move(other.mantissa_);
|
||||||
|
exponent_ = std::move(other.exponent_);
|
||||||
|
if (other.enforceInteger_ > enforceInteger_)
|
||||||
|
enforceInteger_ = std::move(other.enforceInteger_);
|
||||||
|
|
||||||
|
checkInteger("Number::operator= integer overflow");
|
||||||
|
}
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
inline constexpr Number::rep
|
inline constexpr Number::rep
|
||||||
Number::mantissa() const noexcept
|
Number::mantissa() const noexcept
|
||||||
{
|
{
|
||||||
@@ -219,6 +307,20 @@ Number::exponent() const noexcept
|
|||||||
return exponent_;
|
return exponent_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline void
|
||||||
|
Number::setIntegerEnforcement(EnforceInteger enforce)
|
||||||
|
{
|
||||||
|
enforceInteger_ = enforce;
|
||||||
|
|
||||||
|
checkInteger("Number::setIntegerEnforcement integer overflow");
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Number::EnforceInteger
|
||||||
|
Number::integerEnforcement() const noexcept
|
||||||
|
{
|
||||||
|
return enforceInteger_;
|
||||||
|
}
|
||||||
|
|
||||||
inline constexpr Number
|
inline constexpr Number
|
||||||
Number::operator+() const noexcept
|
Number::operator+() const noexcept
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -155,6 +155,13 @@ Number::Guard::round() noexcept
|
|||||||
|
|
||||||
constexpr Number one{1000000000000000, -15, Number::unchecked{}};
|
constexpr Number one{1000000000000000, -15, Number::unchecked{}};
|
||||||
|
|
||||||
|
void
|
||||||
|
Number::checkInteger(char const* what) const
|
||||||
|
{
|
||||||
|
if (enforceInteger_ == strong && !valid())
|
||||||
|
throw std::overflow_error(what);
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
Number::normalize()
|
Number::normalize()
|
||||||
{
|
{
|
||||||
@@ -207,9 +214,27 @@ Number::normalize()
|
|||||||
mantissa_ = -mantissa_;
|
mantissa_ = -mantissa_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
Number::valid() const noexcept
|
||||||
|
{
|
||||||
|
if (enforceInteger_ != none)
|
||||||
|
{
|
||||||
|
static Number const max = maxIntValue;
|
||||||
|
static Number const maxNeg = -maxIntValue;
|
||||||
|
// Avoid making a copy
|
||||||
|
if (mantissa_ < 0)
|
||||||
|
return *this >= maxNeg;
|
||||||
|
return *this <= max;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
Number&
|
Number&
|
||||||
Number::operator+=(Number const& y)
|
Number::operator+=(Number const& y)
|
||||||
{
|
{
|
||||||
|
// The strictest setting prevails
|
||||||
|
enforceInteger_ = std::max(enforceInteger_, y.enforceInteger_);
|
||||||
|
|
||||||
if (y == Number{})
|
if (y == Number{})
|
||||||
return *this;
|
return *this;
|
||||||
if (*this == Number{})
|
if (*this == Number{})
|
||||||
@@ -322,6 +347,9 @@ Number::operator+=(Number const& y)
|
|||||||
}
|
}
|
||||||
mantissa_ = xm * xn;
|
mantissa_ = xm * xn;
|
||||||
exponent_ = xe;
|
exponent_ = xe;
|
||||||
|
|
||||||
|
checkInteger("Number::addition integer overflow");
|
||||||
|
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -356,6 +384,9 @@ divu10(uint128_t& u)
|
|||||||
Number&
|
Number&
|
||||||
Number::operator*=(Number const& y)
|
Number::operator*=(Number const& y)
|
||||||
{
|
{
|
||||||
|
// The strictest setting prevails
|
||||||
|
enforceInteger_ = std::max(enforceInteger_, y.enforceInteger_);
|
||||||
|
|
||||||
if (*this == Number{})
|
if (*this == Number{})
|
||||||
return *this;
|
return *this;
|
||||||
if (y == Number{})
|
if (y == Number{})
|
||||||
@@ -422,12 +453,18 @@ Number::operator*=(Number const& y)
|
|||||||
XRPL_ASSERT(
|
XRPL_ASSERT(
|
||||||
isnormal() || *this == Number{},
|
isnormal() || *this == Number{},
|
||||||
"ripple::Number::operator*=(Number) : result is normal");
|
"ripple::Number::operator*=(Number) : result is normal");
|
||||||
|
|
||||||
|
checkInteger("Number::multiplication integer overflow");
|
||||||
|
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
Number&
|
Number&
|
||||||
Number::operator/=(Number const& y)
|
Number::operator/=(Number const& y)
|
||||||
{
|
{
|
||||||
|
// The strictest setting prevails
|
||||||
|
enforceInteger_ = std::max(enforceInteger_, y.enforceInteger_);
|
||||||
|
|
||||||
if (y == Number{})
|
if (y == Number{})
|
||||||
throw std::overflow_error("Number: divide by 0");
|
throw std::overflow_error("Number: divide by 0");
|
||||||
if (*this == Number{})
|
if (*this == Number{})
|
||||||
@@ -455,6 +492,9 @@ Number::operator/=(Number const& y)
|
|||||||
exponent_ = ne - de - 17;
|
exponent_ = ne - de - 17;
|
||||||
mantissa_ *= np * dp;
|
mantissa_ *= np * dp;
|
||||||
normalize();
|
normalize();
|
||||||
|
|
||||||
|
checkInteger("Number::division integer overflow");
|
||||||
|
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
#include <xrpl/beast/unit_test.h>
|
#include <xrpl/beast/unit_test.h>
|
||||||
#include <xrpl/protocol/IOUAmount.h>
|
#include <xrpl/protocol/IOUAmount.h>
|
||||||
#include <xrpl/protocol/STAmount.h>
|
#include <xrpl/protocol/STAmount.h>
|
||||||
|
#include <xrpl/protocol/SystemParameters.h>
|
||||||
|
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <tuple>
|
#include <tuple>
|
||||||
@@ -725,6 +726,172 @@ public:
|
|||||||
BEAST_EXPECT(Number(-100, -30000).truncate() == Number(0, 0));
|
BEAST_EXPECT(Number(-100, -30000).truncate() == Number(0, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
testInteger()
|
||||||
|
{
|
||||||
|
testcase("Integer enforcement");
|
||||||
|
|
||||||
|
using namespace std::string_literals;
|
||||||
|
|
||||||
|
{
|
||||||
|
Number a{100};
|
||||||
|
BEAST_EXPECT(a.integerEnforcement() == Number::none);
|
||||||
|
BEAST_EXPECT(a.valid());
|
||||||
|
a = Number{1, 30};
|
||||||
|
BEAST_EXPECT(a.valid());
|
||||||
|
a = -100;
|
||||||
|
BEAST_EXPECT(a.valid());
|
||||||
|
}
|
||||||
|
{
|
||||||
|
Number a{100, Number::weak};
|
||||||
|
BEAST_EXPECT(a.integerEnforcement() == Number::weak);
|
||||||
|
BEAST_EXPECT(a.valid());
|
||||||
|
a = Number{1, 30, Number::none};
|
||||||
|
BEAST_EXPECT(!a.valid());
|
||||||
|
a = -100;
|
||||||
|
BEAST_EXPECT(a.integerEnforcement() == Number::weak);
|
||||||
|
BEAST_EXPECT(a.valid());
|
||||||
|
a = Number{5, Number::strong};
|
||||||
|
BEAST_EXPECT(a.integerEnforcement() == Number::strong);
|
||||||
|
BEAST_EXPECT(a.valid());
|
||||||
|
}
|
||||||
|
{
|
||||||
|
Number a{100, Number::strong};
|
||||||
|
BEAST_EXPECT(a.integerEnforcement() == Number::strong);
|
||||||
|
BEAST_EXPECT(a.valid());
|
||||||
|
try
|
||||||
|
{
|
||||||
|
a = Number{1, 30};
|
||||||
|
BEAST_EXPECT(false);
|
||||||
|
}
|
||||||
|
catch (std::overflow_error const& e)
|
||||||
|
{
|
||||||
|
BEAST_EXPECT(e.what() == "Number::operator= integer overflow"s);
|
||||||
|
// The throw is done _after_ the number is updated.
|
||||||
|
BEAST_EXPECT((a == Number{1, 30}));
|
||||||
|
}
|
||||||
|
BEAST_EXPECT(!a.valid());
|
||||||
|
a = -100;
|
||||||
|
BEAST_EXPECT(a.integerEnforcement() == Number::strong);
|
||||||
|
BEAST_EXPECT(a.valid());
|
||||||
|
}
|
||||||
|
{
|
||||||
|
Number a{INITIAL_XRP.drops(), Number::weak};
|
||||||
|
BEAST_EXPECT(!a.valid());
|
||||||
|
a = -a;
|
||||||
|
BEAST_EXPECT(!a.valid());
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
a.setIntegerEnforcement(Number::strong);
|
||||||
|
BEAST_EXPECT(false);
|
||||||
|
}
|
||||||
|
catch (std::overflow_error const& e)
|
||||||
|
{
|
||||||
|
BEAST_EXPECT(
|
||||||
|
e.what() ==
|
||||||
|
"Number::setIntegerEnforcement integer overflow"s);
|
||||||
|
// The throw is internal to the operator before the result is
|
||||||
|
// assigned to the Number
|
||||||
|
BEAST_EXPECT(a == -INITIAL_XRP);
|
||||||
|
BEAST_EXPECT(!a.valid());
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
++a;
|
||||||
|
BEAST_EXPECT(false);
|
||||||
|
}
|
||||||
|
catch (std::overflow_error const& e)
|
||||||
|
{
|
||||||
|
BEAST_EXPECT(e.what() == "Number::addition integer overflow"s);
|
||||||
|
// The throw is internal to the operator before the result is
|
||||||
|
// assigned to the Number
|
||||||
|
BEAST_EXPECT(a == -INITIAL_XRP);
|
||||||
|
BEAST_EXPECT(!a.valid());
|
||||||
|
}
|
||||||
|
a = Number::maxIntValue;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
++a;
|
||||||
|
BEAST_EXPECT(false);
|
||||||
|
}
|
||||||
|
catch (std::overflow_error const& e)
|
||||||
|
{
|
||||||
|
BEAST_EXPECT(e.what() == "Number::addition integer overflow"s);
|
||||||
|
// This time, the throw is done _after_ the number is updated.
|
||||||
|
BEAST_EXPECT(a == Number::maxIntValue + 1);
|
||||||
|
BEAST_EXPECT(!a.valid());
|
||||||
|
}
|
||||||
|
a = -Number::maxIntValue;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
--a;
|
||||||
|
BEAST_EXPECT(false);
|
||||||
|
}
|
||||||
|
catch (std::overflow_error const& e)
|
||||||
|
{
|
||||||
|
BEAST_EXPECT(e.what() == "Number::addition integer overflow"s);
|
||||||
|
// This time, the throw is done _after_ the number is updated.
|
||||||
|
BEAST_EXPECT(a == -Number::maxIntValue - 1);
|
||||||
|
BEAST_EXPECT(!a.valid());
|
||||||
|
}
|
||||||
|
a = Number(1, 10);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
a *= Number(1, 10);
|
||||||
|
BEAST_EXPECT(false);
|
||||||
|
}
|
||||||
|
catch (std::overflow_error const& e)
|
||||||
|
{
|
||||||
|
BEAST_EXPECT(
|
||||||
|
e.what() == "Number::multiplication integer overflow"s);
|
||||||
|
// The throw is done _after_ the number is updated.
|
||||||
|
BEAST_EXPECT((a == Number{1, 20}));
|
||||||
|
BEAST_EXPECT(!a.valid());
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
a = Number::maxIntValue * 2;
|
||||||
|
BEAST_EXPECT(false);
|
||||||
|
}
|
||||||
|
catch (std::overflow_error const& e)
|
||||||
|
{
|
||||||
|
BEAST_EXPECT(e.what() == "Number::operator= integer overflow"s);
|
||||||
|
// The throw is done _after_ the number is updated.
|
||||||
|
BEAST_EXPECT((a == Number{2, 14}));
|
||||||
|
BEAST_EXPECT(!a.valid());
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
a = Number(3, 15, Number::strong);
|
||||||
|
BEAST_EXPECT(false);
|
||||||
|
}
|
||||||
|
catch (std::overflow_error const& e)
|
||||||
|
{
|
||||||
|
BEAST_EXPECT(e.what() == "Number::Number integer overflow"s);
|
||||||
|
// The Number doesn't get updated because the ctor throws
|
||||||
|
BEAST_EXPECT((a == Number{2, 14}));
|
||||||
|
BEAST_EXPECT(!a.valid());
|
||||||
|
}
|
||||||
|
a = Number(1, 10);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
a /= Number(1, -10);
|
||||||
|
BEAST_EXPECT(false);
|
||||||
|
}
|
||||||
|
catch (std::overflow_error const& e)
|
||||||
|
{
|
||||||
|
BEAST_EXPECT(e.what() == "Number::division integer overflow"s);
|
||||||
|
// The throw is done _after_ the number is updated.
|
||||||
|
BEAST_EXPECT((a == Number{1, 20}));
|
||||||
|
BEAST_EXPECT(!a.valid());
|
||||||
|
}
|
||||||
|
a /= Number(1, 15);
|
||||||
|
BEAST_EXPECT((a == Number{1, 5}));
|
||||||
|
BEAST_EXPECT(a.valid());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
run() override
|
run() override
|
||||||
{
|
{
|
||||||
@@ -746,6 +913,7 @@ public:
|
|||||||
test_inc_dec();
|
test_inc_dec();
|
||||||
test_toSTAmount();
|
test_toSTAmount();
|
||||||
test_truncate();
|
test_truncate();
|
||||||
|
testInteger();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user