mirror of
https://github.com/XRPLF/rippled.git
synced 2026-01-29 11:05:25 +00:00
- Refactor Number internals away from int64 to uint64 & a sign flag
- ctors and accessors use `rep`. Very few things expose
`internalrep`.
- An exception is "unchecked" and the new "normalized", which explicitly
take an internalrep. But with those special control flags, it's easier
to distinguish and control when they are used.
- For now, skip the larger mantissas in AMM transactions and tests
- Remove trailing zeros from scientific notation Number strings
- Update tests. This has the happy side effect of making some of the string
representations _more_ consistent between the small and large
mantissa ranges.
- Add semi-automatic rounding of STNumbers based on Asset types
- Create a new SField metadata enum, sMD_NeedsAsset, which indicates
the field should be associated with an Asset so it can be rounded.
- Add a new STTakesAsset intermediate class to handle the Asset
association to a derived ST class. Currently only used in STNumber,
but could be used by other types in the future.
- Add "associateAsset" which takes an SLE and an Asset, finds the
sMD_NeedsAsset fields, and associates the Asset to them. In the case
of STNumber, that both stores the Asset, and rounds the value
immediately.
- Transactors only need to add a call to associateAsset _after_ all of
the STNumbers have been set. Unfortunately, the inner workings of
STObject do not do the association correctly with uninitialized
fields.
- When serializing an STNumber that has an Asset, round it before
serializing.
- Add an override of roundToAsset, which rounds a Number value in place
to an Asset, but without any additional scale.
- Update and fix a bunch of Loan-related tests to accommodate the
expanded Number class.
---------
Co-authored-by: Vito <5780819+Tapanito@users.noreply.github.com>
267 lines
6.7 KiB
C++
267 lines
6.7 KiB
C++
#include <xrpl/protocol/STNumber.h>
|
|
// Do not remove. Keep STNumber.h first
|
|
#include <xrpl/basics/Number.h>
|
|
#include <xrpl/beast/core/LexicalCast.h>
|
|
#include <xrpl/beast/utility/instrumentation.h>
|
|
#include <xrpl/protocol/Rules.h>
|
|
#include <xrpl/protocol/SField.h>
|
|
#include <xrpl/protocol/STAmount.h>
|
|
#include <xrpl/protocol/STBase.h>
|
|
#include <xrpl/protocol/STIssue.h>
|
|
#include <xrpl/protocol/Serializer.h>
|
|
|
|
#include <boost/lexical_cast.hpp>
|
|
#include <boost/regex.hpp>
|
|
|
|
#include <cstddef>
|
|
#include <ostream>
|
|
#include <string>
|
|
#include <utility>
|
|
|
|
namespace xrpl {
|
|
|
|
STNumber::STNumber(SField const& field, Number const& value)
|
|
: STTakesAsset(field), value_(value)
|
|
{
|
|
}
|
|
|
|
STNumber::STNumber(SerialIter& sit, SField const& field) : STTakesAsset(field)
|
|
{
|
|
// We must call these methods in separate statements
|
|
// to guarantee their order of execution.
|
|
auto mantissa = sit.geti64();
|
|
auto exponent = sit.geti32();
|
|
value_ = Number{mantissa, exponent};
|
|
}
|
|
|
|
SerializedTypeID
|
|
STNumber::getSType() const
|
|
{
|
|
return STI_NUMBER;
|
|
}
|
|
|
|
std::string
|
|
STNumber::getText() const
|
|
{
|
|
return to_string(value_);
|
|
}
|
|
|
|
void
|
|
STNumber::associateAsset(Asset const& a)
|
|
{
|
|
STTakesAsset::associateAsset(a);
|
|
|
|
XRPL_ASSERT_PARTS(
|
|
getFName().shouldMeta(SField::sMD_NeedsAsset),
|
|
"STNumber::associateAsset",
|
|
"field needs asset");
|
|
|
|
roundToAsset(a, value_);
|
|
}
|
|
|
|
void
|
|
STNumber::add(Serializer& s) const
|
|
{
|
|
XRPL_ASSERT(getFName().isBinary(), "xrpl::STNumber::add : field is binary");
|
|
XRPL_ASSERT(
|
|
getFName().fieldType == getSType(),
|
|
"xrpl::STNumber::add : field type match");
|
|
|
|
auto value = value_;
|
|
auto const mantissa = value.mantissa();
|
|
auto const exponent = value.exponent();
|
|
|
|
SField const& field = getFName();
|
|
if (field.shouldMeta(SField::sMD_NeedsAsset))
|
|
{
|
|
// asset is defined in the STTakesAsset base class
|
|
if (asset_)
|
|
{
|
|
// The number should be rounded to the asset's precision, but round
|
|
// it here if it has an asset assigned.
|
|
roundToAsset(*asset_, value);
|
|
XRPL_ASSERT_PARTS(
|
|
value_ == value,
|
|
"xrpl::STNumber::add",
|
|
"value is already rounded");
|
|
}
|
|
else
|
|
{
|
|
#if !NDEBUG
|
|
// There are circumstances where an already-rounded Number is
|
|
// serialized without being touched by a transactor, and thus
|
|
// without an asset. We can't know if it's rounded, because it could
|
|
// represent _anything_, particularly when serializing user-provided
|
|
// Json. Regardless, the only time we should be serializing an
|
|
// STNumber is when the scale is large.
|
|
XRPL_ASSERT_PARTS(
|
|
Number::getMantissaScale() == MantissaRange::large,
|
|
"xrpl::STNumber::add",
|
|
"STNumber only used with large mantissa scale");
|
|
#endif
|
|
}
|
|
}
|
|
|
|
XRPL_ASSERT_PARTS(
|
|
mantissa <= std::numeric_limits<std::int64_t>::max() &&
|
|
mantissa >= std::numeric_limits<std::int64_t>::min(),
|
|
"xrpl::STNumber::add",
|
|
"mantissa in valid range");
|
|
s.add64(mantissa);
|
|
s.add32(exponent);
|
|
}
|
|
|
|
Number const&
|
|
STNumber::value() const
|
|
{
|
|
return value_;
|
|
}
|
|
|
|
void
|
|
STNumber::setValue(Number const& v)
|
|
{
|
|
value_ = v;
|
|
}
|
|
|
|
STBase*
|
|
STNumber::copy(std::size_t n, void* buf) const
|
|
{
|
|
return emplace(n, buf, *this);
|
|
}
|
|
|
|
STBase*
|
|
STNumber::move(std::size_t n, void* buf)
|
|
{
|
|
return emplace(n, buf, std::move(*this));
|
|
}
|
|
|
|
bool
|
|
STNumber::isEquivalent(STBase const& t) const
|
|
{
|
|
XRPL_ASSERT(
|
|
t.getSType() == this->getSType(),
|
|
"xrpl::STNumber::isEquivalent : field type match");
|
|
STNumber const& v = dynamic_cast<STNumber const&>(t);
|
|
return value_ == v;
|
|
}
|
|
|
|
bool
|
|
STNumber::isDefault() const
|
|
{
|
|
return value_ == Number();
|
|
}
|
|
|
|
std::ostream&
|
|
operator<<(std::ostream& out, STNumber const& rhs)
|
|
{
|
|
return out << rhs.getText();
|
|
}
|
|
|
|
NumberParts
|
|
partsFromString(std::string const& number)
|
|
{
|
|
static boost::regex const reNumber(
|
|
"^" // the beginning of the string
|
|
"([-+]?)" // (optional) + or - character
|
|
"(0|[1-9][0-9]*)" // a number (no leading zeroes, unless 0)
|
|
"(\\.([0-9]+))?" // (optional) period followed by any number
|
|
"([eE]([+-]?)([0-9]+))?" // (optional) E, optional + or -, any number
|
|
"$",
|
|
boost::regex_constants::optimize);
|
|
|
|
boost::smatch match;
|
|
|
|
if (!boost::regex_match(number, match, reNumber))
|
|
Throw<std::runtime_error>("'" + number + "' is not a number");
|
|
|
|
// Match fields:
|
|
// 0 = whole input
|
|
// 1 = sign
|
|
// 2 = integer portion
|
|
// 3 = whole fraction (with '.')
|
|
// 4 = fraction (without '.')
|
|
// 5 = whole exponent (with 'e')
|
|
// 6 = exponent sign
|
|
// 7 = exponent number
|
|
|
|
bool negative = (match[1].matched && (match[1] == "-"));
|
|
|
|
std::uint64_t mantissa;
|
|
int exponent;
|
|
|
|
if (!match[4].matched) // integer only
|
|
{
|
|
mantissa = boost::lexical_cast<std::uint64_t>(std::string(match[2]));
|
|
exponent = 0;
|
|
}
|
|
else
|
|
{
|
|
// integer and fraction
|
|
mantissa = boost::lexical_cast<std::uint64_t>(match[2] + match[4]);
|
|
exponent = -(match[4].length());
|
|
}
|
|
|
|
if (match[5].matched)
|
|
{
|
|
// we have an exponent
|
|
if (match[6].matched && (match[6] == "-"))
|
|
exponent -= boost::lexical_cast<int>(std::string(match[7]));
|
|
else
|
|
exponent += boost::lexical_cast<int>(std::string(match[7]));
|
|
}
|
|
|
|
return {mantissa, exponent, negative};
|
|
}
|
|
|
|
STNumber
|
|
numberFromJson(SField const& field, Json::Value const& value)
|
|
{
|
|
NumberParts parts;
|
|
|
|
if (value.isInt())
|
|
{
|
|
if (value.asInt() >= 0)
|
|
{
|
|
parts.mantissa = value.asInt();
|
|
}
|
|
else
|
|
{
|
|
parts.mantissa = value.asAbsUInt();
|
|
parts.negative = true;
|
|
}
|
|
}
|
|
else if (value.isUInt())
|
|
{
|
|
parts.mantissa = value.asUInt();
|
|
}
|
|
else if (value.isString())
|
|
{
|
|
parts = partsFromString(value.asString());
|
|
|
|
XRPL_ASSERT_PARTS(
|
|
!getCurrentTransactionRules(),
|
|
"xrpld::numberFromJson",
|
|
"Not in a Transactor context");
|
|
|
|
// Number mantissas are much bigger than the allowable parsed values, so
|
|
// it can't be out of range.
|
|
static_assert(
|
|
std::numeric_limits<std::uint64_t>::max() >=
|
|
std::numeric_limits<decltype(parts.mantissa)>::max());
|
|
}
|
|
else
|
|
{
|
|
Throw<std::runtime_error>("not a number");
|
|
}
|
|
|
|
return STNumber{
|
|
field,
|
|
Number{
|
|
parts.negative,
|
|
parts.mantissa,
|
|
parts.exponent,
|
|
Number::normalized{}}};
|
|
}
|
|
|
|
} // namespace xrpl
|