userver: userver/decimal64/decimal64.hpp Source File
⚠️ This is the documentation for an old userver version. Click here to switch to the latest version.
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages Concepts
decimal64.hpp
Go to the documentation of this file.
1#pragma once
2
3/// @file userver/decimal64/decimal64.hpp
4/// @brief Decimal data type for fixed-point arithmetic
5/// @ingroup userver_universal
6
7// Original source taken from https://github.com/vpiotr/decimal_for_cpp
8// Original license:
9//
10// ==================================================================
11// Name: decimal.h
12// Purpose: Decimal data type support, for COBOL-like fixed-point
13// operations on currency values.
14// Author: Piotr Likus
15// Created: 03/01/2011
16// Modified: 23/09/2018
17// Version: 1.16
18// Licence: BSD
19// ==================================================================
20
21#include <array>
22#include <cassert>
23#include <cstdint>
24#include <ios>
25#include <iosfwd>
26#include <istream>
27#include <limits>
28#include <numeric>
29#include <optional>
30#include <stdexcept>
31#include <string>
32#include <string_view>
33#include <type_traits>
34
35#include <fmt/compile.h>
36#include <fmt/format.h>
37
38#include <userver/decimal64/format_options.hpp>
39#include <userver/formats/common/meta.hpp>
40#include <userver/utils/assert.hpp>
41#include <userver/utils/flags.hpp>
42#include <userver/utils/meta_light.hpp>
43
44USERVER_NAMESPACE_BEGIN
45
46namespace logging {
47
48class LogHelper;
49
50} // namespace logging
51
52/// Fixed-point decimal data type and related functions
53namespace decimal64 {
54
55/// The base class for Decimal-related exceptions
56class DecimalError : public std::runtime_error {
57 public:
58 using std::runtime_error::runtime_error;
59};
60
61/// Thrown on all errors related to parsing `Decimal` from string
62class ParseError : public DecimalError {
63 public:
64 using DecimalError::DecimalError;
65};
66
67/// Thrown on overflow in `Decimal` arithmetic
69 public:
70 OutOfBoundsError();
71};
72
73/// Thrown on division by zero in `Decimal` arithmetic
75 public:
76 DivisionByZeroError();
77};
78
79namespace impl {
80
81inline constexpr auto kMaxInt64 = std::numeric_limits<int64_t>::max();
82inline constexpr auto kMinInt64 = std::numeric_limits<int64_t>::min();
83
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};