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/// is 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 ///
465 /// @throw decimal64::ParseError on invalid input
466 /// @see FromStringPermissive
467 explicit constexpr Decimal(std::string_view value);
468
469 /// @brief Lossy conversion from a floating-point number
470 ///
471 /// To somewhat resist the accumulated error, the number is always rounded
472 /// to the nearest Decimal, regardless of `RoundPolicy`.
473 ///
474 /// @warning Prefer storing and sending `Decimal` as string, and performing
475 /// the computations between `Decimal`s.
476 template <typename T>
477 static constexpr Decimal FromFloatInexact(T value) {
478 static_assert(std::is_floating_point_v<T>);
479 // Check that overflow does not occur when converting to int64_t
480 // (constexpr detects UB).
481 static_assert(DefRoundPolicy::Round(impl::kMinRepresentableLongDouble) < 0);
482 static_assert(DefRoundPolicy::Round(impl::kMaxRepresentableLongDouble) > 0);
483
484 const auto unbiased_float = static_cast<long double>(value) * kDecimalFactor;
485 if (unbiased_float < impl::kMinRepresentableLongDouble || unbiased_float > impl::kMaxRepresentableLongDouble) {
486 throw OutOfBoundsError();
487 }
488 return FromUnbiased(DefRoundPolicy::Round(unbiased_float));
489 }
490
491 /// @brief Convert from a string, allowing rounding, spaces and boundary dot
492 ///
493 /// In addition to the `Decimal(str)` constructor, allows:
494 /// - rounding (as per `RoundPolicy`), e.g. "12.3456789" with `Prec == 2`
495 /// - space characters, e.g. " \t42 \n"
496 /// - leading and trailing dot, e.g. "5." and ".5"
497 ///
498 /// @throw decimal64::ParseError on invalid input
499 /// @see Decimal(std::string_view)
500 static constexpr Decimal FromStringPermissive(std::string_view input);
501
502 /// @brief Reconstruct from the internal representation, as acquired
503 /// with `AsUnbiased`
504 ///
505 /// The Decimal value will be equal to `value/kDecimalFactor`.
506 ///
507 /// @see AsUnbiased
508 static constexpr Decimal FromUnbiased(int64_t value) noexcept {
509 Decimal result;
510 result.value_ = value;
511 return result;
512 }
513
514 /// @brief Convert from `original_unbiased / 10^original_precision`, rounding
515 /// according to `RoundPolicy` if necessary
516 ///
517 /// Usage examples:
518 ///
519 /// Decimal<4>::FromBiased(123, 6) -> 0.0001
520 /// Decimal<4>::FromBiased(123, 2) -> 1.23
521 /// Decimal<4>::FromBiased(123, -1) -> 1230
522 ///
523 /// @param original_unbiased The original mantissa
524 /// @param original_precision The original precision (negated exponent)
525 static constexpr Decimal FromBiased(int64_t original_unbiased, int original_precision) {
526 const int exponent_for_pack = Prec - original_precision;
527
528 if (exponent_for_pack >= 0) {
529 return FromUnbiased(original_unbiased) * Pow10(exponent_for_pack);
530 } else {
531 return FromUnbiased(impl::Div<RoundPolicy>(original_unbiased, Pow10(-exponent_for_pack)));
532 }
533 }
534
535 /// @brief Assignment from another `Decimal`
536 ///
537 /// The assignment is allowed as long as `RoundPolicy` is the same. Rounding
538 /// will be performed according to `RoundPolicy` if necessary.
539 template <int Prec2>
540 Decimal& operator=(Decimal<Prec2, RoundPolicy> rhs) {
541 *this = FromDecimal(rhs);
542 return *this;
543 }
544
545 /// @brief Assignment from an integer
546 template <typename Int, impl::EnableIfInt<Int> = 0>
547 constexpr Decimal& operator=(Int rhs) {
548 *this = Decimal{rhs};
549 return *this;
550 }
551
552#ifdef USERVER_IMPL_HAS_THREE_WAY_COMPARISON
553 constexpr auto operator<=>(const Decimal& rhs) const = default;
554#else
555 constexpr bool operator==(Decimal rhs) const { return value_ == rhs.value_; }
556
557 constexpr bool operator!=(Decimal rhs) const { return value_ != rhs.value_; }
558
559 constexpr bool operator<(Decimal rhs) const { return value_ < rhs.value_; }
560
561 constexpr bool operator<=(Decimal rhs) const { return value_ <= rhs.value_; }
562
563 constexpr bool operator>(Decimal rhs) const { return value_ > rhs.value_; }
564
565 constexpr bool operator>=(Decimal rhs) const { return value_ >= rhs.value_; }
566#endif
567
568 constexpr Decimal operator+() const { return *this; }
569
570 constexpr Decimal operator-() const {
571 if (value_ == impl::kMinInt64) throw OutOfBoundsError();
572 return FromUnbiased(-value_);
573 }
574
575 template <int Prec2>
576 constexpr auto operator+(Decimal<Prec2, RoundPolicy> rhs) const {
577 if constexpr (Prec2 > Prec) {
578 return Decimal<Prec2, RoundPolicy>::FromDecimal(*this) + rhs;
579 } else if constexpr (Prec2 < Prec) {
580 return *this + FromDecimal(rhs);
581 } else {
582 int64_t result{};
583 if (__builtin_add_overflow(AsUnbiased(), rhs.AsUnbiased(), &result)) {
584 throw OutOfBoundsError();
585 }
586 return FromUnbiased(result);
587 }
588 }
589
590 template <typename Int, impl::EnableIfInt<Int> = 0>
591 constexpr Decimal operator+(Int rhs) const {
592 return *this + Decimal{rhs};
593 }
594
595 template <typename Int, impl::EnableIfInt<Int> = 0>
596 friend constexpr Decimal operator+(Int lhs, Decimal rhs) {
597 return Decimal{lhs} + rhs;
598 }
599
600 template <int Prec2>
601 constexpr Decimal& operator+=(Decimal<Prec2, RoundPolicy> rhs) {
602 static_assert(Prec2 <= Prec, "Implicit cast to Decimal of lower precision in assignment");
603 *this = *this + rhs;
604 return *this;
605 }
606
607 template <typename Int, impl::EnableIfInt<Int> = 0>
608 constexpr Decimal& operator+=(Int rhs) {
609 *this = *this + rhs;
610 return *this;
611 }
612
613 template <int Prec2>
614 constexpr auto operator-(Decimal<Prec2, RoundPolicy> rhs) const {
615 if constexpr (Prec2 > Prec) {
616 return Decimal<Prec2, RoundPolicy>::FromDecimal(*this) - rhs;
617 } else if constexpr (Prec2 < Prec) {
618 return *this - FromDecimal(rhs);
619 } else {
620 int64_t result{};
621 if (__builtin_sub_overflow(AsUnbiased(), rhs.AsUnbiased(), &result)) {
622 throw OutOfBoundsError();
623 }
624 return FromUnbiased(result);
625 }
626 }
627
628 template <typename Int, impl::EnableIfInt<Int> = 0>
629 constexpr Decimal operator-(Int rhs) const {
630 return *this - Decimal{rhs};
631 }
632
633 template <typename Int, impl::EnableIfInt<Int> = 0>
634 friend constexpr Decimal operator-(Int lhs, Decimal rhs) {
635 return Decimal{lhs} - rhs;
636 }
637
638 template <int Prec2>
639 constexpr Decimal& operator-=(Decimal<Prec2, RoundPolicy> rhs) {
640 static_assert(Prec2 <= Prec, "Implicit cast to Decimal of lower precision in assignment");
641 *this = *this - rhs;
642 return *this;
643 }
644
645 template <typename Int, impl::EnableIfInt<Int> = 0>
646 constexpr Decimal& operator-=(Int rhs) {
647 *this = *this - rhs;
648 return *this;
649 }
650
651 template <typename Int, typename = impl::EnableIfInt<Int>>
652 constexpr Decimal operator*(Int rhs) const {
653 int64_t result{};
654 if (rhs > impl::kMaxInt64 || __builtin_mul_overflow(value_, static_cast<int64_t>(rhs), &result)) {
655 throw OutOfBoundsError();
656 }
657 return FromUnbiased(result);
658 }
659
660 template <typename Int, impl::EnableIfInt<Int> = 0>
661 friend constexpr Decimal operator*(Int lhs, Decimal rhs) {
662 return rhs * lhs;
663 }
664
665 template <typename Int, impl::EnableIfInt<Int> = 0>
666 constexpr Decimal& operator*=(Int rhs) {
667 *this = *this * rhs;
668 return *this;
669 }
670
671 template <int Prec2>
672 constexpr Decimal operator*(Decimal<Prec2, RoundPolicy> rhs) const {
673 return FromUnbiased(impl::MulDiv<RoundPolicy>(AsUnbiased(), rhs.AsUnbiased(), kPow10<Prec2>));
674 }
675
676 template <int Prec2>
677 constexpr Decimal& operator*=(Decimal<Prec2, RoundPolicy> rhs) {
678 *this = *this * rhs;
679 return *this;
680 }
681
682 template <typename Int, typename = impl::EnableIfInt<Int>>
683 constexpr Decimal operator/(Int rhs) const {
684 return FromUnbiased(impl::Div<RoundPolicy>(AsUnbiased(), rhs));
685 }
686
687 template <typename Int, typename = impl::EnableIfInt<Int>>
688 friend constexpr Decimal operator/(Int lhs, Decimal rhs) {
689 return Decimal{lhs} / rhs;
690 }
691
692 template <typename Int, typename = impl::EnableIfInt<Int>>
693 constexpr Decimal& operator/=(Int rhs) {
694 *this = *this / rhs;
695 return *this;
696 }
697
698 template <int Prec2>
699 constexpr Decimal operator/(Decimal<Prec2, RoundPolicy> rhs) const {
700 return FromUnbiased(impl::MulDiv<RoundPolicy>(AsUnbiased(), kPow10<Prec2>, rhs.AsUnbiased()));
701 }
702
703 template <int Prec2>
704 constexpr Decimal& operator/=(Decimal<Prec2, RoundPolicy> rhs) {
705 *this = *this / rhs;
706 return *this;
707 }
708
709 /// Returns one of {-1, 0, +1}, depending on the sign of the `Decimal`
710 constexpr int Sign() const { return impl::Sign(value_); }
711
712 /// Returns the absolute value of the `Decimal`
713 constexpr Decimal Abs() const { return FromUnbiased(impl::Abs(value_)); }
714
715 /// Rounds `this` to the nearest multiple of `base` according to `RoundPolicy`
716 constexpr Decimal RoundToMultipleOf(Decimal base) const {
717 if (base < Decimal{0}) throw OutOfBoundsError();
718 return *this / base.AsUnbiased() * base.AsUnbiased();
719 }
720
721 /// Returns the value rounded to integer using the active rounding policy
722 constexpr int64_t ToInteger() const { return impl::Div<RoundPolicy>(value_, kDecimalFactor); }
723
724 /// @brief Returns the value converted to `double`
725 ///
726 /// @warning Operations with `double`, and even the returned value,
727 /// is inexact. Prefer storing and sending `Decimal` as string, and performing
728 /// the computations between `Decimal`s.
729 ///
730 /// @see FromFloatInexact
731 constexpr double ToDoubleInexact() const {
732 // maximum number that can be represented without modification
733 constexpr std::int64_t kLossLimit = (static_cast<std::int64_t>(1) << std::numeric_limits<double>::digits);
734
735 if (value_ > -kLossLimit && value_ < kLossLimit) {
736 return static_cast<double>(value_) / kDecimalFactor;
737 }
738
739 constexpr int kCoef =
740 1 << (std::max(
741 std::numeric_limits<std::int64_t>::digits - std::numeric_limits<double>::digits - 3 * Prec, 0
742 ));
743
744 // divide the value into two parts (each no more than kLossLimit)
745 const std::int64_t p1 = value_ / (kDecimalFactor * kCoef) * kCoef;
746 const std::int64_t p2 = value_ % (kDecimalFactor * kCoef);
747
748 // combine without loss of accuracy
749 return p1 + static_cast<double>(p2) / kDecimalFactor;
750 }
751
752 /// @brief Retrieve the internal representation
753 ///
754 /// The internal representation of `Decimal` is `real_value * kDecimalFactor`.
755 /// Use for storing the value of Decimal efficiently when `Prec` is guaranteed
756 /// not to change.
757 ///
758 /// @see FromUnbiased
759 constexpr int64_t AsUnbiased() const { return value_; }
760
761private:
762 template <int Prec2, typename RoundPolicy2>
763 static constexpr Decimal FromDecimal(Decimal<Prec2, RoundPolicy2> source) {
764 if constexpr (Prec > Prec2) {
765 int64_t result{};
766 if (__builtin_mul_overflow(source.AsUnbiased(), kPow10<Prec - Prec2>, &result)) {
767 throw OutOfBoundsError();
768 }
769 return FromUnbiased(result);
770 } else if constexpr (Prec < Prec2) {
771 return FromUnbiased(impl::Div<RoundPolicy>(source.AsUnbiased(), kPow10<Prec2 - Prec>));
772 } else {
773 return FromUnbiased(source.AsUnbiased());
774 }
775 }
776
777 template <int Prec2, typename RoundPolicy2>
778 friend class Decimal;
779
780 template <typename T, int OldPrec, typename OldRound>
781 friend constexpr T decimal_cast(Decimal<OldPrec, OldRound> arg);
782
783 int64_t value_{0};
784};
785
786namespace impl {
787
788template <typename T>
789struct IsDecimal : std::false_type {};
790
791template <int Prec, typename RoundPolicy>
792struct IsDecimal<Decimal<Prec, RoundPolicy>> : std::true_type {};
793
794} // namespace impl
795
796/// `true` if the type is an instantiation of `Decimal`
797template <typename T>
798inline constexpr bool kIsDecimal = impl::IsDecimal<T>::value;
799
800/// @brief Cast one `Decimal` to another `Decimal` type
801///
802/// When casting to a `Decimal` with a lower `Prec`, rounding is performed
803/// according to the new `RoundPolicy`.
804///
805/// Usage example:
806/// @code{.cpp}
807/// using Money = decimal64::Decimal<4>;
808/// using Discount = decimal64::Decimal<4, FloorRoundPolicy>;
809///
810/// Money cost = ...;
811/// auto discount = decimal64::decimal_cast<Discount>(cost) * Discount{"0.05"};
812/// @endcode
813template <typename T, int OldPrec, typename OldRound>
814constexpr T decimal_cast(Decimal<OldPrec, OldRound> arg) {
815 static_assert(kIsDecimal<T>);
816 return T::FromDecimal(arg);
817}
818
819namespace impl {
820
821// FromUnpacked<Decimal<4>>(12, 34) -> 12.0034
822// FromUnpacked<Decimal<4>>(-12, -34) -> -12.0034
823// FromUnpacked<Decimal<4>>(0, -34) -> -0.0034
824template <int Prec, typename RoundPolicy>
825constexpr Decimal<Prec, RoundPolicy> FromUnpacked(int64_t before, int64_t after) {
826 using Dec = Decimal<Prec, RoundPolicy>;
827 UASSERT(((before >= 0) && (after >= 0)) || ((before <= 0) && (after <= 0)));
828
829 int64_t result{};
830 if (__builtin_mul_overflow(before, Dec::kDecimalFactor, &result) ||
831 __builtin_add_overflow(result, after, &result)) {
832 throw OutOfBoundsError();
833 }
834
835 return Dec::FromUnbiased(result);
836}
837
838// FromUnpacked<Decimal<4>>(12, 34, 3) -> 12.034
839template <int Prec, typename RoundPolicy>
840constexpr Decimal<Prec, RoundPolicy> FromUnpacked(int64_t before, int64_t after, int original_precision) {
841 UASSERT(((before >= 0) && (after >= 0)) || ((before <= 0) && (after <= 0)));
842 UASSERT(after > -Pow10(original_precision) && after < Pow10(original_precision));
843
844 if (original_precision <= Prec) {
845 // direct mode
846 const int missing_digits = Prec - original_precision;
847 const int64_t factor = Pow10(missing_digits);
848 return FromUnpacked<Prec, RoundPolicy>(before, after * factor);
849 } else {
850 // rounding mode
851 const int extra_digits = original_precision - Prec;
852 const int64_t factor = Pow10(extra_digits);
853 // note: if rounded up, rounded_after may represent a "fractional part"
854 // greater than 1.0, which is ok
855 const int64_t rounded_after = Div<RoundPolicy>(after, factor);
856 return FromUnpacked<Prec, RoundPolicy>(before, rounded_after);
857 }
858}
859
860template <int Prec, typename RoundPolicy>
861constexpr Decimal<Prec, RoundPolicy> FromUnpackedWithExponent(
862 int64_t before,
863 int64_t after,
864 int original_precision,
865 int exponent,
866 bool is_negative_exponent,
867 int leading_zeros
868) {
869 UASSERT(((before >= 0) && (after >= 0)) || ((before <= 0) && (after <= 0)));
870 UASSERT(after > -Pow10(original_precision) && after < Pow10(original_precision));
871
872 if (before == 0 && after == 0) {
873 return Decimal<Prec, RoundPolicy>::FromUnbiased(0);
874 }
875
876 const int effective_exponent = is_negative_exponent ? -exponent : exponent;
877
878 if (effective_exponent + Prec < -kMaxDecimalDigits) {
879 return Decimal<Prec, RoundPolicy>::FromUnbiased(0);
880 }
881
882 int total_scale = effective_exponent + Prec - original_precision;
883
884 if (before == 0 && after != 0) {
885 if (!is_negative_exponent) {
886 if (exponent >= leading_zeros) {
887 exponent -= leading_zeros;
888 original_precision -= leading_zeros;
889 } else {
890 original_precision -= exponent;
891 exponent = 0;
892 }
893 }
894 }
895
896 int64_t value = before;
897
898 if (__builtin_mul_overflow(value, Pow10(original_precision), &value) ||
899 __builtin_add_overflow(value, after, &value)) {
900 throw OutOfBoundsError();
901 }
902
903 if (total_scale > 0) {
904 if (total_scale > kMaxDecimalDigits) {
905 throw OutOfBoundsError();
906 }
907 if (__builtin_mul_overflow(value, Pow10(total_scale), &value)) {
908 throw OutOfBoundsError();
909 }
910 } else if (total_scale < 0) {
911 const int64_t divisor = Pow10(-total_scale);
912 value = Div<RoundPolicy>(value, divisor);
913 }
914
915 return Decimal<Prec, RoundPolicy>::FromUnbiased(value);
916}
917
918struct UnpackedDecimal {
919 int64_t before;
920 int64_t after;
921};
922
923// AsUnpacked(Decimal<4>{"3.14"}) -> {3, 1400}
924// AsUnpacked(Decimal<4>{"-3.14"}) -> {-3, -1400}
925// AsUnpacked(Decimal<4>{"-0.14"}) -> {0, -1400}
926template <int Prec, typename RoundPolicy>
927constexpr UnpackedDecimal AsUnpacked(Decimal<Prec, RoundPolicy> dec) {
928 using Dec = Decimal<Prec, RoundPolicy>;
929 return {dec.AsUnbiased() / Dec::kDecimalFactor, dec.AsUnbiased() % Dec::kDecimalFactor};
930}
931
932// AsUnpacked(Decimal<4>{"3.14"}, 5) -> {3, 14000}
933// AsUnpacked(Decimal<4>{"-3.14"}, 6) -> {-3, -140000}
934// AsUnpacked(Decimal<4>{"-0.14"}, 1) -> {0, -1}
935template <int Prec, typename RoundPolicy>
936UnpackedDecimal AsUnpacked(Decimal<Prec, RoundPolicy> dec, int new_prec) {
937 if (new_prec == Prec) {
938 return AsUnpacked(dec);
939 }
940 int64_t result{};
941 if (new_prec > Prec) {
942 if (__builtin_mul_overflow(dec.AsUnbiased(), Pow10(new_prec - Prec), &result)) {
943 throw OutOfBoundsError();
944 }
945 } else {
946 result = impl::Div<RoundPolicy>(dec.AsUnbiased(), Pow10(Prec - new_prec));
947 }
948 const auto dec_factor = Pow10(new_prec);
949 return {result / dec_factor, result % dec_factor};
950}
951
952template <typename CharT>
953constexpr bool IsSpace(CharT c) {
954 return c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == '\v';
955}
956
957template <typename CharT, typename Traits>
958class StringCharSequence {
959public:
960 explicit constexpr StringCharSequence(std::basic_string_view<CharT, Traits> sv)
961 : current_(sv.begin()), end_(sv.end()) {}
962
963 // on sequence end, returns '\0'
964 constexpr CharT Get() { return current_ == end_ ? CharT{'\0'} : *current_++; }
965
966 constexpr void Unget() { --current_; }
967
968private:
969 typename std::basic_string_view<CharT, Traits>::iterator current_;
970 typename std::basic_string_view<CharT, Traits>::iterator end_;
971};
972
973template <typename CharT, typename Traits>
974class StreamCharSequence {
975public:
976 explicit StreamCharSequence(std::basic_istream<CharT, Traits>& in) : in_(&in) {}
977
978 // on sequence end, returns '\0'
979 CharT Get() {
980 constexpr CharT kEof = std::basic_istream<CharT, Traits>::traits_type::eof();
981 if (!in_->good()) {
982 return CharT{'\0'};
983 }
984 const CharT c = in_->peek();
985 if (c == kEof) {
986 return CharT{'\0'};
987 }
988 in_->ignore();
989 return c;
990 }
991
992 void Unget() { in_->unget(); }
993
994private:
995 std::basic_istream<CharT, Traits>* in_;
996};
997
998enum class ParseOptions {
999 kNone = 0,
1000
1001 /// Allow space characters in the beginning or in the end
1002 /// " 42 "
1003 kAllowSpaces = 1 << 0,
1004
1005 /// Allow any trailing characters
1006 /// "42ABC"
1007 kAllowTrailingJunk = 1 << 1,
1008
1009 /// Allow leading or trailing dot
1010 /// "42.", ".42"
1011 kAllowBoundaryDot = 1 << 2,
1012
1013 /// Allow decimal digits beyond Prec, round according to RoundPolicy
1014 /// "0.123456" -> "0.1234" or "0.1235"
1015 kAllowRounding = 1 << 3,
1016
1017 /// Allow using exponential format (e or E)
1018 /// 1e2 -> 100 or 1E2 -> 100
1019 kAllowExponent = 1 << 4
1020};
1021
1022enum class ParseErrorCode : uint8_t {
1023 /// An unexpected character has been met
1024 kWrongChar,
1025
1026 /// No digits before or after dot
1027 kNoDigits,
1028
1029 /// The integral part does not fit in a Decimal
1030 kOverflow,
1031
1032 /// The string contains leading spaces, while disallowed by options
1033 kSpace,
1034
1035 /// The string contains trailing junk, while disallowed by options
1036 kTrailingJunk,
1037
1038 /// On inputs like "42." or ".42" if disallowed by options
1039 kBoundaryDot,
1040
1041 /// When there are more decimal digits than in any Decimal and rounding is
1042 /// disallowed by options
1043 kRounding,
1044
1045 /// On inputs like "1e2" or "1E2" if exponent is not allowed by options
1046 kExponentNotAllowed,
1047
1048 /// When there are no numbers after the exponent character (or after exponent sign if it exist)
1049 /// On inputs like "1e" or "1E+" or "1e-"
1050 kNoExponentDigits
1051};
1052
1053struct ParseUnpackedResult {
1054 int64_t before{0};
1055 int64_t after{0};
1056 uint8_t decimal_digits{0};
1057 bool is_negative{false};
1058 uint8_t exponent{0};
1059 bool is_negative_exponent{false};
1060 std::optional<ParseErrorCode> error;
1061 uint32_t error_position{-1U};
1062 int zeros_after_dec{0};
1063};
1064
1065enum class ParseState {
1066 /// Before reading any part of the Decimal
1067 kSign,
1068
1069 /// After reading a sign
1070 kBeforeFirstDig,
1071
1072 /// Only leading zeros (at least one) have been met
1073 kLeadingZeros,
1074
1075 /// At least one digit before dot has been met
1076 kBeforeDec,
1077
1078 /// Reading fractional digits
1079 kAfterDec,
1080
1081 /// Only zeros (at least one) have been met after dot
1082 kZerosAfterDec,
1083
1084 /// Reading and rounding extra fractional digits
1085 kIgnoringAfterDec,
1086
1087 /// Exponent sign
1088 kExpSign,
1089
1090 // First digit after sign
1091 kExpFirstDigit,
1092
1093 /// Exponent digits
1094 kExpDigits,
1095
1096 /// A character unrelated to the Decimal has been met
1097 kEnd
1098};
1099
1100constexpr inline void
1101StateToExpSign(std::optional<ParseErrorCode>& error, ParseState& state, utils::Flags<ParseOptions>& options) {
1102 if (!error && !(options & ParseOptions::kAllowExponent)) {
1103 error = ParseErrorCode::kExponentNotAllowed;
1104 }
1105 state = ParseState::kExpSign;
1106}
1107
1108/// Extract values from a CharSequence ready to be packed to Decimal
1109template <typename CharSequence>
1110[[nodiscard]] constexpr ParseUnpackedResult ParseUnpacked(CharSequence input, utils::Flags<ParseOptions> options) {
1111 constexpr char dec_point = '.';
1112
1113 int64_t before = 0;
1114 int64_t after = 0;
1115 bool is_negative = false;
1116
1117 ptrdiff_t position = -1;
1118 auto state = ParseState::kSign;
1119 std::optional<ParseErrorCode> error;
1120 int before_digit_count = 0;
1121 uint8_t after_digit_count = 0;
1122 bool is_negative_exp = false;
1123 uint8_t exponent = 0;
1124 int zeros_after_dec = 0;
1125
1126 while (state != ParseState::kEnd) {
1127 const auto c = input.Get();
1128 if (c == '\0') break;
1129 if (!error) ++position;
1130
1131 switch (state) {
1132 case ParseState::kSign:
1133 if (c == '-') {
1134 is_negative = true;
1135 state = ParseState::kBeforeFirstDig;
1136 } else if (c == '+') {
1137 state = ParseState::kBeforeFirstDig;
1138 } else if (c == '0') {
1139 state = ParseState::kLeadingZeros;
1140 before_digit_count = 1;
1141 } else if ((c >= '1') && (c <= '9')) {
1142 state = ParseState::kBeforeDec;
1143 before = static_cast<int>(c - '0');
1144 before_digit_count = 1;
1145 } else if (c == dec_point) {
1146 if (!(options & ParseOptions::kAllowBoundaryDot) && !error) {
1147 error = ParseErrorCode::kBoundaryDot; // keep reading digits
1148 }
1149 state = ParseState::kZerosAfterDec;
1150 } else if (IsSpace(c)) {
1151 if (!(options & ParseOptions::kAllowSpaces)) {
1152 state = ParseState::kEnd;
1153 error = ParseErrorCode::kSpace;
1154 }
1155 } else {
1156 state = ParseState::kEnd;
1157 error = ParseErrorCode::kWrongChar;
1158 }
1159 break;
1160 case ParseState::kBeforeFirstDig:
1161 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::kAfterDec;
1173 } else {
1174 state = ParseState::kEnd;
1175 error = ParseErrorCode::kWrongChar;
1176 }
1177 break;
1178 case ParseState::kLeadingZeros:
1179 if (c == '0') {
1180 // skip
1181 } else if ((c >= '1') && (c <= '9')) {
1182 state = ParseState::kBeforeDec;
1183 before = static_cast<int>(c - '0');
1184 } else if (c == dec_point) {
1185 state = ParseState::kZerosAfterDec;
1186 } else if (c == 'e' || c == 'E') {
1187 StateToExpSign(/*error*/ error, /*state*/ state, /*options*/ options);
1188 } else {
1189 state = ParseState::kEnd;
1190 }
1191 break;
1192 case ParseState::kBeforeDec:
1193 if ((c >= '0') && (c <= '9')) {
1194 if (before_digit_count < kMaxDecimalDigits) {
1195 before = 10 * before + static_cast<int>(c - '0');
1196 before_digit_count++;
1197 } else if (!error) {
1198 error = ParseErrorCode::kOverflow; // keep reading digits
1199 }
1200 } else if (c == dec_point) {
1201 state = ParseState::kZerosAfterDec;
1202 } else if (c == 'e' || c == 'E') {
1203 StateToExpSign(/*error*/ error, /*state*/ state, /*options*/ options);
1204 } else {
1205 state = ParseState::kEnd;
1206 }
1207 break;
1208
1209 case ParseState::kZerosAfterDec:
1210 if (c == '0') {
1211 zeros_after_dec++;
1212 after_digit_count++;
1213 break;
1214 }
1215 [[fallthrough]];
1216
1217 case ParseState::kAfterDec:
1218 state = ParseState::kAfterDec;
1219 if ((c >= '0') && (c <= '9')) {
1220 if (after_digit_count < kMaxDecimalDigits) {
1221 after = 10 * after + static_cast<int>(c - '0');
1222 after_digit_count++;
1223 } else {
1224 if (!(options & ParseOptions::kAllowRounding) && !error) {
1225 error = ParseErrorCode::kRounding; // keep reading digits
1226 }
1227 state = ParseState::kIgnoringAfterDec;
1228 if (c >= '5') {
1229 // round half up
1230 after++;
1231 }
1232 }
1233
1234 } else if (c == 'e' || c == 'E') {
1235 StateToExpSign(/*error*/ error, /*state*/ state, /*options*/ options);
1236 } else {
1237 if (!(options & ParseOptions::kAllowBoundaryDot) && after_digit_count == 0 && !error) {
1238 error = ParseErrorCode::kBoundaryDot;
1239 }
1240 state = ParseState::kEnd;
1241 }
1242 break;
1243
1244 case ParseState::kIgnoringAfterDec:
1245 if ((c >= '0') && (c <= '9')) {
1246 // skip
1247 } else if (c == 'e' || c == 'E') {
1248 StateToExpSign(/*error*/ error, /*state*/ state, /*options*/ options);
1249 } else {
1250 state = ParseState::kEnd;
1251 }
1252 break;
1253
1254 case ParseState::kExpSign:
1255 if (c == '+') {
1256 is_negative_exp = false;
1257 state = ParseState::kExpFirstDigit;
1258 } else if (c == '-') {
1259 is_negative_exp = true;
1260 state = ParseState::kExpFirstDigit;
1261 } else if (c >= '0' && c <= '9') {
1262 exponent = static_cast<int>(c - '0');
1263 state = ParseState::kExpDigits;
1264 } else {
1265 if (!error) {
1266 error = ParseErrorCode::kWrongChar;
1267 }
1268 state = ParseState::kEnd;
1269 }
1270 break;
1271
1272 case ParseState::kExpFirstDigit:
1273 if (c >= '0' && c <= '9') {
1274 exponent = static_cast<int>(c - '0');
1275 state = ParseState::kExpDigits;
1276 } else {
1277 state = ParseState::kEnd;
1278 if (!error) error = ParseErrorCode::kWrongChar;
1279 }
1280 break;
1281
1282 case ParseState::kExpDigits:
1283 if (c >= '0' && c <= '9') {
1284 if ((__builtin_mul_overflow(10, exponent, &exponent) ||
1285 __builtin_add_overflow(static_cast<int>(c - '0'), exponent, &exponent))) {
1286 if (is_negative_exp && !(options & ParseOptions::kAllowRounding) && !error) {
1287 error = ParseErrorCode::kRounding; // keep reading digits
1288 }
1289 if (!is_negative_exp && !error) {
1290 error = ParseErrorCode::kOverflow; // keep reading digits
1291 }
1292 }
1293 } else {
1294 state = ParseState::kEnd;
1295 }
1296 break;
1297
1298 case ParseState::kEnd:
1299 UASSERT(false);
1300 break;
1301 } // switch state
1302 } // while has more chars & not end
1303
1304 if (state == ParseState::kEnd) {
1305 input.Unget();
1306
1307 if (!error && !(options & ParseOptions::kAllowTrailingJunk)) {
1308 if (!(options & ParseOptions::kAllowSpaces)) {
1309 error = ParseErrorCode::kSpace;
1310 }
1311 --position;
1312
1313 while (true) {
1314 const auto c = input.Get();
1315 if (c == '\0') break;
1316 ++position;
1317 if (!IsSpace(c)) {
1318 error = ParseErrorCode::kTrailingJunk;
1319 input.Unget();
1320 break;
1321 }
1322 }
1323 }
1324 }
1325
1326 if (!error && before_digit_count == 0 && after_digit_count == 0) {
1327 error = ParseErrorCode::kNoDigits;
1328 }
1329
1330 if (!error && state == ParseState::kZerosAfterDec && !(options & ParseOptions::kAllowBoundaryDot) &&
1331 after_digit_count == 0) {
1332 error = ParseErrorCode::kBoundaryDot;
1333 }
1334
1335 if ((!error && state == ParseState::kExpSign) || (!error && state == ParseState::kExpFirstDigit)) {
1336 error = ParseErrorCode::kNoExponentDigits;
1337 }
1338
1339 if (zeros_after_dec >= kMaxDecimalDigits) {
1340 after_digit_count = 0;
1341 after = 0;
1342 zeros_after_dec = 0;
1343 }
1344
1345 return {
1346 before,
1347 after,
1348 after_digit_count,
1349 is_negative,
1350 exponent,
1351 is_negative_exp,
1352 error,
1353 static_cast<uint32_t>(position),
1354 zeros_after_dec,
1355
1356 };
1357}
1358
1359template <int Prec, typename RoundPolicy>
1360struct ParseResult {
1361 Decimal<Prec, RoundPolicy> decimal;
1362 std::optional<ParseErrorCode> error;
1363 uint32_t error_position{-1U};
1364};
1365
1366/// Parse Decimal from a CharSequence
1367template <int Prec, typename RoundPolicy, typename CharSequence>
1368[[nodiscard]] constexpr ParseResult<Prec, RoundPolicy> Parse(CharSequence input, utils::Flags<ParseOptions> options) {
1369 ParseUnpackedResult parsed = ParseUnpacked(input, options);
1370
1371 if (parsed.error) {
1372 return {{}, parsed.error, parsed.error_position};
1373 }
1374
1375 if (!(options & ParseOptions::kAllowRounding) && parsed.decimal_digits > Prec) {
1376 return {{}, ParseErrorCode::kRounding, 0};
1377 }
1378
1379 if (parsed.before >= kMaxInt64 / kPow10<Prec>) {
1380 return {{}, ParseErrorCode::kOverflow, 0};
1381 }
1382
1383 if (parsed.after == 0 && parsed.decimal_digits > 0) {
1384 parsed.after = 0;
1385 parsed.decimal_digits = 0;
1386 parsed.zeros_after_dec = 0;
1387 }
1388
1389 if (parsed.is_negative) {
1390 parsed.before = -parsed.before;
1391 parsed.after = -parsed.after;
1392 }
1393
1394 if (parsed.exponent != 0) {
1395 return {
1396 FromUnpackedWithExponent<Prec, RoundPolicy>(
1397 parsed.before,
1398 parsed.after,
1399 parsed.decimal_digits,
1400 parsed.exponent,
1401 parsed.is_negative_exponent,
1402 parsed.zeros_after_dec
1403 ),
1404 {},
1405 0,
1406 };
1407 }
1408
1409 return {FromUnpacked<Prec, RoundPolicy>(parsed.before, parsed.after, parsed.decimal_digits), {}, 0};
1410}
1411
1412std::string GetErrorMessage(std::string_view source, std::string_view path, size_t position, ParseErrorCode reason);
1413
1414/// removes the zeros on the right in after
1415/// saves the updated precision in after_precision
1416void TrimTrailingZeros(int64_t& after, int& after_precision);
1417
1418std::string ToString(int64_t before, int64_t after, int precision, const FormatOptions& format_options);
1419
1420} // namespace impl
1421
1422template <int Prec, typename RoundPolicy>
1423constexpr Decimal<Prec, RoundPolicy>::Decimal(std::string_view value) {
1424 const auto result = impl::Parse<Prec, RoundPolicy>(impl::StringCharSequence(value), impl::ParseOptions::kNone);
1425
1426 if (result.error) {
1427 throw ParseError(impl::GetErrorMessage(value, "<string>", result.error_position, *result.error));
1428 }
1429 *this = result.decimal;
1430}
1431
1432template <int Prec, typename RoundPolicy>
1433constexpr Decimal<Prec, RoundPolicy> Decimal<Prec, RoundPolicy>::FromStringPermissive(std::string_view input) {
1434 const auto result = impl::Parse<Prec, RoundPolicy>(
1435 impl::StringCharSequence(input),
1436 {impl::ParseOptions::kAllowSpaces,
1437 impl::ParseOptions::kAllowBoundaryDot,
1438 impl::ParseOptions::kAllowRounding,
1439 impl::ParseOptions::kAllowExponent}
1440 );
1441
1442 if (result.error) {
1443 throw ParseError(impl::GetErrorMessage(input, "<string>", result.error_position, *result.error));
1444 }
1445 return result.decimal;
1446}
1447
1448/// @brief Converts Decimal to a string
1449///
1450/// Usage example:
1451///
1452/// ToString(decimal64::Decimal<4>{"1.5"}) -> 1.5
1453///
1454/// @see ToStringTrailingZeros
1455/// @see ToStringFixed
1456template <int Prec, typename RoundPolicy>
1457std::string ToString(Decimal<Prec, RoundPolicy> dec) {
1458 return fmt::to_string(dec);
1459}
1460
1461/// @brief Converts Decimal to a string
1462///
1463/// Usage example:
1464///
1465/// ToString(decimal64::Decimal<4>{"-1234.1234"},
1466/// {"||", "**", "\1", "<>", {}, true})) -> "<>1**2**3**4||1234"
1467/// ToString(decimal64::Decimal<4>{"-1234.1234"},
1468/// {",", " ", "\3", "-", 6, true})) -> "-1 234,123400"
1469///
1470/// @see ToStringTrailingZeros
1471/// @see ToStringFixed
1472template <int Prec, typename RoundPolicy>
1473std::string ToString(const Decimal<Prec, RoundPolicy>& dec, const FormatOptions& format_options) {
1474 auto precision = format_options.precision.value_or(Prec);
1475 if (!format_options.is_fixed) {
1476 precision = std::min(precision, Prec);
1477 }
1478 auto [before, after] = impl::AsUnpacked(dec, precision);
1479 return impl::ToString(before, after, precision, format_options);
1480}
1481
1482/// @brief Converts Decimal to a string, writing exactly `Prec` decimal digits
1483///
1484/// Usage example:
1485///
1486/// ToStringTrailingZeros(decimal64::Decimal<4>{"1.5"}) -> 1.5000
1487///
1488/// @see ToString
1489/// @see ToStringFixed
1490template <int Prec, typename RoundPolicy>
1491std::string ToStringTrailingZeros(Decimal<Prec, RoundPolicy> dec) {
1492 return fmt::format(FMT_COMPILE("{:f}"), dec);
1493}
1494
1495/// @brief Converts Decimal to a string with exactly `NewPrec` decimal digits
1496///
1497/// Usage example:
1498///
1499/// ToStringFixed<3>(decimal64::Decimal<4>{"1.5"}) -> 1.500
1500///
1501/// @see ToString
1502/// @see ToStringTrailingZeros
1503template <int NewPrec, int Prec, typename RoundPolicy>
1504std::string ToStringFixed(Decimal<Prec, RoundPolicy> dec) {
1505 return ToStringTrailingZeros(decimal64::decimal_cast<Decimal<NewPrec, RoundPolicy>>(dec));
1506}
1507
1508/// @brief Parses a `Decimal` from the `istream`
1509///
1510/// Acts like the `Decimal(str)` constructor, except that it allows junk that
1511/// immediately follows the number. Sets the stream's fail bit on failure.
1512///
1513/// Usage example:
1514///
1515/// if (os >> dec) {
1516/// // success
1517/// } else {
1518/// // failure
1519/// }
1520///
1521/// @see Decimal::Decimal(std::string_view)
1522template <typename CharT, typename Traits, int Prec, typename RoundPolicy>
1523std::basic_istream<CharT, Traits>& operator>>(std::basic_istream<CharT, Traits>& is, Decimal<Prec, RoundPolicy>& d) {
1524 if (is.flags() & std::ios_base::skipws) {
1525 std::ws(is);
1526 }
1527 const auto result = impl::Parse<Prec, RoundPolicy>(
1528 impl::StreamCharSequence(is), {impl::ParseOptions::kAllowTrailingJunk, impl::ParseOptions::kAllowExponent}
1529 );
1530
1531 if (result.error) {
1532 is.setstate(std::ios_base::failbit);
1533 } else {
1534 d = result.decimal;
1535 }
1536 return is;
1537}
1538
1539/// @brief Writes the `Decimal` to the `ostream`
1540/// @see ToString
1541template <typename CharT, typename Traits, int Prec, typename RoundPolicy>
1542std::basic_ostream<CharT, Traits>&
1543operator<<(std::basic_ostream<CharT, Traits>& os, const Decimal<Prec, RoundPolicy>& d) {
1544 os << ToString(d);
1545 return os;
1546}
1547
1548/// @brief Writes the `Decimal` to the logger
1549/// @see ToString
1550template <int Prec, typename RoundPolicy>
1551logging::LogHelper& operator<<(logging::LogHelper& lh, const Decimal<Prec, RoundPolicy>& d) {
1552 lh << ToString(d);
1553 return lh;
1554}
1555
1556/// @brief Parses the `Decimal` from the string
1557/// @see Decimal::Decimal(std::string_view)
1558template <int Prec, typename RoundPolicy, typename Value>
1559std::enable_if_t<formats::common::kIsFormatValue<Value>, Decimal<Prec, RoundPolicy>>
1560Parse(const Value& value, formats::parse::To<Decimal<Prec, RoundPolicy>>) {
1561 const std::string input = value.template As<std::string>();
1562
1563 const auto result =
1564 impl::Parse<Prec, RoundPolicy>(impl::StringCharSequence(std::string_view{input}), impl::ParseOptions::kNone);
1565
1566 if (result.error) {
1567 throw ParseError(impl::GetErrorMessage(input, value.GetPath(), result.error_position, *result.error));
1568 }
1569 return result.decimal;
1570}
1571
1572/// @brief Serializes the `Decimal` to string
1573/// @see ToString
1574template <int Prec, typename RoundPolicy, typename TargetType>
1575TargetType Serialize(const Decimal<Prec, RoundPolicy>& object, formats::serialize::To<TargetType>) {
1576 return typename TargetType::Builder(ToString(object)).ExtractValue();
1577}
1578
1579/// @brief Writes the `Decimal` to stream
1580/// @see ToString
1581template <int Prec, typename RoundPolicy, typename StringBuilder>
1582void WriteToStream(const Decimal<Prec, RoundPolicy>& object, StringBuilder& sw) {
1583 WriteToStream(ToString(object), sw);
1584}
1585
1586/// gtest formatter for decimal64::Decimal
1587template <int Prec, typename RoundPolicy>
1588void PrintTo(const Decimal<Prec, RoundPolicy>& v, std::ostream* os) {
1589 *os << v;
1590}
1591
1592} // namespace decimal64
1593
1594USERVER_NAMESPACE_END
1595
1596/// std::hash support
1597template <int Prec, typename RoundPolicy>
1598struct std::hash<USERVER_NAMESPACE::decimal64::Decimal<Prec, RoundPolicy>> {
1599 using Decimal = USERVER_NAMESPACE::decimal64::Decimal<Prec, RoundPolicy>;
1600
1601 std::size_t operator()(const Decimal& v) const noexcept { return std::hash<int64_t>{}(v.AsUnbiased()); }
1602};
1603
1604/// @brief fmt support
1605///
1606/// Spec format:
1607/// - {} trims any trailing zeros;
1608/// - {:f} writes exactly `Prec` decimal digits, including trailing zeros
1609/// if needed.
1610/// - {:.N} writes exactly `N` decimal digits, including trailing zeros
1611/// if needed.
1612template <int Prec, typename RoundPolicy, typename Char>
1613class fmt::formatter<USERVER_NAMESPACE::decimal64::Decimal<Prec, RoundPolicy>, Char> {
1614public:
1615 constexpr auto parse(fmt::basic_format_parse_context<Char>& ctx) {
1616 const auto* it = ctx.begin();
1617 const auto* end = ctx.end();
1618
1619 if (it != end && *it == '.') {
1620 remove_trailing_zeros_ = false;
1621 custom_precision_ = 0;
1622 ++it;
1623 while (it != end && *it >= '0' && *it <= '9') {
1624 *custom_precision_ = *custom_precision_ * 10 + (*it - '0');
1625 ++it;
1626 }
1627 }
1628
1629 if (!custom_precision_ && it != end && *it == 'f') {
1630 remove_trailing_zeros_ = false;
1631 ++it;
1632 }
1633
1634 if (it != end && *it != '}') {
1635 throw format_error("invalid format");
1636 }
1637
1638 return it;
1639 }
1640
1641 template <typename FormatContext>
1642 auto format(const USERVER_NAMESPACE::decimal64::Decimal<Prec, RoundPolicy>& dec, FormatContext& ctx) const {
1643 int after_digits = custom_precision_.value_or(Prec);
1644 auto [before, after] = USERVER_NAMESPACE::decimal64::impl::AsUnpacked(dec, after_digits);
1645 if (remove_trailing_zeros_) {
1646 USERVER_NAMESPACE::decimal64::impl::TrimTrailingZeros(after, after_digits);
1647 }
1648
1649 if (after_digits > 0) {
1650 if (dec.Sign() == -1) {
1651 return fmt::format_to(ctx.out(), FMT_COMPILE("-{}.{:0{}}"), -before, -after, after_digits);
1652 } else {
1653 return fmt::format_to(ctx.out(), FMT_COMPILE("{}.{:0{}}"), before, after, after_digits);
1654 }
1655 } else {
1656 return fmt::format_to(ctx.out(), FMT_COMPILE("{}"), before);
1657 }
1658 }
1659
1660private:
1661 bool remove_trailing_zeros_ = true;
1662 std::optional<int> custom_precision_;
1663};