mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-20 11:05:54 +00:00
Integrate STNumber with STParsedJSON
This commit is contained in:
committed by
Bronek Kozicki
parent
49e0d54c76
commit
1a032f04e3
@@ -417,7 +417,7 @@ STAmount
|
|||||||
amountFromQuality(std::uint64_t rate);
|
amountFromQuality(std::uint64_t rate);
|
||||||
|
|
||||||
STAmount
|
STAmount
|
||||||
amountFromString(Asset const& issue, std::string const& amount);
|
amountFromString(Asset const& asset, std::string const& amount);
|
||||||
|
|
||||||
STAmount
|
STAmount
|
||||||
amountFromJson(SField const& name, Json::Value const& v);
|
amountFromJson(SField const& name, Json::Value const& v);
|
||||||
|
|||||||
@@ -83,6 +83,19 @@ private:
|
|||||||
std::ostream&
|
std::ostream&
|
||||||
operator<<(std::ostream& out, STNumber const& rhs);
|
operator<<(std::ostream& out, STNumber const& rhs);
|
||||||
|
|
||||||
|
struct NumberParts
|
||||||
|
{
|
||||||
|
std::uint64_t mantissa = 0;
|
||||||
|
int exponent = 0;
|
||||||
|
bool negative = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
NumberParts
|
||||||
|
partsFromString(std::string const& number);
|
||||||
|
|
||||||
|
STNumber
|
||||||
|
numberFromJson(SField const& field, Json::Value const& value);
|
||||||
|
|
||||||
} // namespace ripple
|
} // namespace ripple
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -20,15 +20,14 @@
|
|||||||
#include <xrpl/basics/Log.h>
|
#include <xrpl/basics/Log.h>
|
||||||
#include <xrpl/basics/contract.h>
|
#include <xrpl/basics/contract.h>
|
||||||
#include <xrpl/basics/safe_cast.h>
|
#include <xrpl/basics/safe_cast.h>
|
||||||
#include <xrpl/beast/core/LexicalCast.h>
|
|
||||||
#include <xrpl/protocol/Protocol.h>
|
#include <xrpl/protocol/Protocol.h>
|
||||||
#include <xrpl/protocol/STAmount.h>
|
#include <xrpl/protocol/STAmount.h>
|
||||||
|
#include <xrpl/protocol/STNumber.h>
|
||||||
#include <xrpl/protocol/SystemParameters.h>
|
#include <xrpl/protocol/SystemParameters.h>
|
||||||
#include <xrpl/protocol/UintTypes.h>
|
#include <xrpl/protocol/UintTypes.h>
|
||||||
#include <xrpl/protocol/jss.h>
|
#include <xrpl/protocol/jss.h>
|
||||||
#include <boost/algorithm/string.hpp>
|
#include <boost/algorithm/string.hpp>
|
||||||
#include <boost/multiprecision/cpp_int.hpp>
|
#include <boost/multiprecision/cpp_int.hpp>
|
||||||
#include <boost/regex.hpp>
|
|
||||||
|
|
||||||
#include <iterator>
|
#include <iterator>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
@@ -833,75 +832,16 @@ amountFromQuality(std::uint64_t rate)
|
|||||||
STAmount
|
STAmount
|
||||||
amountFromString(Asset const& asset, std::string const& amount)
|
amountFromString(Asset const& asset, std::string const& amount)
|
||||||
{
|
{
|
||||||
static boost::regex const reNumber(
|
auto const parts = partsFromString(amount);
|
||||||
"^" // the beginning of the string
|
if ((asset.native() || asset.holds<MPTIssue>()) && parts.exponent < 0)
|
||||||
"([-+]?)" // (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(amount, match, reNumber))
|
|
||||||
Throw<std::runtime_error>("Number '" + amount + "' is not valid");
|
|
||||||
|
|
||||||
// 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
|
|
||||||
|
|
||||||
// CHECKME: Why 32? Shouldn't this be 16?
|
|
||||||
if ((match[2].length() + match[4].length()) > 32)
|
|
||||||
Throw<std::runtime_error>("Number '" + amount + "' is overlong");
|
|
||||||
|
|
||||||
bool negative = (match[1].matched && (match[1] == "-"));
|
|
||||||
|
|
||||||
// Can't specify XRP or MPT using fractional representation
|
|
||||||
if ((asset.native() || asset.holds<MPTIssue>()) && match[3].matched)
|
|
||||||
Throw<std::runtime_error>(
|
Throw<std::runtime_error>(
|
||||||
"XRP and MPT must be specified as integral amount.");
|
"XRP and MPT must be specified as integral amount.");
|
||||||
|
return {asset, parts.mantissa, parts.exponent, parts.negative};
|
||||||
std::uint64_t mantissa;
|
|
||||||
int exponent;
|
|
||||||
|
|
||||||
if (!match[4].matched) // integer only
|
|
||||||
{
|
|
||||||
mantissa =
|
|
||||||
beast::lexicalCastThrow<std::uint64_t>(std::string(match[2]));
|
|
||||||
exponent = 0;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// integer and fraction
|
|
||||||
mantissa = beast::lexicalCastThrow<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 -= beast::lexicalCastThrow<int>(std::string(match[7]));
|
|
||||||
else
|
|
||||||
exponent += beast::lexicalCastThrow<int>(std::string(match[7]));
|
|
||||||
}
|
|
||||||
|
|
||||||
return {asset, mantissa, exponent, negative};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
STAmount
|
STAmount
|
||||||
amountFromJson(SField const& name, Json::Value const& v)
|
amountFromJson(SField const& name, Json::Value const& v)
|
||||||
{
|
{
|
||||||
STAmount::mantissa_type mantissa = 0;
|
|
||||||
STAmount::exponent_type exponent = 0;
|
|
||||||
bool negative = false;
|
|
||||||
Asset asset;
|
Asset asset;
|
||||||
|
|
||||||
Json::Value value;
|
Json::Value value;
|
||||||
@@ -993,36 +933,38 @@ amountFromJson(SField const& name, Json::Value const& v)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NumberParts parts;
|
||||||
|
|
||||||
if (value.isInt())
|
if (value.isInt())
|
||||||
{
|
{
|
||||||
if (value.asInt() >= 0)
|
if (value.asInt() >= 0)
|
||||||
{
|
{
|
||||||
mantissa = value.asInt();
|
parts.mantissa = value.asInt();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
mantissa = -value.asInt();
|
parts.mantissa = -value.asInt();
|
||||||
negative = true;
|
parts.negative = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (value.isUInt())
|
else if (value.isUInt())
|
||||||
{
|
{
|
||||||
mantissa = v.asUInt();
|
parts.mantissa = v.asUInt();
|
||||||
}
|
}
|
||||||
else if (value.isString())
|
else if (value.isString())
|
||||||
{
|
{
|
||||||
auto const ret = amountFromString(asset, value.asString());
|
parts = partsFromString(value.asString());
|
||||||
|
// Can't specify XRP or MPT using fractional representation
|
||||||
mantissa = ret.mantissa();
|
if ((asset.native() || asset.holds<MPTIssue>()) && parts.exponent < 0)
|
||||||
exponent = ret.exponent();
|
Throw<std::runtime_error>(
|
||||||
negative = ret.negative();
|
"XRP and MPT must be specified as integral amount.");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Throw<std::runtime_error>("invalid amount type");
|
Throw<std::runtime_error>("invalid amount type");
|
||||||
}
|
}
|
||||||
|
|
||||||
return {name, asset, mantissa, exponent, negative};
|
return {name, asset, parts.mantissa, parts.exponent, parts.negative};
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
|
|||||||
@@ -19,8 +19,10 @@
|
|||||||
|
|
||||||
#include <xrpl/protocol/STNumber.h>
|
#include <xrpl/protocol/STNumber.h>
|
||||||
|
|
||||||
|
#include <xrpl/beast/core/LexicalCast.h>
|
||||||
#include <xrpl/beast/utility/instrumentation.h>
|
#include <xrpl/beast/utility/instrumentation.h>
|
||||||
#include <xrpl/protocol/SField.h>
|
#include <xrpl/protocol/SField.h>
|
||||||
|
#include <boost/regex.hpp>
|
||||||
|
|
||||||
namespace ripple {
|
namespace ripple {
|
||||||
|
|
||||||
@@ -108,4 +110,101 @@ operator<<(std::ostream& out, STNumber const& rhs)
|
|||||||
return out << rhs.getText();
|
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 =
|
||||||
|
beast::lexicalCastThrow<std::uint64_t>(std::string(match[2]));
|
||||||
|
exponent = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// integer and fraction
|
||||||
|
mantissa = beast::lexicalCastThrow<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 -= beast::lexicalCastThrow<int>(std::string(match[7]));
|
||||||
|
else
|
||||||
|
exponent += beast::lexicalCastThrow<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.asInt();
|
||||||
|
parts.negative = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (value.isUInt())
|
||||||
|
{
|
||||||
|
parts.mantissa = value.asUInt();
|
||||||
|
}
|
||||||
|
else if (value.isString())
|
||||||
|
{
|
||||||
|
parts = partsFromString(value.asString());
|
||||||
|
// Only strings can represent out-of-range values.
|
||||||
|
if (parts.mantissa > std::numeric_limits<std::int64_t>::max())
|
||||||
|
Throw<std::range_error>("too high");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Throw<std::runtime_error>("not a number");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::int64_t mantissa = parts.mantissa;
|
||||||
|
if (parts.negative)
|
||||||
|
mantissa = -mantissa;
|
||||||
|
|
||||||
|
return STNumber{field, Number{mantissa, parts.exponent}};
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace ripple
|
} // namespace ripple
|
||||||
|
|||||||
@@ -32,6 +32,7 @@
|
|||||||
#include <xrpl/protocol/STBlob.h>
|
#include <xrpl/protocol/STBlob.h>
|
||||||
#include <xrpl/protocol/STInteger.h>
|
#include <xrpl/protocol/STInteger.h>
|
||||||
#include <xrpl/protocol/STIssue.h>
|
#include <xrpl/protocol/STIssue.h>
|
||||||
|
#include <xrpl/protocol/STNumber.h>
|
||||||
#include <xrpl/protocol/STParsedJSON.h>
|
#include <xrpl/protocol/STParsedJSON.h>
|
||||||
#include <xrpl/protocol/STPathSet.h>
|
#include <xrpl/protocol/STPathSet.h>
|
||||||
#include <xrpl/protocol/STVector256.h>
|
#include <xrpl/protocol/STVector256.h>
|
||||||
@@ -574,6 +575,20 @@ parseLeaf(
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case STI_NUMBER:
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ret =
|
||||||
|
detail::make_stvar<STNumber>(numberFromJson(field, value));
|
||||||
|
}
|
||||||
|
catch (std::exception const&)
|
||||||
|
{
|
||||||
|
error = invalid_data(json_name, fieldName);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
case STI_VECTOR256:
|
case STI_VECTOR256:
|
||||||
if (!value.isArrayOrNull())
|
if (!value.isArrayOrNull())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -188,6 +188,9 @@ STVar::constructST(SerializedTypeID id, int depth, Args&&... args)
|
|||||||
case STI_AMOUNT:
|
case STI_AMOUNT:
|
||||||
construct<STAmount>(std::forward<Args>(args)...);
|
construct<STAmount>(std::forward<Args>(args)...);
|
||||||
return;
|
return;
|
||||||
|
case STI_NUMBER:
|
||||||
|
construct<STNumber>(std::forward<Args>(args)...);
|
||||||
|
return;
|
||||||
case STI_UINT128:
|
case STI_UINT128:
|
||||||
construct<STUInt128>(std::forward<Args>(args)...);
|
construct<STUInt128>(std::forward<Args>(args)...);
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -78,6 +78,15 @@ struct STNumber_test : public beast::unit_test::suite
|
|||||||
STAmount const totalAmount{totalValue, strikePrice.issue()};
|
STAmount const totalAmount{totalValue, strikePrice.issue()};
|
||||||
BEAST_EXPECT(totalAmount == Number{10'000});
|
BEAST_EXPECT(totalAmount == Number{10'000});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
BEAST_EXPECT(
|
||||||
|
numberFromJson(sfNumber, "123") == STNumber(sfNumber, 123));
|
||||||
|
BEAST_EXPECT(
|
||||||
|
numberFromJson(sfNumber, "3.14e2") == STNumber(sfNumber, 314));
|
||||||
|
BEAST_EXPECT(
|
||||||
|
numberFromJson(sfNumber, "1000e-2") == STNumber(sfNumber, 10));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user