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