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