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/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};