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