xrpld
Loading...
Searching...
No Matches
STNumber.cpp
1#include <xrpl/protocol/STNumber.h>
2
3#include <xrpl/basics/Number.h>
4#include <xrpl/basics/contract.h>
5#include <xrpl/beast/utility/instrumentation.h>
6#include <xrpl/json/json_value.h>
7#include <xrpl/protocol/Asset.h>
8#include <xrpl/protocol/Rules.h> // IWYU pragma: keep
9#include <xrpl/protocol/SField.h>
10#include <xrpl/protocol/STAmount.h>
11#include <xrpl/protocol/STBase.h>
12#include <xrpl/protocol/STTakesAsset.h>
13#include <xrpl/protocol/Serializer.h>
14
15#include <boost/lexical_cast.hpp>
16#include <boost/regex/v5/regbase.hpp>
17#include <boost/regex/v5/regex.hpp>
18#include <boost/regex/v5/regex_match.hpp>
19
20#include <cstddef>
21#include <cstdint>
22#include <format>
23#include <limits>
24#include <ostream>
25#include <stdexcept>
26#include <string>
27#include <utility>
28
29namespace xrpl {
30
32{
33}
34
36{
37 // We must call these methods in separate statements
38 // to guarantee their order of execution.
39 auto mantissa = sit.geti64();
40 auto exponent = sit.geti32();
41 value_ = Number{mantissa, exponent};
42}
43
46{
47 return STI_NUMBER;
48}
49
52{
53 return to_string(value_);
54}
55
56void
58{
60
61 XRPL_ASSERT_PARTS(
62 getFName().shouldMeta(SField::kSmdNeedsAsset),
63 "STNumber::associateAsset",
64 "field needs asset");
65
67}
68
69void
71{
72 XRPL_ASSERT(getFName().isBinary(), "xrpl::STNumber::add : field is binary");
73 XRPL_ASSERT(getFName().fieldType == getSType(), "xrpl::STNumber::add : field type match");
74
75 auto value = value_;
76 auto const mantissa = value.mantissa();
77 auto const exponent = value.exponent();
78
79 SField const& field = getFName();
81 {
82 // asset is defined in the STTakesAsset base class
83 if (asset_)
84 {
85 // The number should be rounded to the asset's precision, but round
86 // it here if it has an asset assigned.
88 XRPL_ASSERT_PARTS(value_ == value, "xrpl::STNumber::add", "value is already rounded");
89 }
90 else
91 {
92 // There are circumstances where an already-rounded Number is
93 // serialized without being touched by a transactor, and thus
94 // without an asset. We can't know if it's rounded, because it could
95 // represent _anything_, particularly when serializing user-provided
96 // Json. Regardless, the only time we should be serializing an
97 // STNumber is when the scale is large.
98 XRPL_ASSERT_PARTS(
100 "xrpl::STNumber::add",
101 "STNumber only used with large mantissa scale");
102 }
103 }
104
105 XRPL_ASSERT_PARTS(
108 "xrpl::STNumber::add",
109 "mantissa in valid range");
110 s.add64(mantissa);
111 s.add32(exponent);
112}
113
114Number const&
116{
117 return value_;
118}
119
120void
122{
123 value_ = v;
124}
125
126STBase*
127STNumber::copy(std::size_t n, void* buf) const
128{
129 return emplace(n, buf, *this);
130}
131
132STBase*
134{
135 return emplace(n, buf, std::move(*this));
136}
137
138bool
140{
141 XRPL_ASSERT(
142 t.getSType() == this->getSType(), "xrpl::STNumber::isEquivalent : field type match");
143 auto const& v = dynamic_cast<STNumber const&>(t);
144 return value_ == v;
145}
146
147bool
149{
150 return value_ == Number();
151}
152
154operator<<(std::ostream& out, STNumber const& rhs)
155{
156 return out << rhs.getText();
157}
158
159NumberParts
161{
162 static boost::regex const kReNumber(
163 "^" // the beginning of the string
164 "([-+]?)" // (optional) + or - character
165 "(0|[1-9][0-9]*)" // a number (no leading zeroes, unless 0)
166 "(\\.([0-9]+))?" // (optional) period followed by any number
167 "([eE]([+-]?)([0-9]+))?" // (optional) E, optional + or -, any number
168 "$",
169 boost::regex_constants::optimize);
170
171 boost::smatch match;
172
173 if (!boost::regex_match(number, match, kReNumber))
174 Throw<std::runtime_error>(std::format("'{}' is not a number", number));
175
176 // Match fields:
177 // 0 = whole input
178 // 1 = sign
179 // 2 = integer portion
180 // 3 = whole fraction (with '.')
181 // 4 = fraction (without '.')
182 // 5 = whole exponent (with 'e')
183 // 6 = exponent sign
184 // 7 = exponent number
185
186 bool const negative = (match[1].matched && (match[1] == "-"));
187
188 std::uint64_t mantissa = 0;
189 int exponent = 0;
190
191 if (!match[4].matched) // integer only
192 {
193 mantissa = boost::lexical_cast<std::uint64_t>(std::string(match[2]));
194 exponent = 0;
195 }
196 else
197 {
198 // integer and fraction
199 mantissa = boost::lexical_cast<std::uint64_t>(match[2] + match[4]);
200 exponent = -(match[4].length());
201 }
202
203 if (match[5].matched)
204 {
205 // we have an exponent
206 if (match[6].matched && (match[6] == "-"))
207 {
208 exponent -= boost::lexical_cast<int>(std::string(match[7]));
209 }
210 else
211 {
212 exponent += boost::lexical_cast<int>(std::string(match[7]));
213 }
214 }
215
216 return {.mantissa = mantissa, .exponent = exponent, .negative = negative};
217}
218
219STNumber
220numberFromJson(SField const& field, json::Value const& value)
221{
222 NumberParts parts;
223
224 if (value.isInt())
225 {
226 if (value.asInt() >= 0)
227 {
228 parts.mantissa = value.asInt();
229 }
230 else
231 {
232 parts.mantissa = value.asAbsUInt();
233 parts.negative = true;
234 }
235 }
236 else if (value.isUInt())
237 {
238 parts.mantissa = value.asUInt();
239 }
240 else if (value.isString())
241 {
242 parts = partsFromString(value.asString());
243
244 XRPL_ASSERT_PARTS(
245 !getCurrentTransactionRules(), "xrpld::numberFromJson", "Not in a Transactor context");
246
247 // Number mantissas are much bigger than the allowable parsed values, so
248 // it can't be out of range.
249 static_assert(
250 // NOLINTNEXTLINE(misc-redundant-expression)
252 std::numeric_limits<decltype(parts.mantissa)>::max());
253 }
254 else
255 {
256 Throw<std::runtime_error>("not a number");
257 }
258
259 Number const num{parts.negative, parts.mantissa, parts.exponent, Number::Normalized{}};
260
261 // Canonicalize "parts" and "num" with each other by getting rid of trailing 0s until either the
262 // exponents match, or there are no more 0s. If the two results don't match exactly, then the
263 // value has been rounded one way or another, and should not be used, because it may lead to an
264 // unexpected result. canonicalizeParts is not to be confused with Number::canonicalize, because
265 // they have completely different goals.
266 auto canonicalizeParts = [](NumberParts p, int otherExponent) {
267 if (p.mantissa == 0)
268 return NumberParts{};
269
270 while (p.exponent < otherExponent && p.mantissa % 10 == 0)
271 {
272 p.mantissa /= 10;
273 ++p.exponent;
274 }
275
276 return p;
277 };
278
279 auto const numberMantissa = num.mantissa();
280 auto const numberExponent = num.exponent();
281
282 auto const canonicalParts = canonicalizeParts(parts, numberExponent);
283
284 auto const canonicalNum = canonicalizeParts(
286 .mantissa = Number::externalToInternal(numberMantissa),
287 .exponent = numberExponent,
288 .negative = numberMantissa < 0,
289 },
290 canonicalParts.exponent);
291
292 if (canonicalParts.mantissa != canonicalNum.mantissa ||
293 canonicalParts.exponent != canonicalNum.exponent ||
294 canonicalParts.negative != canonicalNum.negative)
295 {
296 Throw<std::runtime_error>("number cannot be represented");
297 }
298
299 return STNumber{field, num};
300}
301
302} // namespace xrpl
Represents a JSON value.
Definition json_value.h:117
Number is a floating point type that can represent a wide range of values.
Definition Number.h:351
constexpr rep mantissa() const noexcept
Returns the mantissa of the external view of the Number.
Definition Number.h:692
static MantissaRange::MantissaScale getMantissaScale()
Returns which mantissa scale is currently in use for normalization.
constexpr int exponent() const noexcept
Returns the exponent of the external view of the Number.
Definition Number.h:714
static InternalRep externalToInternal(rep mantissa)
Identifies fields.
Definition SField.h:132
static constexpr auto kSmdNeedsAsset
Definition SField.h:143
bool shouldMeta(int c) const
Definition SField.h:265
A type which can be exported to a well known binary format.
Definition STBase.h:129
SField const & getFName() const
Definition STBase.cpp:120
static STBase * emplace(std::size_t n, void *buf, T &&val)
Definition STBase.h:226
virtual SerializedTypeID getSType() const
Definition STBase.cpp:54
A serializable number.
Definition STNumber.h:42
void add(Serializer &s) const override
Definition STNumber.cpp:70
bool isDefault() const override
Definition STNumber.cpp:148
void associateAsset(Asset const &a) override
Definition STNumber.cpp:57
void setValue(Number const &v)
Definition STNumber.cpp:121
STNumber()=default
std::string getText() const override
Definition STNumber.cpp:51
bool isEquivalent(STBase const &t) const override
Definition STNumber.cpp:139
STBase * move(std::size_t n, void *buf) override
Definition STNumber.cpp:133
SerializedTypeID getSType() const override
Definition STNumber.cpp:45
STBase * copy(std::size_t n, void *buf) const override
Definition STNumber.cpp:127
Number value_
Definition STNumber.h:44
Number const & value() const
Definition STNumber.cpp:115
Intermediate class for any STBase-derived class to store an Asset.
virtual void associateAsset(Asset const &a)
std::optional< Asset > asset_
T format(T... args)
T max(T... args)
T min(T... args)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
NumberParts partsFromString(std::string const &number)
Definition STNumber.cpp:160
std::optional< Rules > const & getCurrentTransactionRules()
Definition Rules.cpp:30
std::ostream & operator<<(std::ostream &out, BaseUInt< Bits, Tag > const &u)
Definition base_uint.h:672
std::string to_string(BaseUInt< Bits, Tag > const &a)
Definition base_uint.h:657
SerializedTypeID
Definition SField.h:94
void roundToAsset(A const &asset, Number &value)
Round an arbitrary precision Number IN PLACE to the precision of a given Asset.
Definition STAmount.h:735
STNumber numberFromJson(SField const &field, json::Value const &value)
Definition STNumber.cpp:220
XRPL_NO_SANITIZE_ADDRESS void Throw(Args &&... args)
Definition contract.h:52
std::uint64_t mantissa
Definition STNumber.h:97