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