userver: userver/decimal64/decimal64.hpp Source File
Loading...
Searching...
No Matches
decimal64.hpp
Go to the documentation of this file.
1#pragma once
2
3/// @file userver/decimal64/decimal64.hpp
4/// @brief Decimal data type for fixed-point arithmetic
5/// @ingroup userver_universal
6
7// Original source taken from https://github.com/vpiotr/decimal_for_cpp
8// Original license:
9//
10// ==================================================================
11// Name: decimal.h
12// Purpose: Decimal data type support, for COBOL-like fixed-point
13// operations on currency values.
14// Author: Piotr Likus
15// Created: 03/01/2011
16// Modified: 23/09/2018
17// Version: 1.16
18// Licence: BSD
19// ==================================================================
20
21#include <array>
22#include <cassert>
23#include <cstdint>
24#include <ios>
25#include <iosfwd>
26#include <istream>
27#include <limits>
28#include <numeric>
29#include <optional>
30#include <stdexcept>
31#include <string>
32#include <string_view>
33#include <type_traits>
34
35#include <fmt/compile.h>
36#include <fmt/format.h>
37
38#include <userver/compiler/impl/three_way_comparison.hpp>
39#include <userver/decimal64/format_options.hpp>
40#include <userver/formats/common/meta.hpp>
41#include <userver/utils/assert.hpp>
42#include <userver/utils/flags.hpp>
43#include <userver/utils/meta_light.hpp>
44
45USERVER_NAMESPACE_BEGIN
46
47namespace logging {
48
49class LogHelper;
50
51} // namespace logging
52
53/// Fixed-point decimal data type and related functions
54namespace decimal64 {
55
56/// The base class for Decimal-related exceptions
57class DecimalError : public std::runtime_error {
58public:
59 using std::runtime_error::runtime_error;
60};
61
62/// Thrown on all errors related to parsing `Decimal` from string
63class ParseError : public DecimalError {
64public:
65 using DecimalError::DecimalError;
66};
67
68/// Thrown on overflow in `Decimal` arithmetic
70public:
71 OutOfBoundsError();
72};
73
74/// Thrown on division by zero in `Decimal` arithmetic
76public:
77 DivisionByZeroError();
78};
79
80namespace impl {
81
82inline constexpr auto kMaxInt64 = std::numeric_limits<int64_t>::max();
83inline constexpr auto kMinInt64 = std::numeric_limits<int64_t>::min();
84
85// Note: static_cast may introduce an inaccuracy. To be on the safe side,
86// we'd have to call std::nextafter, but it's not constexpr until C++23.
87inline constexpr auto kMinRepresentableLongDouble =
88 static_cast<long double>(impl::kMinInt64) * (1 - 2 * std::numeric_limits<long double>::epsilon());
89inline constexpr auto kMaxRepresentableLongDouble =
90 static_cast<long double>(impl::kMaxInt64) * (1 - 2 * std::numeric_limits<long double>::epsilon());
91
92template <typename T>
93using EnableIfInt = std::enable_if_t<meta::kIsInteger<T>, int>;
94
95template <typename T>
96using EnableIfFloat = std::enable_if_t<std::is_floating_point_v<T>, int>;
97
98template <int MaxExp>
99constexpr std::array<int64_t, MaxExp + 1> PowSeries(int64_t base) {
100 int64_t pow = 1;
101 std::array<int64_t, MaxExp + 1> result{};
102 for (int i = 0; i < MaxExp; ++i) {
103 result[i] = pow;
104 if (pow > kMaxInt64 / base) {
105 throw OutOfBoundsError();
106 }
107 pow *= base;
108 }
109 result[MaxExp] = pow;
110 return result;
111}
112
113inline constexpr int kMaxDecimalDigits = 18;
114inline constexpr auto kPowSeries10 = PowSeries<kMaxDecimalDigits>(10);
115
116// Check that kMaxDecimalDigits is indeed max integer x such that 10^x is valid
117static_assert(kMaxInt64 / 10 < kPowSeries10[kMaxDecimalDigits]);
118
119template <typename RoundPolicy>
120constexpr int64_t Div(int64_t nominator, int64_t denominator, bool extra_odd_quotient = false) {
121 // RoundPolicies don't protect against arithmetic errors
122 if (denominator == 0) {
123 throw DivisionByZeroError();
124 }
125 if (denominator == -1) {
126 if (nominator == kMinInt64) {
127 throw OutOfBoundsError();
128 }
129 return -nominator; // RoundPolicies behave badly for denominator == -1
130 }
131
132 return RoundPolicy::DivRounded(nominator, denominator, extra_odd_quotient);
133}
134
135// result = (value1 * value2) / divisor
136template <typename RoundPolicy>
137constexpr int64_t MulDiv(int64_t value1, int64_t value2, int64_t divisor) {
138 if (divisor == 0) {
139 throw DivisionByZeroError();
140 }
141
142#if __x86_64__ || __ppc64__ || __aarch64__
143 using LongInt = __int128_t;
144 static_assert(sizeof(void*) == 8);
145#else
146 using LongInt = int64_t;
147 static_assert(sizeof(void*) == 4);
148#endif
149
150 LongInt prod{};
151 if constexpr (sizeof(void*) == 4) {
152 if (__builtin_mul_overflow(static_cast<LongInt>(value1), static_cast<LongInt>(value2), &prod)) {
153 throw OutOfBoundsError();
154 }
155 } else {
156 prod = static_cast<LongInt>(value1) * value2;
157 }
158 const auto whole = prod / divisor;
159 const auto rem = static_cast<int64_t>(prod % divisor);
160
161 if (whole <= kMinInt64 || whole >= kMaxInt64) {
162 throw OutOfBoundsError();
163 }
164
165 const auto whole64 = static_cast<int64_t>(whole);
166 const bool extra_odd_quotient = whole64 % 2 != 0;
167 const int64_t rem_divided = Div<RoundPolicy>(rem, divisor, extra_odd_quotient);
168 UASSERT(rem_divided == -1 || rem_divided == 0 || rem_divided == 1);
169
170 return whole64 + rem_divided;
171}
172
173constexpr int Sign(int64_t value) { return (value > 0) - (value < 0); }
174
175// Needed because std::abs is not constexpr
176constexpr int64_t Abs(int64_t value) {
177 if (value == kMinInt64) {
178 throw OutOfBoundsError();
179 }
180 return value >= 0 ? value : -value;
181}
182
183// Needed because std::abs is not constexpr
184// Insignificantly less performant
185template <typename T>
186constexpr int64_t Abs(T value) {
187 static_assert(std::is_floating_point_v<T>);
188 return value >= 0 ? value : -value;
189}
190
191// Needed because std::floor is not constexpr
192// Insignificantly less performant
193template <typename T>
194constexpr int64_t Floor(T value) {
195 if (static_cast<int64_t>(value) <= value) { // whole or positive
196 return static_cast<int64_t>(value);
197 } else {
198 return static_cast<int64_t>(value) - 1;
199 }
200}
201
202// Needed because std::ceil is not constexpr
203// Insignificantly less performant
204template <typename T>
205constexpr int64_t Ceil(T value) {
206 if (static_cast<int64_t>(value) >= value) { // whole or negative
207 return static_cast<int64_t>(value);
208 } else {
209 return static_cast<int64_t>(value) + 1;
210 }
211}
212
213template <typename Int>
214constexpr int64_t ToInt64(Int value) {
215 static_assert(meta::kIsInteger<Int>);
216 static_assert(sizeof(Int) <= sizeof(int64_t));
217
218 if constexpr (sizeof(Int) == sizeof(int64_t)) {
219 if (value > kMaxInt64) {
220 throw OutOfBoundsError();
221 }
222 }
223 return static_cast<int64_t>(value);
224}
225
226class HalfUpPolicy final {
227public:
228 // returns 'true' iff 'abs' should be rounded away from 0
229 template <typename T>
230 [[nodiscard]] static constexpr bool ShouldRoundAwayFromZero(T abs) {
231 const T abs_remainder = abs - static_cast<int64_t>(abs);
232 return abs_remainder >= 0.5;
233 }
234
235 // returns 'true' iff 'a / b' should be rounded away from 0
236 static constexpr bool ShouldRoundAwayFromZeroDiv(int64_t a, int64_t b, bool /*extra_odd_quotient*/) {
237 const int64_t abs_a = impl::Abs(a);
238 const int64_t abs_b = impl::Abs(b);
239 const int64_t half_b = abs_b / 2;
240 const int64_t abs_remainder = abs_a % abs_b;
241 return abs_b % 2 == 0 ? abs_remainder >= half_b : abs_remainder > half_b;
242 }
243};
244
245class HalfDownPolicy final {
246public:
247 // returns 'true' iff 'abs' should be rounded away from 0
248 template <typename T>
249 [[nodiscard]] static constexpr bool ShouldRoundAwayFromZero(T abs) {
250 const T abs_remainder = abs - static_cast<int64_t>(abs);
251 return abs_remainder > 0.5;
252 }
253
254 // returns 'true' iff 'a / b' should be rounded away from 0
255 static constexpr bool ShouldRoundAwayFromZeroDiv(int64_t a, int64_t b, bool /*extra_odd_quotient*/) {
256 const int64_t abs_a = impl::Abs(a);
257 const int64_t abs_b = impl::Abs(b);
258 const int64_t half_b = abs_b / 2;
259 const int64_t abs_remainder = abs_a % abs_b;
260 return abs_remainder > half_b;
261 }
262};
263
264class HalfEvenPolicy final {
265public:
266 // returns 'true' iff 'abs' should be rounded away from 0
267 template <typename T>
268 [[nodiscard]] static constexpr bool ShouldRoundAwayFromZero(T abs) {
269 const T abs_remainder = abs - static_cast<int64_t>(abs);
270 return abs_remainder == 0.5 ? impl::Floor(abs) % 2 != 0 : abs_remainder > 0.5;
271 }
272
273 // returns 'true' iff 'a / b' should be rounded away from 0
274 static constexpr bool ShouldRoundAwayFromZeroDiv(int64_t a, int64_t b, bool extra_odd_quotient) {
275 const int64_t abs_a = impl::Abs(a);
276 const int64_t abs_b = impl::Abs(b);
277 const int64_t half_b = abs_b / 2;
278 const int64_t abs_remainder = abs_a % abs_b;
279 return (abs_b % 2 == 0 && abs_remainder == half_b)
280 ? ((abs_a / abs_b) % 2 == 0) == extra_odd_quotient
281 : abs_remainder > half_b;
282 }
283};
284
285template <typename HalfPolicy>
286class HalfRoundPolicyBase {
287public:
288 template <typename T>
289 [[nodiscard]] static constexpr int64_t Round(T value) {
290 if ((value >= 0.0) == HalfPolicy::ShouldRoundAwayFromZero(value)) {
291 return impl::Ceil(value);
292 } else {
293 return impl::Floor(value);
294 }
295 }
296
297 [[nodiscard]] static constexpr int64_t DivRounded(int64_t a, int64_t b, bool extra_odd_quotient) {
298 if (HalfPolicy::ShouldRoundAwayFromZeroDiv(a, b, extra_odd_quotient)) {
299 const auto quotient_sign = impl::Sign(a) * impl::Sign(b);
300 return (a / b) + quotient_sign; // round away from 0
301 } else {
302 return a / b; // round towards 0
303 }
304 }
305};
306
307} // namespace impl
308
309/// A fast, constexpr-friendly power of 10
310constexpr int64_t Pow10(int exp) {
311 if (exp < 0 || exp > impl::kMaxDecimalDigits) {
312 throw std::runtime_error("Pow10: invalid power of 10");
313 }
314 return impl::kPowSeries10[static_cast<size_t>(exp)];
315}
316
317/// A guaranteed-compile-time power of 10
318template <int Exp>
319inline constexpr int64_t kPow10 = Pow10(Exp);
320
321/// @brief Default rounding. Fast, rounds to nearest.
322///
323/// On 0.5, rounds away from zero. Also, sometimes rounds up numbers
324/// in the neighborhood of 0.5, e.g. 0.49999999999999994 -> 1.
325class DefRoundPolicy final {
326public:
327 template <typename T>
328 [[nodiscard]] static constexpr int64_t Round(T value) {
329 return static_cast<int64_t>(value + (value < 0 ? -0.5 : 0.5));
330 }
331
332 [[nodiscard]] static constexpr int64_t DivRounded(int64_t a, int64_t b, bool /*extra_odd_quotient*/) {
333 const int64_t divisor_corr = impl::Abs(b / 2);
334 if (a >= 0) {
335 if (impl::kMaxInt64 - a < divisor_corr) {
336 throw OutOfBoundsError();
337 }
338 return (a + divisor_corr) / b;
339 } else {
340 if (-(impl::kMinInt64 - a) < divisor_corr) {
341 throw OutOfBoundsError();
342 }
343 return (a - divisor_corr) / b;
344 }
345 }
346};
347
348/// Round to nearest, 0.5 towards zero
349class HalfDownRoundPolicy final : public impl::HalfRoundPolicyBase<impl::HalfDownPolicy> {};
350
351/// Round to nearest, 0.5 away from zero
352class HalfUpRoundPolicy final : public impl::HalfRoundPolicyBase<impl::HalfUpPolicy> {};
353
354/// Round to nearest, 0.5 towards number with even last digit
355class HalfEvenRoundPolicy final : public impl::HalfRoundPolicyBase<impl::HalfEvenPolicy> {};
356
357/// Round towards +infinity
359public:
360 template <typename T>
361 [[nodiscard]] static constexpr int64_t Round(T value) {
362 return impl::Ceil(value);
363 }
364
365 [[nodiscard]] static constexpr int64_t DivRounded(int64_t a, int64_t b, bool /*extra_odd_quotient*/) {
366 const bool quotient_positive = (a >= 0) == (b >= 0);
367 return (a / b) + (a % b != 0 && quotient_positive);
368 }
369};
370
371/// Round towards -infinity
372class FloorRoundPolicy final {
373public:
374 template <typename T>
375 [[nodiscard]] static constexpr int64_t Round(T value) {
376 return impl::Floor(value);
377 }
378
379 [[nodiscard]] static constexpr int64_t DivRounded(int64_t a, int64_t b, bool /*extra_odd_quotient*/) {
380 const bool quotient_negative = (a < 0) != (b < 0);
381 return (a / b) - (a % b != 0 && quotient_negative);
382 }
383};
384
385/// Round towards zero. The fastest rounding.
386class RoundDownRoundPolicy final {
387public:
388 template <typename T>
389 [[nodiscard]] static constexpr int64_t Round(T value) {
390 return static_cast<int64_t>(value);
391 }
392
393 [[nodiscard]] static constexpr int64_t DivRounded(int64_t a, int64_t b, bool /*extra_odd_quotient*/) {
394 return a / b;
395 }
396};
397
398/// Round away from zero
399class RoundUpRoundPolicy final {
400public:
401 template <typename T>
402 [[nodiscard]] static constexpr int64_t Round(T value) {
403 if (value >= 0.0) {
404 return impl::Ceil(value);
405 } else {
406 return impl::Floor(value);
407 }
408 }
409
410 [[nodiscard]] static constexpr int64_t DivRounded(int64_t a, int64_t b, bool /*extra_odd_quotient*/) {
411 const auto quotient_sign = impl::Sign(a) * impl::Sign(b);
412 return (a / b) + (a % b != 0) * quotient_sign;
413 }
414};
415
416/// @ingroup userver_universal userver_containers
417///
418/// @brief Fixed-point decimal data type for use in deterministic calculations,
419/// oftentimes involving money
420///
421/// @tparam Prec The number of fractional digits
422/// @tparam RoundPolicy Specifies how to round in lossy operations
423///
424/// Decimal is internally represented as `int64_t`. It means that it can be
425/// passed around by value. It also means that operations with huge
426/// numbers can overflow and trap. For example, with `Prec == 6`, the maximum
427/// representable number is about 10 trillion.
428///
429/// Decimal should be serialized and stored as a string, NOT as `double`. Use
430/// `Decimal{str}` constructor (or `Decimal::FromStringPermissive` if rounding
431/// and exponential format are allowed) to read a `Decimal`, and `ToString(dec)`
432/// (or `ToStringTrailingZeros(dec)`/`ToStringFixed<N>(dec)`) to write a
433/// `Decimal`.
434///
435/// Use arithmetic with caution! Multiplication and division operations involve
436/// rounding. You may want to cast to `Decimal` with another `Prec`
437/// or `RoundPolicy` beforehand. For that purpose you can use
438/// `decimal64::decimal_cast<NewDec>(dec)`.
439///
440/// Usage example:
441/// @code{.cpp}
442/// // create a single alias instead of specifying Decimal everywhere
443/// using Money = decimal64::Decimal<4, decimal64::HalfEvenRoundPolicy>;
444///
445/// std::vector<std::string> cart = ...;
446/// Money sum{0};
447/// for (const std::string& cost_string : cart) {
448/// // or use FromStringPermissive to enable rounding
449/// sum += Money{cost_string};
450/// }
451/// return ToString(sum);
452/// @endcode
453template <int Prec, typename TRoundPolicy = DefRoundPolicy>
454class Decimal {
455public:
456 /// The number of fractional digits
457 static constexpr int kDecimalPoints = Prec;
458
459 /// Specifies how to round in lossy operations
460 using RoundPolicy = TRoundPolicy;
461
462 /// The denominator of the decimal fraction
463 static constexpr int64_t kDecimalFactor = kPow10<Prec>;
464
465 /// Zero by default
466 constexpr Decimal() noexcept = default;
467
468 /// @brief Convert from an integer
469 template <typename Int, impl::EnableIfInt<Int> = 0>
470 explicit constexpr Decimal(Int value)
471 : Decimal(FromDecimal(Decimal<0>::FromUnbiased(impl::ToInt64(value))))
472 {}
473
474 /// @brief Convert from a string
475 ///
476 /// The string must match the following regexp exactly:
477 ///
478 /// [+-]?\d+(\.\d+)?
479 ///
480 /// No extra characters, including spaces, are allowed. Extra leading
481 /// and trailing zeros (within `Prec`) are discarded. Input containing more
482 /// fractional digits that `Prec` is not allowed (no implicit rounding).
483 /// Exponential format (e.g., "1.23e4", "5E-2") is not supported by this
484 /// constructor.
485 ///
486 /// @throw decimal64::ParseError on invalid input
487 /// @see FromStringPermissive
488 explicit constexpr Decimal(std::string_view value);
489
490 /// @brief Lossy conversion from a floating-point number
491 ///
492 /// To somewhat resist the accumulated error, the number is always rounded
493 /// to the nearest Decimal, regardless of `RoundPolicy`.
494 ///
495 /// @warning Prefer storing and sending `Decimal` as string, and performing
496 /// the computations between `Decimal`s.
497 template <typename T>
498 static constexpr Decimal FromFloatInexact(T value) {
499 static_assert(std::is_floating_point_v<T>);
500 // Check that overflow does not occur when converting to int64_t
501 // (constexpr detects UB).
502 static_assert(DefRoundPolicy::Round(impl::kMinRepresentableLongDouble) < 0);
503 static_assert(DefRoundPolicy::Round(impl::kMaxRepresentableLongDouble) > 0);
504
505 const auto unbiased_float = static_cast<long double>(value) * kDecimalFactor;
506 if (unbiased_float < impl::kMinRepresentableLongDouble || unbiased_float > impl::kMaxRepresentableLongDouble) {
507 throw OutOfBoundsError();
508 }
509 return FromUnbiased(DefRoundPolicy::Round(unbiased_float));
510 }
511
512 /// @brief Convert from a string, allowing rounding, spaces and boundary dot
513 ///
514 /// In addition to the `Decimal(str)` constructor, allows:
515 /// - rounding (as per `RoundPolicy`), e.g. "12.3456789" with `Prec == 2`
516 /// - space characters, e.g. " \t42 \n"
517 /// - leading and trailing dot, e.g. "5." and ".5"
518 /// - exponential format, e.g. "1.23e4" -> 12300, "5E-2" -> 0.05
519 ///
520 /// @throw decimal64::ParseError on invalid input
521 /// @see Decimal(std::string_view)
522 static constexpr Decimal FromStringPermissive(std::string_view input);
523
524 /// @brief Reconstruct from the internal representation, as acquired
525 /// with `AsUnbiased`
526 ///
527 /// The Decimal value will be equal to `value/kDecimalFactor`.
528 ///
529 /// @see AsUnbiased
530 static constexpr Decimal FromUnbiased(int64_t value) noexcept {
531 Decimal result;
532 result.value_ = value;
533 return result;
534 }
535
536 /// @brief Convert from `original_unbiased / 10^original_precision`, rounding
537 /// according to `RoundPolicy` if necessary
538 ///
539 /// Usage examples:
540 ///
541 /// Decimal<4>::FromBiased(123, 6) -> 0.0001
542 /// Decimal<4>::FromBiased(123, 2) -> 1.23
543 /// Decimal<4>::FromBiased(123, -1) -> 1230
544 ///
545 /// @param original_unbiased The original mantissa
546 /// @param original_precision The original precision (negated exponent)
547 static constexpr Decimal FromBiased(int64_t original_unbiased, int original_precision) {
548 const int exponent_for_pack = Prec - original_precision;
549
550 if (exponent_for_pack >= 0) {
551 return FromUnbiased(original_unbiased) * Pow10(exponent_for_pack);
552 } else {
553 return FromUnbiased(impl::Div<RoundPolicy>(original_unbiased, Pow10(-exponent_for_pack)));
554 }
555 }
556
557 /// @brief Assignment from another `Decimal`
558 ///
559 /// The assignment is allowed as long as `RoundPolicy` is the same. Rounding
560 /// will be performed according to `RoundPolicy` if necessary.
561 template <int Prec2>
562 Decimal& operator=(Decimal<Prec2, RoundPolicy> rhs) {
563 *this = FromDecimal(rhs);
564 return *this;
565 }
566
567 /// @brief Assignment from an integer
568 template <typename Int, impl::EnableIfInt<Int> = 0>
569 constexpr Decimal& operator=(Int rhs) {
570 *this = Decimal{rhs};
571 return *this;
572 }
573
574#ifdef USERVER_IMPL_HAS_THREE_WAY_COMPARISON
575 constexpr auto operator<=>(const Decimal& rhs) const = default;
576#else
577 constexpr bool operator==(Decimal rhs) const { return value_ == rhs.value_; }
578
579 constexpr bool operator!=(Decimal rhs) const { return value_ != rhs.value_; }
580
581 constexpr bool operator<(Decimal rhs) const { return value_ < rhs.value_; }
582
583 constexpr bool operator<=(Decimal rhs) const { return value_ <= rhs.value_; }
584
585 constexpr bool operator>(Decimal rhs) const { return value_ > rhs.value_; }
586
587 constexpr bool operator>=(Decimal rhs) const { return value_ >= rhs.value_; }
588#endif
589
590 constexpr Decimal operator+() const { return *this; }
591
592 constexpr Decimal operator-() const {
593 if (value_ == impl::kMinInt64) {
594 throw OutOfBoundsError();
595 }
596 return FromUnbiased(-value_);
597 }
598
599 template <int Prec2>
600 constexpr auto operator+(Decimal<Prec2, RoundPolicy> rhs) const {
601 if constexpr (Prec2 > Prec) {
602 return Decimal<Prec2, RoundPolicy>::FromDecimal(*this) + rhs;
603 } else if constexpr (Prec2 < Prec) {
604 return *this + FromDecimal(rhs);
605 } else {
606 int64_t result{};
607 if (__builtin_add_overflow(AsUnbiased(), rhs.AsUnbiased(), &result)) {
608 throw OutOfBoundsError();
609 }
610 return FromUnbiased(result);
611 }
612 }
613
614 template <typename Int, impl::EnableIfInt<Int> = 0>
615 constexpr Decimal operator+(Int rhs) const {
616 return *this + Decimal{rhs};
617 }
618
619 template <typename Int, impl::EnableIfInt<Int> = 0>
620 friend constexpr Decimal operator+(Int lhs, Decimal rhs) {
621 return Decimal{lhs} + rhs;
622 }
623
624 template <int Prec2>
625 constexpr Decimal& operator+=(Decimal<Prec2, RoundPolicy> rhs) {
626 static_assert(Prec2 <= Prec, "Implicit cast to Decimal of lower precision in assignment");
627 *this = *this + rhs;
628 return *this;
629 }
630
631 template <typename Int, impl::EnableIfInt<Int> = 0>
632 constexpr Decimal& operator+=(Int rhs) {
633 *this = *this + rhs;
634 return *this;
635 }
636
637 template <int Prec2>
638 constexpr auto operator-(Decimal<Prec2, RoundPolicy> rhs) const {
639 if constexpr (Prec2 > Prec) {
640 return Decimal<Prec2, RoundPolicy>::FromDecimal(*this) - rhs;
641 } else if constexpr (Prec2 < Prec) {
642 return *this - FromDecimal(rhs);
643 } else {
644 int64_t result{};
645 if (__builtin_sub_overflow(AsUnbiased(), rhs.AsUnbiased(), &result)) {
646 throw OutOfBoundsError();
647 }
648 return FromUnbiased(result);
649 }
650 }
651
652 template <typename Int, impl::EnableIfInt<Int> = 0>
653 constexpr Decimal operator-(Int rhs) const {
654 return *this - Decimal{rhs};
655 }
656
657 template <typename Int, impl::EnableIfInt<Int> = 0>
658 friend constexpr Decimal operator-(Int lhs, Decimal rhs) {
659 return Decimal{lhs} - rhs;
660 }
661
662 template <int Prec2>
663 constexpr Decimal& operator-=(Decimal<Prec2, RoundPolicy> rhs) {
664 static_assert(Prec2 <= Prec, "Implicit cast to Decimal of lower precision in assignment");
665 *this = *this - rhs;
666 return *this;
667 }
668
669 template <typename Int, impl::EnableIfInt<Int> = 0>
670 constexpr Decimal& operator-=(Int rhs) {
671 *this = *this - rhs;
672 return *this;
673 }
674
675 template <typename Int, typename = impl::EnableIfInt<Int>>
676 constexpr Decimal operator*(Int rhs) const {
677 int64_t result{};
678 if (rhs > impl::kMaxInt64 || __builtin_mul_overflow(value_, static_cast<int64_t>(rhs), &result)) {
679 throw OutOfBoundsError();
680 }
681 return FromUnbiased(result);
682 }
683
684 template <typename Int, impl::EnableIfInt<Int> = 0>
685 friend constexpr Decimal operator*(Int lhs, Decimal rhs) {
686 return rhs * lhs;
687 }
688
689 template <typename Int, impl::EnableIfInt<Int> = 0>
690 constexpr Decimal& operator*=(Int rhs) {
691 *this = *this * rhs;
692 return *this;
693 }
694
695 template <int Prec2>
696 constexpr Decimal operator*(Decimal<Prec2, RoundPolicy> rhs) const {
697 return FromUnbiased(impl::MulDiv<RoundPolicy>(AsUnbiased(), rhs.AsUnbiased(), kPow10<Prec2>));
698 }
699
700 template <int Prec2>
701 constexpr Decimal& operator*=(Decimal<Prec2, RoundPolicy> rhs) {
702 *this = *this * rhs;
703 return *this;
704 }
705
706 template <typename Int, typename = impl::EnableIfInt<Int>>
707 constexpr Decimal operator/(Int rhs) const {
708 return FromUnbiased(impl::Div<RoundPolicy>(AsUnbiased(), rhs));
709 }
710
711 template <typename Int, typename = impl::EnableIfInt<Int>>
712 friend constexpr Decimal operator/(Int lhs, Decimal rhs) {
713 return Decimal{lhs} / rhs;
714 }
715
716 template <typename Int, typename = impl::EnableIfInt<Int>>
717 constexpr Decimal& operator/=(Int rhs) {
718 *this = *this / rhs;
719 return *this;
720 }
721
722 template <int Prec2>
723 constexpr Decimal operator/(Decimal<Prec2, RoundPolicy> rhs) const {
724 return FromUnbiased(impl::MulDiv<RoundPolicy>(AsUnbiased(), kPow10<Prec2>, rhs.AsUnbiased()));
725 }
726
727 template <int Prec2>
728 constexpr Decimal& operator/=(Decimal<Prec2, RoundPolicy> rhs) {
729 *this = *this / rhs;
730 return *this;
731 }
732
733 /// Returns one of {-1, 0, +1}, depending on the sign of the `Decimal`
734 constexpr int Sign() const { return impl::Sign(value_); }
735
736 /// Returns the absolute value of the `Decimal`
737 constexpr Decimal Abs() const { return FromUnbiased(impl::Abs(value_)); }
738
739 /// Rounds `this` to the nearest multiple of `base` according to `RoundPolicy`
740 constexpr Decimal RoundToMultipleOf(Decimal base) const {
741 if (base < Decimal{0}) {
742 throw OutOfBoundsError();
743 }
744 return *this / base.AsUnbiased() * base.AsUnbiased();
745 }
746
747 /// Returns the value rounded to integer using the active rounding policy
748 constexpr int64_t ToInteger() const { return impl::Div<RoundPolicy>(value_, kDecimalFactor); }
749
750 /// @brief Returns the value converted to `double`
751 ///
752 /// @warning Operations with `double`, and even the returned value,
753 /// is inexact. Prefer storing and sending `Decimal` as string, and performing
754 /// the computations between `Decimal`s.
755 ///
756 /// @see FromFloatInexact
757 constexpr double ToDoubleInexact() const {
758 // maximum number that can be represented without modification
759 constexpr std::int64_t kLossLimit = (static_cast<std::int64_t>(1) << std::numeric_limits<double>::digits);
760
761 if (value_ > -kLossLimit && value_ < kLossLimit) {
762 return static_cast<double>(value_) / kDecimalFactor;
763 }
764
765 constexpr int kCoef =
766 1
767 << (std::max(std::numeric_limits<std::int64_t>::digits - std::numeric_limits<double>::digits - 3 * Prec, 0)
768 );
769
770 // divide the value into two parts (each no more than kLossLimit)
771 const std::int64_t p1 = value_ / (kDecimalFactor * kCoef) * kCoef;
772 const std::int64_t p2 = value_ % (kDecimalFactor * kCoef);
773
774 // combine without loss of accuracy
775 return p1 + static_cast<double>(p2) / kDecimalFactor;
776 }
777
778 /// @brief Retrieve the internal representation
779 ///
780 /// The internal representation of `Decimal` is `real_value * kDecimalFactor`.
781 /// Use for storing the value of Decimal efficiently when `Prec` is guaranteed
782 /// not to change.
783 ///
784 /// @see FromUnbiased
785 constexpr int64_t AsUnbiased() const { return value_; }
786
787private:
788 template <int Prec2, typename RoundPolicy2>
789 static constexpr Decimal FromDecimal(Decimal<Prec2, RoundPolicy2> source) {
790 if constexpr (Prec > Prec2) {
791 int64_t result{};
792 if (__builtin_mul_overflow(source.AsUnbiased(), kPow10<Prec - Prec2>, &result)) {
793 throw OutOfBoundsError();
794 }
795 return FromUnbiased(result);
796 } else if constexpr (Prec < Prec2) {
797 return FromUnbiased(impl::Div<RoundPolicy>(source.AsUnbiased(), kPow10<Prec2 - Prec>));
798 } else {
799 return FromUnbiased(source.AsUnbiased());
800 }
801 }
802
803 template <int Prec2, typename RoundPolicy2>
804 friend class Decimal;
805
806 template <typename T, int OldPrec, typename OldRound>
807 friend constexpr T decimal_cast(Decimal<OldPrec, OldRound> arg); // NOLINT(readability-identifier-naming)
808
809 int64_t value_{0};
810};
811
812namespace impl {
813
814template <typename T>
815struct IsDecimal : std::false_type {};
816
817template <int Prec, typename RoundPolicy>
818struct IsDecimal<Decimal<Prec, RoundPolicy>> : std::true_type {};
819
820} // namespace impl
821
822/// `true` if the type is an instantiation of `Decimal`
823template <typename T>
824inline constexpr bool kIsDecimal = impl::IsDecimal<T>::value;
825
826/// @brief Cast one `Decimal` to another `Decimal` type
827///
828/// When casting to a `Decimal` with a lower `Prec`, rounding is performed
829/// according to the new `RoundPolicy`.
830///
831/// Usage example:
832/// @code{.cpp}
833/// using Money = decimal64::Decimal<4>;
834/// using Discount = decimal64::Decimal<4, FloorRoundPolicy>;
835///
836/// Money cost = ...;
837/// auto discount = decimal64::decimal_cast<Discount>(cost) * Discount{"0.05"};
838/// @endcode
839template <typename T, int OldPrec, typename OldRound>
840constexpr T decimal_cast(Decimal<OldPrec, OldRound> arg) { // NOLINT(readability-identifier-naming)
841 static_assert(kIsDecimal<T>);
842 return T::FromDecimal(arg);
843}
844
845namespace impl {
846
847// FromUnpacked<Decimal<4>>(12, 34) -> 12.0034
848// FromUnpacked<Decimal<4>>(-12, -34) -> -12.0034
849// FromUnpacked<Decimal<4>>(0, -34) -> -0.0034
850template <int Prec, typename RoundPolicy>
851constexpr Decimal<Prec, RoundPolicy> FromUnpacked(int64_t before, int64_t after) {
852 using Dec = Decimal<Prec, RoundPolicy>;
853 UASSERT(((before >= 0) && (after >= 0)) || ((before <= 0) && (after <= 0)));
854
855 int64_t result{};
856 if (__builtin_mul_overflow(before, Dec::kDecimalFactor, &result) || __builtin_add_overflow(result, after, &result))
857 {
858 throw OutOfBoundsError();
859 }
860
861 return Dec::FromUnbiased(result);
862}
863
864// FromUnpacked<Decimal<4>>(12, 34, 3) -> 12.034
865template <int Prec, typename RoundPolicy>
866constexpr Decimal<Prec, RoundPolicy> FromUnpacked(int64_t before, int64_t after, int original_precision) {
867 UASSERT(((before >= 0) && (after >= 0)) || ((before <= 0) && (after <= 0)));
868 UASSERT(after > -Pow10(original_precision) && after < Pow10(original_precision));
869
870 if (original_precision <= Prec) {
871 // direct mode
872 const int missing_digits = Prec - original_precision;
873 const int64_t factor = Pow10(missing_digits);
874 return FromUnpacked<Prec, RoundPolicy>(before, after * factor);
875 } else {
876 // rounding mode
877 const int extra_digits = original_precision - Prec;
878 const int64_t factor = Pow10(extra_digits);
879 // note: if rounded up, rounded_after may represent a "fractional part"
880 // greater than 1.0, which is ok
881 const int64_t rounded_after = Div<RoundPolicy>(after, factor);
882 return FromUnpacked<Prec, RoundPolicy>(before, rounded_after);
883 }
884}
885
886template <int Prec, typename RoundPolicy>
887constexpr Decimal<Prec, RoundPolicy> FromUnpackedWithExponent(
888 int64_t before,
889 int64_t after,
890 int original_precision,
891 int exponent,
892 bool is_negative_exponent,
893 int leading_zeros
894) {
895 UASSERT(((before >= 0) && (after >= 0)) || ((before <= 0) && (after <= 0)));
896 UASSERT(after > -Pow10(original_precision) && after < Pow10(original_precision));
897
898 if (before == 0 && after == 0) {
899 return Decimal<Prec, RoundPolicy>::FromUnbiased(0);
900 }
901
902 const int effective_exponent = is_negative_exponent ? -exponent : exponent;
903
904 if (effective_exponent + Prec < -kMaxDecimalDigits) {
905 return Decimal<Prec, RoundPolicy>::FromUnbiased(0);
906 }
907
908 int total_scale = effective_exponent + Prec - original_precision;
909
910 if (before == 0 && after != 0) {
911 if (!is_negative_exponent) {
912 if (exponent >= leading_zeros) {
913 exponent -= leading_zeros;
914 original_precision -= leading_zeros;
915 } else {
916 original_precision -= exponent;
917 exponent = 0;
918 }
919 }
920 }
921
922 int64_t value = before;
923
924 if (__builtin_mul_overflow(value, Pow10(original_precision), &value) ||
925 __builtin_add_overflow(value, after, &value))
926 {
927 throw OutOfBoundsError();
928 }
929
930 if (total_scale > 0) {
931 if (total_scale > kMaxDecimalDigits) {
932 throw OutOfBoundsError();
933 }
934 if (__builtin_mul_overflow(value, Pow10(total_scale), &value)) {
935 throw OutOfBoundsError();
936 }
937 } else if (total_scale < 0) {
938 const int64_t divisor = Pow10(-total_scale);
939 value = Div<RoundPolicy>(value, divisor);
940 }
941
942 return Decimal<Prec, RoundPolicy>::FromUnbiased(value);
943}
944
945struct UnpackedDecimal {
946 int64_t before;
947 int64_t after;
948};
949
950// AsUnpacked(Decimal<4>{"3.14"}) -> {3, 1400}
951// AsUnpacked(Decimal<4>{"-3.14"}) -> {-3, -1400}
952// AsUnpacked(Decimal<4>{"-0.14"}) -> {0, -1400}
953template <int Prec, typename RoundPolicy>
954constexpr UnpackedDecimal AsUnpacked(Decimal<Prec, RoundPolicy> dec) {
955 using Dec = Decimal<Prec, RoundPolicy>;
956 return {dec.AsUnbiased() / Dec::kDecimalFactor, dec.AsUnbiased() % Dec::kDecimalFactor};
957}
958
959// AsUnpacked(Decimal<4>{"3.14"}, 5) -> {3, 14000}
960// AsUnpacked(Decimal<4>{"-3.14"}, 6) -> {-3, -140000}
961// AsUnpacked(Decimal<4>{"-0.14"}, 1) -> {0, -1}
962template <int Prec, typename RoundPolicy>
963UnpackedDecimal AsUnpacked(Decimal<Prec, RoundPolicy> dec, int new_prec) {
964 if (new_prec == Prec) {
965 return AsUnpacked(dec);
966 }
967 int64_t result{};
968 if (new_prec > Prec) {
969 if (__builtin_mul_overflow(dec.AsUnbiased(), Pow10(new_prec - Prec), &result)) {
970 throw OutOfBoundsError();
971 }
972 } else {
973 result = impl::Div<RoundPolicy>(dec.AsUnbiased(), Pow10(Prec - new_prec));
974 }
975 const auto dec_factor = Pow10(new_prec);
976 return {result / dec_factor, result % dec_factor};
977}
978
979template <typename CharT>
980constexpr bool IsSpace(CharT c) {
981 return c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == '\v';
982}
983
984template <typename CharT, typename Traits>
985class StringCharSequence {
986public:
987 explicit constexpr StringCharSequence(std::basic_string_view<CharT, Traits> sv)
988 : current_(sv.begin()),
989 end_(sv.end())
990 {}
991
992 // on sequence end, returns '\0'
993 constexpr CharT Get() { return current_ == end_ ? CharT{'\0'} : *current_++; }
994
995 constexpr void Unget() { --current_; }
996
997private:
998 typename std::basic_string_view<CharT, Traits>::iterator current_;
999 typename std::basic_string_view<CharT, Traits>::iterator end_;
1000};
1001
1002template <typename CharT, typename Traits>
1003class StreamCharSequence {
1004public:
1005 explicit StreamCharSequence(std::basic_istream<CharT, Traits>& in)
1006 : in_(&in)
1007 {}
1008
1009 // on sequence end, returns '\0'
1010 CharT Get() {
1011 constexpr CharT kEof = std::basic_istream<CharT, Traits>::traits_type::eof();
1012 if (!in_->good()) {
1013 return CharT{'\0'};
1014 }
1015 const CharT c = in_->peek();
1016 if (c == kEof) {
1017 return CharT{'\0'};
1018 }
1019 in_->ignore();
1020 return c;
1021 }
1022
1023 void Unget() { in_->unget(); }
1024
1025private:
1026 std::basic_istream<CharT, Traits>* in_;
1027};
1028
1029enum class ParseOptions {
1030 kNone = 0,
1031
1032 /// Allow space characters in the beginning or in the end
1033 /// " 42 "
1034 kAllowSpaces = 1 << 0,
1035
1036 /// Allow any trailing characters
1037 /// "42ABC"
1038 kAllowTrailingJunk = 1 << 1,
1039
1040 /// Allow leading or trailing dot
1041 /// "42.", ".42"
1042 kAllowBoundaryDot = 1 << 2,
1043
1044 /// Allow decimal digits beyond Prec, round according to RoundPolicy
1045 /// "0.123456" -> "0.1234" or "0.1235"
1046 kAllowRounding = 1 << 3,
1047
1048 /// Allow using exponential format (e or E)
1049 /// 1e2 -> 100 or 1E2 -> 100
1050 kAllowExponent = 1 << 4
1051};
1052
1053enum class ParseErrorCode : uint8_t {
1054 /// An unexpected character has been met
1055 kWrongChar,
1056
1057 /// No digits before or after dot
1058 kNoDigits,
1059
1060 /// The integral part does not fit in a Decimal
1061 kOverflow,
1062
1063 /// The string contains leading spaces, while disallowed by options
1064 kSpace,
1065
1066 /// The string contains trailing junk, while disallowed by options
1067 kTrailingJunk,
1068
1069 /// On inputs like "42." or ".42" if disallowed by options
1070 kBoundaryDot,
1071
1072 /// When there are more decimal digits than in any Decimal and rounding is
1073 /// disallowed by options
1074 kRounding,
1075
1076 /// On inputs like "1e2" or "1E2" if exponent is not allowed by options
1077 kExponentNotAllowed,
1078
1079 /// When there are no numbers after the exponent character (or after exponent sign if it exist)
1080 /// On inputs like "1e" or "1E+" or "1e-"
1081 kNoExponentDigits
1082};
1083
1084struct ParseUnpackedResult {
1085 int64_t before{0};
1086 int64_t after{0};
1087 uint8_t decimal_digits{0};
1088 bool is_negative{false};
1089 uint8_t exponent{0};
1090 bool is_negative_exponent{false};
1091 std::optional<ParseErrorCode> error;
1092 uint32_t error_position{-1U};
1093 int zeros_after_dec{0};
1094};
1095
1096enum class ParseState {
1097 /// Before reading any part of the Decimal
1098 kSign,
1099
1100 /// After reading a sign
1101 kBeforeFirstDig,
1102
1103 /// Only leading zeros (at least one) have been met
1104 kLeadingZeros,
1105
1106 /// At least one digit before dot has been met
1107 kBeforeDec,
1108
1109 /// Reading fractional digits
1110 kAfterDec,
1111
1112 /// Only zeros (at least one) have been met after dot
1113 kZerosAfterDec,
1114
1115 /// Reading and rounding extra fractional digits
1116 kIgnoringAfterDec,
1117
1118 /// Exponent sign
1119 kExpSign,
1120
1121 // First digit after sign
1122 kExpFirstDigit,
1123
1124 /// Exponent digits
1125 kExpDigits,
1126
1127 /// A character unrelated to the Decimal has been met
1128 kEnd
1129};
1130
1131constexpr inline void StateToExpSign(
1132 std::optional<ParseErrorCode>& error,
1133 ParseState& state,
1134 utils::Flags<ParseOptions>& options
1135) {
1136 if (!error && !(options & ParseOptions::kAllowExponent)) {
1137 error = ParseErrorCode::kExponentNotAllowed;
1138 }
1139 state = ParseState::kExpSign;
1140}
1141
1142/// Extract values from a CharSequence ready to be packed to Decimal
1143template <typename CharSequence>
1144[[nodiscard]] constexpr ParseUnpackedResult ParseUnpacked(CharSequence input, utils::Flags<ParseOptions> options) {
1145 constexpr char dec_point = '.';
1146
1147 int64_t before = 0;
1148 int64_t after = 0;
1149 bool is_negative = false;
1150
1151 ptrdiff_t position = -1;
1152 auto state = ParseState::kSign;
1153 std::optional<ParseErrorCode> error;
1154 int before_digit_count = 0;
1155 uint8_t after_digit_count = 0;
1156 bool is_negative_exp = false;
1157 uint8_t exponent = 0;
1158 int zeros_after_dec = 0;
1159
1160 while (state != ParseState::kEnd) {
1161 const auto c = input.Get();
1162 if (c == '\0') {
1163 break;
1164 }
1165 if (!error) {
1166 ++position;
1167 }
1168
1169 switch (state) {
1170 case ParseState::kSign:
1171 if (c == '-') {
1172 is_negative = true;
1173 state = ParseState::kBeforeFirstDig;
1174 } else if (c == '+') {
1175 state = ParseState::kBeforeFirstDig;
1176 } else if (c == '0') {
1177 state = ParseState::kLeadingZeros;
1178 before_digit_count = 1;
1179 } else if ((c >= '1') && (c <= '9')) {
1180 state = ParseState::kBeforeDec;
1181 before = static_cast<int>(c - '0');
1182 before_digit_count = 1;
1183 } else if (c == dec_point) {
1184 if (!(options & ParseOptions::kAllowBoundaryDot) && !error) {
1185 error = ParseErrorCode::kBoundaryDot; // keep reading digits
1186 }
1187 state = ParseState::kZerosAfterDec;
1188 } else if (IsSpace(c)) {
1189 if (!(options & ParseOptions::kAllowSpaces)) {
1190 state = ParseState::kEnd;
1191 error = ParseErrorCode::kSpace;
1192 }
1193 } else {
1194 state = ParseState::kEnd;
1195 error = ParseErrorCode::kWrongChar;
1196 }
1197 break;
1198 case ParseState::kBeforeFirstDig:
1199 if (c == '0') {
1200 state = ParseState::kLeadingZeros;
1201 before_digit_count = 1;
1202 } else if ((c >= '1') && (c <= '9')) {
1203 state = ParseState::kBeforeDec;
1204 before = static_cast<int>(c - '0');
1205 before_digit_count = 1;
1206 } else if (c == dec_point) {
1207 if (!(options & ParseOptions::kAllowBoundaryDot) && !error) {
1208 error = ParseErrorCode::kBoundaryDot; // keep reading digits
1209 }
1210 state = ParseState::kAfterDec;
1211 } else {
1212 state = ParseState::kEnd;
1213 error = ParseErrorCode::kWrongChar;
1214 }
1215 break;
1216 case ParseState::kLeadingZeros:
1217 if (c == '0') {
1218 // skip
1219 } else if ((c >= '1') && (c <= '9')) {
1220 state = ParseState::kBeforeDec;
1221 before = static_cast<int>(c - '0');
1222 } else if (c == dec_point) {
1223 state = ParseState::kZerosAfterDec;
1224 } else if (c == 'e' || c == 'E') {
1225 StateToExpSign(/*error*/ error, /*state*/ state, /*options*/ options);
1226 } else {
1227 state = ParseState::kEnd;
1228 }
1229 break;
1230 case ParseState::kBeforeDec:
1231 if ((c >= '0') && (c <= '9')) {
1232 if (before_digit_count < kMaxDecimalDigits) {
1233 before = 10 * before + static_cast<int>(c - '0');
1234 before_digit_count++;
1235 } else if (!error) {
1236 error = ParseErrorCode::kOverflow; // keep reading digits
1237 }
1238 } else if (c == dec_point) {
1239 state = ParseState::kZerosAfterDec;
1240 } else if (c == 'e' || c == 'E') {
1241 StateToExpSign(/*error*/ error, /*state*/ state, /*options*/ options);
1242 } else {
1243 state = ParseState::kEnd;
1244 }
1245 break;
1246
1247 case ParseState::kZerosAfterDec:
1248 if (c == '0') {
1249 zeros_after_dec++;
1250 after_digit_count++;
1251 break;
1252 }
1253 [[fallthrough]];
1254
1255 case ParseState::kAfterDec:
1256 state = ParseState::kAfterDec;
1257 if ((c >= '0') && (c <= '9')) {
1258 if (after_digit_count < kMaxDecimalDigits) {
1259 after = 10 * after + static_cast<int>(c - '0');
1260 after_digit_count++;
1261 } else {
1262 if (!(options & ParseOptions::kAllowRounding) && !error) {
1263 error = ParseErrorCode::kRounding; // keep reading digits
1264 }
1265 state = ParseState::kIgnoringAfterDec;
1266 if (c >= '5') {
1267 // round half up
1268 after++;
1269 }
1270 }
1271
1272 } else if (c == 'e' || c == 'E') {
1273 StateToExpSign(/*error*/ error, /*state*/ state, /*options*/ options);
1274 } else {
1275 if (!(options & ParseOptions::kAllowBoundaryDot) && after_digit_count == 0 && !error) {
1276 error = ParseErrorCode::kBoundaryDot;
1277 }
1278 state = ParseState::kEnd;
1279 }
1280 break;
1281
1282 case ParseState::kIgnoringAfterDec:
1283 if ((c >= '0') && (c <= '9')) {
1284 // skip
1285 } else if (c == 'e' || c == 'E') {
1286 StateToExpSign(/*error*/ error, /*state*/ state, /*options*/ options);
1287 } else {
1288 state = ParseState::kEnd;
1289 }
1290 break;
1291
1292 case ParseState::kExpSign:
1293 if (c == '+') {
1294 is_negative_exp = false;
1295 state = ParseState::kExpFirstDigit;
1296 } else if (c == '-') {
1297 is_negative_exp = true;
1298 state = ParseState::kExpFirstDigit;
1299 } else if (c >= '0' && c <= '9') {
1300 exponent = static_cast<int>(c - '0');
1301 state = ParseState::kExpDigits;
1302 } else {
1303 if (!error) {
1304 error = ParseErrorCode::kWrongChar;
1305 }
1306 state = ParseState::kEnd;
1307 }
1308 break;
1309
1310 case ParseState::kExpFirstDigit:
1311 if (c >= '0' && c <= '9') {
1312 exponent = static_cast<int>(c - '0');
1313 state = ParseState::kExpDigits;
1314 } else {
1315 state = ParseState::kEnd;
1316 if (!error) {
1317 error = ParseErrorCode::kWrongChar;
1318 }
1319 }
1320 break;
1321
1322 case ParseState::kExpDigits:
1323 if (c >= '0' && c <= '9') {
1324 if ((__builtin_mul_overflow(10, exponent, &exponent) ||
1325 __builtin_add_overflow(static_cast<int>(c - '0'), exponent, &exponent)))
1326 {
1327 if (is_negative_exp && !(options & ParseOptions::kAllowRounding) && !error) {
1328 error = ParseErrorCode::kRounding; // keep reading digits
1329 }
1330 if (!is_negative_exp && !error) {
1331 error = ParseErrorCode::kOverflow; // keep reading digits
1332 }
1333 }
1334 } else {
1335 state = ParseState::kEnd;
1336 }
1337 break;
1338
1339 case ParseState::kEnd:
1340 UASSERT(false);
1341 break;
1342 } // switch state
1343 } // while has more chars & not end
1344
1345 if (state == ParseState::kEnd) {
1346 input.Unget();
1347
1348 if (!error && !(options & ParseOptions::kAllowTrailingJunk)) {
1349 if (!(options & ParseOptions::kAllowSpaces)) {
1350 error = ParseErrorCode::kSpace;
1351 }
1352 --position;
1353
1354 while (true) {
1355 const auto c = input.Get();
1356 if (c == '\0') {
1357 break;
1358 }
1359 ++position;
1360 if (!IsSpace(c)) {
1361 error = ParseErrorCode::kTrailingJunk;
1362 input.Unget();
1363 break;
1364 }
1365 }
1366 }
1367 }
1368
1369 if (!error && before_digit_count == 0 && after_digit_count == 0) {
1370 error = ParseErrorCode::kNoDigits;
1371 }
1372
1373 if (!error && state == ParseState::kZerosAfterDec && !(options & ParseOptions::kAllowBoundaryDot) &&
1374 after_digit_count == 0)
1375 {
1376 error = ParseErrorCode::kBoundaryDot;
1377 }
1378
1379 if ((!error && state == ParseState::kExpSign) || (!error && state == ParseState::kExpFirstDigit)) {
1380 error = ParseErrorCode::kNoExponentDigits;
1381 }
1382
1383 if (zeros_after_dec >= kMaxDecimalDigits) {
1384 after_digit_count = 0;
1385 after = 0;
1386 zeros_after_dec = 0;
1387 }
1388
1389 return {
1390 before,
1391 after,
1392 after_digit_count,
1393 is_negative,
1394 exponent,
1395 is_negative_exp,
1396 error,
1397 static_cast<uint32_t>(position),
1398 zeros_after_dec,
1399
1400 };
1401}
1402
1403template <int Prec, typename RoundPolicy>
1404struct ParseResult {
1405 Decimal<Prec, RoundPolicy> decimal;
1406 std::optional<ParseErrorCode> error;
1407 uint32_t error_position{-1U};
1408};
1409
1410/// Parse Decimal from a CharSequence
1411template <int Prec, typename RoundPolicy, typename CharSequence>
1412[[nodiscard]] constexpr ParseResult<Prec, RoundPolicy> Parse(CharSequence input, utils::Flags<ParseOptions> options) {
1413 ParseUnpackedResult parsed = ParseUnpacked(input, options);
1414
1415 if (parsed.error) {
1416 return {{}, parsed.error, parsed.error_position};
1417 }
1418
1419 if (!(options & ParseOptions::kAllowRounding) && parsed.decimal_digits > Prec) {
1420 return {{}, ParseErrorCode::kRounding, 0};
1421 }
1422
1423 if (parsed.before >= kMaxInt64 / kPow10<Prec>) {
1424 return {{}, ParseErrorCode::kOverflow, 0};
1425 }
1426
1427 if (parsed.after == 0 && parsed.decimal_digits > 0) {
1428 parsed.after = 0;
1429 parsed.decimal_digits = 0;
1430 parsed.zeros_after_dec = 0;
1431 }
1432
1433 if (parsed.is_negative) {
1434 parsed.before = -parsed.before;
1435 parsed.after = -parsed.after;
1436 }
1437
1438 if (parsed.exponent != 0) {
1439 return {
1440 FromUnpackedWithExponent<Prec, RoundPolicy>(
1441 parsed.before,
1442 parsed.after,
1443 parsed.decimal_digits,
1444 parsed.exponent,
1445 parsed.is_negative_exponent,
1446 parsed.zeros_after_dec
1447 ),
1448 {},
1449 0,
1450 };
1451 }
1452
1453 return {FromUnpacked<Prec, RoundPolicy>(parsed.before, parsed.after, parsed.decimal_digits), {}, 0};
1454}
1455
1456std::string GetErrorMessage(std::string_view source, std::string_view path, size_t position, ParseErrorCode reason);
1457
1458/// removes the zeros on the right in after
1459/// saves the updated precision in after_precision
1460void TrimTrailingZeros(int64_t& after, int& after_precision);
1461
1462std::string ToString(int64_t before, int64_t after, int precision, const FormatOptions& format_options);
1463
1464} // namespace impl
1465
1466template <int Prec, typename RoundPolicy>
1467constexpr Decimal<Prec, RoundPolicy>::Decimal(std::string_view value) {
1468 const auto result = impl::Parse<Prec, RoundPolicy>(impl::StringCharSequence(value), impl::ParseOptions::kNone);
1469
1470 if (result.error) {
1471 throw ParseError(impl::GetErrorMessage(value, "<string>", result.error_position, *result.error));
1472 }
1473 *this = result.decimal;
1474}
1475
1476template <int Prec, typename RoundPolicy>
1477constexpr Decimal<Prec, RoundPolicy> Decimal<Prec, RoundPolicy>::FromStringPermissive(std::string_view input) {
1478 const auto result = impl::Parse<Prec, RoundPolicy>(
1479 impl::StringCharSequence(input),
1480 {impl::ParseOptions::kAllowSpaces,
1481 impl::ParseOptions::kAllowBoundaryDot,
1482 impl::ParseOptions::kAllowRounding,
1483 impl::ParseOptions::kAllowExponent}
1484 );
1485
1486 if (result.error) {
1487 throw ParseError(impl::GetErrorMessage(input, "<string>", result.error_position, *result.error));
1488 }
1489 return result.decimal;
1490}
1491
1492/// @brief Converts Decimal to a string
1493///
1494/// Usage example:
1495///
1496/// ToString(decimal64::Decimal<4>{"1.5"}) -> 1.5
1497///
1498/// @see ToStringTrailingZeros
1499/// @see ToStringFixed
1500template <int Prec, typename RoundPolicy>
1501std::string ToString(Decimal<Prec, RoundPolicy> dec) {
1502 return fmt::to_string(dec);
1503}
1504
1505/// @brief Converts Decimal to a string
1506///
1507/// Usage example:
1508///
1509/// ToString(decimal64::Decimal<4>{"-1234.1234"},
1510/// {"||", "**", "\1", "<>", {}, true})) -> "<>1**2**3**4||1234"
1511/// ToString(decimal64::Decimal<4>{"-1234.1234"},
1512/// {",", " ", "\3", "-", 6, true})) -> "-1 234,123400"
1513///
1514/// @see ToStringTrailingZeros
1515/// @see ToStringFixed
1516template <int Prec, typename RoundPolicy>
1517std::string ToString(const Decimal<Prec, RoundPolicy>& dec, const FormatOptions& format_options) {
1518 auto precision = format_options.precision.value_or(Prec);
1519 if (!format_options.is_fixed) {
1520 precision = std::min(precision, Prec);
1521 }
1522 auto [before, after] = impl::AsUnpacked(dec, precision);
1523 return impl::ToString(before, after, precision, format_options);
1524}
1525
1526/// @brief Converts Decimal to a string, writing exactly `Prec` decimal digits
1527///
1528/// Usage example:
1529///
1530/// ToStringTrailingZeros(decimal64::Decimal<4>{"1.5"}) -> 1.5000
1531///
1532/// @see ToString
1533/// @see ToStringFixed
1534template <int Prec, typename RoundPolicy>
1535std::string ToStringTrailingZeros(Decimal<Prec, RoundPolicy> dec) {
1536 return fmt::format(FMT_COMPILE("{:f}"), dec);
1537}
1538
1539/// @brief Converts Decimal to a string with exactly `NewPrec` decimal digits
1540///
1541/// Usage example:
1542///
1543/// ToStringFixed<3>(decimal64::Decimal<4>{"1.5"}) -> 1.500
1544///
1545/// @see ToString
1546/// @see ToStringTrailingZeros
1547template <int NewPrec, int Prec, typename RoundPolicy>
1548std::string ToStringFixed(Decimal<Prec, RoundPolicy> dec) {
1549 return ToStringTrailingZeros(decimal64::decimal_cast<Decimal<NewPrec, RoundPolicy>>(dec));
1550}
1551
1552/// @brief Parses a `Decimal` from the `istream`
1553///
1554/// Acts like the `Decimal(str)` constructor, except that it allows junk that
1555/// immediately follows the number and supports exponential format.
1556/// Sets the stream's fail bit on failure.
1557///
1558/// Usage example:
1559///
1560/// if (os >> dec) {
1561/// // success
1562/// } else {
1563/// // failure
1564/// }
1565///
1566/// @see Decimal::Decimal(std::string_view)
1567template <typename CharT, typename Traits, int Prec, typename RoundPolicy>
1568std::basic_istream<CharT, Traits>& operator>>(std::basic_istream<CharT, Traits>& is, Decimal<Prec, RoundPolicy>& d) {
1569 if (is.flags() & std::ios_base::skipws) {
1570 std::ws(is);
1571 }
1572 const auto result = impl::Parse<Prec, RoundPolicy>(
1573 impl::StreamCharSequence(is),
1574 {impl::ParseOptions::kAllowTrailingJunk, impl::ParseOptions::kAllowExponent}
1575 );
1576
1577 if (result.error) {
1578 is.setstate(std::ios_base::failbit);
1579 } else {
1580 d = result.decimal;
1581 }
1582 return is;
1583}
1584
1585/// @brief Writes the `Decimal` to the `ostream`
1586/// @see ToString
1587template <typename CharT, typename Traits, int Prec, typename RoundPolicy>
1588std::basic_ostream<CharT, Traits>& operator<<(
1589 std::basic_ostream<CharT, Traits>& os,
1590 const Decimal<Prec, RoundPolicy>& d
1591) {
1592 os << ToString(d);
1593 return os;
1594}
1595
1596/// @brief Writes the `Decimal` to the logger
1597/// @see ToString
1598template <int Prec, typename RoundPolicy>
1599logging::LogHelper& operator<<(logging::LogHelper& lh, const Decimal<Prec, RoundPolicy>& d) {
1600 lh << ToString(d);
1601 return lh;
1602}
1603
1604/// @brief Parses the `Decimal` from the string
1605/// @see Decimal::Decimal(std::string_view)
1606template <int Prec, typename RoundPolicy, typename Value>
1607std::enable_if_t<formats::common::kIsFormatValue<Value>, Decimal<Prec, RoundPolicy>>
1608Parse(const Value& value, formats::parse::To<Decimal<Prec, RoundPolicy>>) {
1609 const std::string input = value.template As<std::string>();
1610
1611 const auto result =
1612 impl::Parse<Prec, RoundPolicy>(impl::StringCharSequence(std::string_view{input}), impl::ParseOptions::kNone);
1613
1614 if (result.error) {
1615 throw ParseError(impl::GetErrorMessage(input, value.GetPath(), result.error_position, *result.error));
1616 }
1617 return result.decimal;
1618}
1619
1620/// @brief Serializes the `Decimal` to string
1621/// @see ToString
1622template <int Prec, typename RoundPolicy, typename TargetType>
1623TargetType Serialize(const Decimal<Prec, RoundPolicy>& object, formats::serialize::To<TargetType>) {
1624 return typename TargetType::Builder(ToString(object)).ExtractValue();
1625}
1626
1627/// @brief Writes the `Decimal` to stream
1628/// @see ToString
1629template <int Prec, typename RoundPolicy, typename StringBuilder>
1630void WriteToStream(const Decimal<Prec, RoundPolicy>& object, StringBuilder& sw) {
1631 WriteToStream(ToString(object), sw);
1632}
1633
1634/// gtest formatter for decimal64::Decimal
1635template <int Prec, typename RoundPolicy>
1636void PrintTo(const Decimal<Prec, RoundPolicy>& v, std::ostream* os) {
1637 *os << v;
1638}
1639
1640} // namespace decimal64
1641
1642USERVER_NAMESPACE_END
1643
1644/// std::hash support
1645template <int Prec, typename RoundPolicy>
1646struct std::hash<USERVER_NAMESPACE::decimal64::Decimal<Prec, RoundPolicy>> {
1647 using Decimal = USERVER_NAMESPACE::decimal64::Decimal<Prec, RoundPolicy>;
1648
1649 std::size_t operator()(const Decimal& v) const noexcept { return std::hash<int64_t>{}(v.AsUnbiased()); }
1650};
1651
1652/// @brief fmt support
1653///
1654/// Spec format:
1655/// - {} trims any trailing zeros;
1656/// - {:f} writes exactly `Prec` decimal digits, including trailing zeros
1657/// if needed.
1658/// - {:.N} writes exactly `N` decimal digits, including trailing zeros
1659/// if needed.
1660template <int Prec, typename RoundPolicy, typename Char>
1661class fmt::formatter<USERVER_NAMESPACE::decimal64::Decimal<Prec, RoundPolicy>, Char> {
1662public:
1663 constexpr auto parse(fmt::basic_format_parse_context<Char>& ctx) {
1664 const auto* it = ctx.begin();
1665 const auto* end = ctx.end();
1666
1667 if (it != end && *it == '.') {
1668 remove_trailing_zeros_ = false;
1669 custom_precision_ = 0;
1670 ++it;
1671 while (it != end && *it >= '0' && *it <= '9') {
1672 *custom_precision_ = *custom_precision_ * 10 + (*it - '0');
1673 ++it;
1674 }
1675 }
1676
1677 if (!custom_precision_ && it != end && *it == 'f') {
1678 remove_trailing_zeros_ = false;
1679 ++it;
1680 }
1681
1682 if (it != end && *it != '}') {
1683 throw format_error("invalid format");
1684 }
1685
1686 return it;
1687 }
1688
1689 template <typename FormatContext>
1690 auto format(const USERVER_NAMESPACE::decimal64::Decimal<Prec, RoundPolicy>& dec, FormatContext& ctx) const {
1691 int after_digits = custom_precision_.value_or(Prec);
1692 auto [before, after] = USERVER_NAMESPACE::decimal64::impl::AsUnpacked(dec, after_digits);
1693 if (remove_trailing_zeros_) {
1694 USERVER_NAMESPACE::decimal64::impl::TrimTrailingZeros(after, after_digits);
1695 }
1696
1697 if (after_digits > 0) {
1698 if (dec.Sign() == -1) {
1699 return fmt::format_to(ctx.out(), FMT_COMPILE("-{}.{:0{}}"), -before, -after, after_digits);
1700 } else {
1701 return fmt::format_to(ctx.out(), FMT_COMPILE("{}.{:0{}}"), before, after, after_digits);
1702 }
1703 } else {
1704 return fmt::format_to(ctx.out(), FMT_COMPILE("{}"), before);
1705 }
1706 }
1707
1708private:
1709 bool remove_trailing_zeros_ = true;
1710 std::optional<int> custom_precision_;
1711};