455 using RoundPolicy = TRoundPolicy;
464 template <
meta::kIsInteger Int>
483 explicit constexpr Decimal(std::string_view value);
492 template <
typename T>
494 static_assert(std::is_floating_point_v<T>);
497 static_assert(DefRoundPolicy::Round(impl::kMinRepresentableLongDouble) < 0);
498 static_assert(DefRoundPolicy::Round(impl::kMaxRepresentableLongDouble) > 0);
500 const auto unbiased_float =
static_cast<
long double>(value) *
kDecimalFactor;
501 if (unbiased_float < impl::kMinRepresentableLongDouble || unbiased_float > impl::kMaxRepresentableLongDouble) {
527 result.value_ = value;
543 const int exponent_for_pack = Prec - original_precision;
545 if (exponent_for_pack >= 0) {
558 *
this = FromDecimal(rhs);
563 template <
meta::kIsInteger Int>
569 constexpr auto operator<=>(
const Decimal& rhs)
const =
default;
571 constexpr Decimal operator+()
const {
return *
this; }
573 constexpr Decimal operator-()
const {
574 if (value_ == impl::kMinInt64) {
581 constexpr auto operator+(
Decimal<Prec2, RoundPolicy> rhs)
const {
582 if constexpr (Prec2 > Prec) {
583 return Decimal<Prec2, RoundPolicy>::FromDecimal(*
this) + rhs;
584 }
else if constexpr (Prec2 < Prec) {
585 return *
this + FromDecimal(rhs);
588 if (__builtin_add_overflow(
AsUnbiased(), rhs.AsUnbiased(), &result)) {
595 template <meta::kIsInteger Int>
596 constexpr Decimal operator+(Int rhs)
const {
600 template <meta::kIsInteger Int>
607 static_assert(Prec2 <= Prec,
"Implicit cast to Decimal of lower precision in assignment");
612 template <meta::kIsInteger Int>
613 constexpr Decimal& operator+=(Int rhs) {
619 constexpr auto operator-(
Decimal<Prec2, RoundPolicy> rhs)
const {
620 if constexpr (Prec2 > Prec) {
621 return Decimal<Prec2, RoundPolicy>::FromDecimal(*
this) - rhs;
622 }
else if constexpr (Prec2 < Prec) {
623 return *
this - FromDecimal(rhs);
626 if (__builtin_sub_overflow(
AsUnbiased(), rhs.AsUnbiased(), &result)) {
633 template <meta::kIsInteger Int>
634 constexpr Decimal operator-(Int rhs)
const {
638 template <meta::kIsInteger Int>
645 static_assert(Prec2 <= Prec,
"Implicit cast to Decimal of lower precision in assignment");
650 template <meta::kIsInteger Int>
651 constexpr Decimal& operator-=(Int rhs) {
656 template <meta::kIsInteger Int>
657 constexpr Decimal operator*(Int rhs)
const {
659 if (rhs > impl::kMaxInt64 || __builtin_mul_overflow(value_,
static_cast<int64_t>(rhs), &result)) {
665 template <meta::kIsInteger Int>
670 template <meta::kIsInteger Int>
671 constexpr Decimal& operator*=(Int rhs) {
677 constexpr Decimal operator*(
Decimal<Prec2, RoundPolicy> rhs)
const {
687 template <meta::kIsInteger Int>
688 constexpr Decimal operator/(Int rhs)
const {
692 template <meta::kIsInteger Int>
697 template <meta::kIsInteger Int>
698 constexpr Decimal& operator/=(Int rhs) {
704 constexpr Decimal operator/(
Decimal<Prec2, RoundPolicy> rhs)
const {
715 constexpr int Sign()
const {
return impl::Sign(value_); }
740 constexpr std::int64_t kLossLimit = (
static_cast<std::int64_t>(1) << std::numeric_limits<
double>::digits);
742 if (value_ > -kLossLimit && value_ < kLossLimit) {
746 constexpr int kCoef =
748 << (std::max(std::numeric_limits<std::int64_t>::digits - std::numeric_limits<
double>::digits - 3 * Prec, 0)
752 const std::int64_t p1 = value_ / (
kDecimalFactor * kCoef) * kCoef;
769 template <
int Prec2,
typename RoundPolicy2>
770 static constexpr Decimal FromDecimal(
Decimal<Prec2, RoundPolicy2> source) {
771 if constexpr (Prec > Prec2) {
773 if (__builtin_mul_overflow(source.AsUnbiased(), kPow10<Prec - Prec2>, &result)) {
777 }
else if constexpr (Prec < Prec2) {
778 return FromUnbiased(impl::Div<RoundPolicy>(source.AsUnbiased(), kPow10<Prec2 - Prec>)
);
784 template <
int Prec2,
typename RoundPolicy2>
787 template <
typename T,
int OldPrec,
typename OldRound>
796struct IsDecimal : std::false_type {};
798template <
int Prec,
typename RoundPolicy>
799struct IsDecimal<
Decimal<Prec, RoundPolicy>> : std::true_type {};
805concept IsDecimal = impl::IsDecimal<T>::value;
809concept kIsDecimal = IsDecimal<T>;
824template <
typename T,
int OldPrec,
typename OldRound>
826 static_assert(kIsDecimal<T>);
827 return T::FromDecimal(arg);
835template <
int Prec,
typename RoundPolicy>
836constexpr Decimal<Prec, RoundPolicy> FromUnpacked(int64_t before, int64_t after) {
837 using Dec =
Decimal<Prec, RoundPolicy>;
838 UASSERT(((before >= 0) && (after >= 0)) || ((before <= 0) && (after <= 0)));
841 if (__builtin_mul_overflow(before, Dec::kDecimalFactor, &result) || __builtin_add_overflow(result, after, &result))
846 return Dec::FromUnbiased(result);
850template <
int Prec,
typename RoundPolicy>
851constexpr Decimal<Prec, RoundPolicy> FromUnpacked(int64_t before, int64_t after,
int original_precision) {
852 UASSERT(((before >= 0) && (after >= 0)) || ((before <= 0) && (after <= 0)));
855 if (original_precision <= Prec) {
857 const int missing_digits = Prec - original_precision;
858 const int64_t factor =
Pow10(missing_digits
);
859 return FromUnpacked<Prec, RoundPolicy>(before, after * factor);
862 const int extra_digits = original_precision - Prec;
863 const int64_t factor =
Pow10(extra_digits
);
866 const int64_t rounded_after = Div<RoundPolicy>(after, factor);
867 return FromUnpacked<Prec, RoundPolicy>(before, rounded_after);
871template <
int Prec,
typename RoundPolicy>
872constexpr Decimal<Prec, RoundPolicy> FromUnpackedWithExponent(
875 int original_precision,
877 bool is_negative_exponent,
880 UASSERT(((before >= 0) && (after >= 0)) || ((before <= 0) && (after <= 0)));
883 if (before == 0 && after == 0) {
884 return Decimal<Prec, RoundPolicy>::FromUnbiased(0);
887 const int effective_exponent = is_negative_exponent ? -exponent : exponent;
889 if (effective_exponent + Prec < -kMaxDecimalDigits) {
890 return Decimal<Prec, RoundPolicy>::FromUnbiased(0);
893 int total_scale = effective_exponent + Prec - original_precision;
895 if (before == 0 && after != 0) {
896 if (!is_negative_exponent) {
897 if (exponent >= leading_zeros) {
898 exponent -= leading_zeros;
899 original_precision -= leading_zeros;
901 original_precision -= exponent;
907 int64_t value = before;
909 if (__builtin_mul_overflow(value,
Pow10(original_precision
), &value) ||
910 __builtin_add_overflow(value, after, &value))
915 if (total_scale > 0) {
916 if (total_scale > kMaxDecimalDigits) {
919 if (__builtin_mul_overflow(value,
Pow10(total_scale
), &value)) {
922 }
else if (total_scale < 0) {
923 const int64_t divisor =
Pow10(-total_scale
);
924 value = Div<RoundPolicy>(value, divisor);
927 return Decimal<Prec, RoundPolicy>::FromUnbiased(value);
930struct UnpackedDecimal {
938template <
int Prec,
typename RoundPolicy>
939constexpr UnpackedDecimal AsUnpacked(
Decimal<Prec, RoundPolicy> dec) {
940 using Dec =
Decimal<Prec, RoundPolicy>;
941 return {dec.AsUnbiased() / Dec::kDecimalFactor, dec.AsUnbiased() % Dec::kDecimalFactor};
947template <
int Prec,
typename RoundPolicy>
948UnpackedDecimal AsUnpacked(
Decimal<Prec, RoundPolicy> dec,
int new_prec) {
949 if (new_prec == Prec) {
950 return AsUnpacked(dec);
953 if (new_prec > Prec) {
954 if (__builtin_mul_overflow(dec.AsUnbiased(),
Pow10(new_prec - Prec
), &result)) {
958 result = impl::Div<RoundPolicy>(dec.AsUnbiased(),
Pow10(Prec - new_prec
));
960 const auto dec_factor =
Pow10(new_prec
);
961 return {result / dec_factor, result % dec_factor};
964template <
typename CharT>
965constexpr bool IsSpace(CharT c) {
966 return c ==
' ' || c ==
'\t' || c ==
'\r' || c ==
'\n' || c ==
'\v';
969template <
typename CharT,
typename Traits>
970class StringCharSequence {
972 explicit constexpr StringCharSequence(std::basic_string_view<CharT, Traits> sv)
973 : current_(sv.begin()),
978 constexpr CharT Get() {
return current_ == end_ ? CharT{
'\0'} : *current_++; }
980 constexpr void Unget() { --current_; }
983 typename std::basic_string_view<CharT, Traits>::iterator current_;
984 typename std::basic_string_view<CharT, Traits>::iterator end_;
987template <
typename CharT,
typename Traits>
988class StreamCharSequence {
990 explicit StreamCharSequence(std::basic_istream<CharT, Traits>& in)
996 constexpr CharT kEof = std::basic_istream<CharT, Traits>::traits_type::eof();
1000 const CharT c = in_->peek();
1008 void Unget() { in_->unget(); }
1011 std::basic_istream<CharT, Traits>* in_;
1014enum class ParseOptions {
1019 kAllowSpaces = 1 << 0,
1023 kAllowTrailingJunk = 1 << 1,
1027 kAllowBoundaryDot = 1 << 2,
1031 kAllowRounding = 1 << 3,
1035 kAllowExponent = 1 << 4
1038enum class ParseErrorCode : uint8_t {
1062 kExponentNotAllowed,
1069struct ParseUnpackedResult {
1072 uint8_t decimal_digits{0};
1073 bool is_negative{
false};
1074 uint8_t exponent{0};
1075 bool is_negative_exponent{
false};
1076 std::optional<ParseErrorCode> error;
1077 uint32_t error_position{-1U};
1078 int zeros_after_dec{0};
1081enum class ParseState {
1116constexpr inline void StateToExpSign(
1117 std::optional<ParseErrorCode>& error,
1119 utils::Flags<ParseOptions>& options
1121 if (!error && !(options & ParseOptions::kAllowExponent)) {
1122 error = ParseErrorCode::kExponentNotAllowed;
1124 state = ParseState::kExpSign;
1128template <
typename CharSequence>
1129[[nodiscard]]
constexpr ParseUnpackedResult ParseUnpacked(CharSequence input,
utils::Flags<ParseOptions> options) {
1130 constexpr char dec_point =
'.';
1134 bool is_negative =
false;
1136 ptrdiff_t position = -1;
1137 auto state = ParseState::kSign;
1138 std::optional<ParseErrorCode> error;
1139 int before_digit_count = 0;
1140 uint8_t after_digit_count = 0;
1141 bool is_negative_exp =
false;
1142 uint8_t exponent = 0;
1143 int zeros_after_dec = 0;
1145 while (state != ParseState::kEnd) {
1146 const auto c = input.Get();
1155 case ParseState::kSign:
1158 state = ParseState::kBeforeFirstDig;
1159 }
else if (c ==
'+') {
1160 state = ParseState::kBeforeFirstDig;
1161 }
else if (c ==
'0') {
1162 state = ParseState::kLeadingZeros;
1163 before_digit_count = 1;
1164 }
else if ((c >=
'1') && (c <=
'9')) {
1165 state = ParseState::kBeforeDec;
1166 before =
static_cast<
int>(c -
'0');
1167 before_digit_count = 1;
1168 }
else if (c == dec_point) {
1169 if (!(options & ParseOptions::kAllowBoundaryDot) && !error) {
1170 error = ParseErrorCode::kBoundaryDot;
1172 state = ParseState::kZerosAfterDec;
1173 }
else if (IsSpace(c)) {
1174 if (!(options & ParseOptions::kAllowSpaces)) {
1175 state = ParseState::kEnd;
1176 error = ParseErrorCode::kSpace;
1179 state = ParseState::kEnd;
1180 error = ParseErrorCode::kWrongChar;
1183 case ParseState::kBeforeFirstDig:
1185 state = ParseState::kLeadingZeros;
1186 before_digit_count = 1;
1187 }
else if ((c >=
'1') && (c <=
'9')) {
1188 state = ParseState::kBeforeDec;
1189 before =
static_cast<
int>(c -
'0');
1190 before_digit_count = 1;
1191 }
else if (c == dec_point) {
1192 if (!(options & ParseOptions::kAllowBoundaryDot) && !error) {
1193 error = ParseErrorCode::kBoundaryDot;
1195 state = ParseState::kAfterDec;
1197 state = ParseState::kEnd;
1198 error = ParseErrorCode::kWrongChar;
1201 case ParseState::kLeadingZeros:
1204 }
else if ((c >=
'1') && (c <=
'9')) {
1205 state = ParseState::kBeforeDec;
1206 before =
static_cast<
int>(c -
'0');
1207 }
else if (c == dec_point) {
1208 state = ParseState::kZerosAfterDec;
1209 }
else if (c ==
'e' || c ==
'E') {
1210 StateToExpSign( error, state, options);
1212 state = ParseState::kEnd;
1215 case ParseState::kBeforeDec:
1216 if ((c >=
'0') && (c <=
'9')) {
1217 if (before_digit_count < kMaxDecimalDigits) {
1218 before = 10 * before +
static_cast<
int>(c -
'0');
1219 before_digit_count++;
1220 }
else if (!error) {
1221 error = ParseErrorCode::kOverflow;
1223 }
else if (c == dec_point) {
1224 state = ParseState::kZerosAfterDec;
1225 }
else if (c ==
'e' || c ==
'E') {
1226 StateToExpSign( error, state, options);
1228 state = ParseState::kEnd;
1232 case ParseState::kZerosAfterDec:
1235 after_digit_count++;
1240 case ParseState::kAfterDec:
1241 state = ParseState::kAfterDec;
1242 if ((c >=
'0') && (c <=
'9')) {
1243 if (after_digit_count < kMaxDecimalDigits) {
1244 after = 10 * after +
static_cast<
int>(c -
'0');
1245 after_digit_count++;
1247 if (!(options & ParseOptions::kAllowRounding) && !error) {
1248 error = ParseErrorCode::kRounding;
1250 state = ParseState::kIgnoringAfterDec;
1257 }
else if (c ==
'e' || c ==
'E') {
1258 StateToExpSign( error, state, options);
1260 if (!(options & ParseOptions::kAllowBoundaryDot) && after_digit_count == 0 && !error) {
1261 error = ParseErrorCode::kBoundaryDot;
1263 state = ParseState::kEnd;
1267 case ParseState::kIgnoringAfterDec:
1268 if ((c >=
'0') && (c <=
'9')) {
1270 }
else if (c ==
'e' || c ==
'E') {
1271 StateToExpSign( error, state, options);
1273 state = ParseState::kEnd;
1277 case ParseState::kExpSign:
1279 is_negative_exp =
false;
1280 state = ParseState::kExpFirstDigit;
1281 }
else if (c ==
'-') {
1282 is_negative_exp =
true;
1283 state = ParseState::kExpFirstDigit;
1284 }
else if (c >=
'0' && c <=
'9') {
1285 exponent =
static_cast<
int>(c -
'0');
1286 state = ParseState::kExpDigits;
1289 error = ParseErrorCode::kWrongChar;
1291 state = ParseState::kEnd;
1295 case ParseState::kExpFirstDigit:
1296 if (c >=
'0' && c <=
'9') {
1297 exponent =
static_cast<
int>(c -
'0');
1298 state = ParseState::kExpDigits;
1300 state = ParseState::kEnd;
1302 error = ParseErrorCode::kWrongChar;
1307 case ParseState::kExpDigits:
1308 if (c >=
'0' && c <=
'9') {
1309 if ((__builtin_mul_overflow(10, exponent, &exponent) ||
1310 __builtin_add_overflow(
static_cast<
int>(c -
'0'), exponent, &exponent)))
1312 if (is_negative_exp && !(options & ParseOptions::kAllowRounding) && !error) {
1313 error = ParseErrorCode::kRounding;
1315 if (!is_negative_exp && !error) {
1316 error = ParseErrorCode::kOverflow;
1320 state = ParseState::kEnd;
1324 case ParseState::kEnd:
1330 if (state == ParseState::kEnd) {
1333 if (!error && !(options & ParseOptions::kAllowTrailingJunk)) {
1334 if (!(options & ParseOptions::kAllowSpaces)) {
1335 error = ParseErrorCode::kSpace;
1340 const auto c = input.Get();
1346 error = ParseErrorCode::kTrailingJunk;
1354 if (!error && before_digit_count == 0 && after_digit_count == 0) {
1355 error = ParseErrorCode::kNoDigits;
1358 if (!error && state == ParseState::kZerosAfterDec && !(options & ParseOptions::kAllowBoundaryDot) &&
1359 after_digit_count == 0)
1361 error = ParseErrorCode::kBoundaryDot;
1364 if ((!error && state == ParseState::kExpSign) || (!error && state == ParseState::kExpFirstDigit)) {
1365 error = ParseErrorCode::kNoExponentDigits;
1368 if (zeros_after_dec >= kMaxDecimalDigits) {
1369 after_digit_count = 0;
1371 zeros_after_dec = 0;
1382 static_cast<uint32_t>(position),
1388template <
int Prec,
typename RoundPolicy>
1390 Decimal<Prec, RoundPolicy> decimal;
1391 std::optional<ParseErrorCode> error;
1392 uint32_t error_position{-1U};
1396template <
int Prec,
typename RoundPolicy,
typename CharSequence>
1397[[nodiscard]]
constexpr ParseResult<Prec, RoundPolicy> Parse(CharSequence input,
utils::Flags<ParseOptions> options) {
1398 ParseUnpackedResult parsed = ParseUnpacked(input, options);
1401 return {{}, parsed.error, parsed.error_position};
1404 if (!(options & ParseOptions::kAllowRounding) && parsed.decimal_digits > Prec) {
1405 return {{}, ParseErrorCode::kRounding, 0};
1408 if (parsed.before >= kMaxInt64 / kPow10<Prec>) {
1409 return {{}, ParseErrorCode::kOverflow, 0};
1412 if (parsed.after == 0 && parsed.decimal_digits > 0) {
1414 parsed.decimal_digits = 0;
1415 parsed.zeros_after_dec = 0;
1418 if (parsed.is_negative) {
1419 parsed.before = -parsed.before;
1420 parsed.after = -parsed.after;
1423 if (parsed.exponent != 0) {
1425 FromUnpackedWithExponent<Prec, RoundPolicy>(
1428 parsed.decimal_digits,
1430 parsed.is_negative_exponent,
1431 parsed.zeros_after_dec
1438 return {FromUnpacked<Prec, RoundPolicy>(parsed.before, parsed.after, parsed.decimal_digits), {}, 0};
1441std::string GetErrorMessage(std::string_view source, std::string_view path, size_t position, ParseErrorCode reason);
1445void TrimTrailingZeros(int64_t& after,
int& after_precision);
1447std::string ToString(int64_t before, int64_t after,
int precision,
const FormatOptions& format_options);
1451template <
int Prec,
typename RoundPolicy>
1453 const auto result = impl::Parse<Prec, RoundPolicy>(impl::StringCharSequence(value), impl::ParseOptions::kNone);
1456 throw ParseError(impl::GetErrorMessage(value,
"<string>", result.error_position, *result.error));
1458 *
this = result.decimal;
1461template <
int Prec,
typename RoundPolicy>
1463 const auto result = impl::Parse<Prec, RoundPolicy>(
1464 impl::StringCharSequence(input),
1465 {impl::ParseOptions::kAllowSpaces,
1466 impl::ParseOptions::kAllowBoundaryDot,
1467 impl::ParseOptions::kAllowRounding,
1468 impl::ParseOptions::kAllowExponent}
1472 throw ParseError(impl::GetErrorMessage(input,
"<string>", result.error_position, *result.error));
1474 return result.decimal;
1485template <
int Prec,
typename RoundPolicy>
1487 return fmt::to_string(dec);
1501template <
int Prec,
typename RoundPolicy>
1503 auto precision = format_options
.precision.value_or(Prec);
1505 precision = std::min(precision, Prec);
1507 auto [before, after] = impl::AsUnpacked(dec, precision);
1508 return impl::ToString(before, after, precision, format_options);
1519template <
int Prec,
typename RoundPolicy>
1521 return fmt::format(FMT_COMPILE(
"{:f}"), dec);
1532template <
int NewPrec,
int Prec,
typename RoundPolicy>
1534 return ToStringTrailingZeros(
decimal64::decimal_cast<
Decimal<NewPrec, RoundPolicy>>(dec));
1552template <
typename CharT,
typename Traits,
int Prec,
typename RoundPolicy>
1553std::basic_istream<CharT, Traits>&
operator>>(std::basic_istream<CharT, Traits>& is,
Decimal<Prec, RoundPolicy>& d) {
1554 if (is.flags() & std::ios_base::skipws) {
1557 const auto result = impl::Parse<Prec, RoundPolicy>(
1558 impl::StreamCharSequence(is),
1559 {impl::ParseOptions::kAllowTrailingJunk, impl::ParseOptions::kAllowExponent}
1563 is.setstate(std::ios_base::failbit);
1572template <
typename CharT,
typename Traits,
int Prec,
typename RoundPolicy>
1573std::basic_ostream<CharT, Traits>& operator<<(
1574 std::basic_ostream<CharT, Traits>& os,
1575 const Decimal<Prec, RoundPolicy>& d
1583template <
int Prec,
typename RoundPolicy>
1584logging::LogHelper& operator<<(logging::LogHelper& lh,
const Decimal<Prec, RoundPolicy>& d) {
1591template <
int Prec,
typename RoundPolicy,
formats::
common::kIsFormatValue Value>
1593 const std::string input = value.
template As<std::string>();
1596 impl::Parse<Prec, RoundPolicy>(impl::StringCharSequence(std::string_view{input}), impl::ParseOptions::kNone);
1599 throw ParseError(impl::GetErrorMessage(input, value.GetPath(), result.error_position, *result.error));
1601 return result.decimal;
1606template <
int Prec,
typename RoundPolicy,
typename TargetType>
1608 return typename TargetType::Builder(ToString(object)).ExtractValue();
1613template <
int Prec,
typename RoundPolicy,
typename StringBuilder>
1615 WriteToStream(ToString(object), sw);
1619template <
int Prec,
typename RoundPolicy>
1626USERVER_NAMESPACE_END
1629template <
int Prec,
typename RoundPolicy>
1633 std::size_t operator()(
const Decimal& v)
const noexcept {
return std::hash<int64_t>{}(v.AsUnbiased()); }
1644template <
int Prec,
typename RoundPolicy,
typename Char>
1647 constexpr auto parse(fmt::basic_format_parse_context<Char>& ctx) {
1648 const auto* it = ctx.begin();
1649 const auto* end = ctx.end();
1651 if (it != end && *it ==
'.') {
1652 remove_trailing_zeros_ =
false;
1653 custom_precision_ = 0;
1655 while (it != end && *it >=
'0' && *it <=
'9') {
1656 *custom_precision_ = *custom_precision_ * 10 + (*it -
'0');
1661 if (!custom_precision_ && it != end && *it ==
'f') {
1662 remove_trailing_zeros_ =
false;
1666 if (it != end && *it !=
'}') {
1667 throw format_error(
"invalid format");
1673 template <
typename FormatContext>
1674 auto format(
const USERVER_NAMESPACE::
decimal64::
Decimal<Prec, RoundPolicy>& dec, FormatContext& ctx)
const {
1675 int after_digits = custom_precision_.value_or(Prec);
1676 auto [before, after] = USERVER_NAMESPACE::
decimal64::impl::AsUnpacked(dec, after_digits);
1677 if (remove_trailing_zeros_) {
1678 USERVER_NAMESPACE::
decimal64::impl::TrimTrailingZeros(after, after_digits);
1681 if (after_digits > 0) {
1682 if (dec.Sign() == -1) {
1683 return fmt::format_to(ctx.out(), FMT_COMPILE(
"-{}.{:0{}}"), -before, -after, after_digits);
1685 return fmt::format_to(ctx.out(), FMT_COMPILE(
"{}.{:0{}}"), before, after, after_digits);
1688 return fmt::format_to(ctx.out(), FMT_COMPILE(
"{}"), before);
1693 bool remove_trailing_zeros_ =
true;
1694 std::optional<
int> custom_precision_;