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