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