microfmt
Zero-allocation C++ formatting for constrained environments
Loading...
Searching...
No Matches
fixed_point.hpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2026 Jarosław Pelczar <jarek@jpelczar.com>
2//
3// SPDX-License-Identifier: BSD-2-Clause
4
5#pragma once
6
9#include "../microfmt.hpp"
10#include <cstdint>
11#include <type_traits>
12
13namespace microfmt {
14
15namespace detail {
16
17// Compile-time power of 10 for uint64_t
18constexpr uint64_t ct_pow10(uint8_t exp) noexcept {
19 uint64_t result = 1;
20 for (uint8_t i = 0; i < exp; ++i) {
21 result *= 10;
22 }
23 return result;
24}
25
26} // namespace detail
27
28// ============================================================================
29// Fixed-Point View Adapter
30// ============================================================================
31// Scale: Value representation divider (e.g., Scale = 1000 for
32// millivolts/milliseconds) Decimals: Number of fractional digits to format (0
33// to 18)
34template <uint64_t Scale, uint8_t Decimals = 2, typename IntType = int32_t> struct fixed_point_view {
35 static_assert(Scale > 0, "fixed_point Scale must be greater than 0");
36 static_assert(Decimals <= 18, "fixed_point Decimals exceeds 64-bit precision");
37 static_assert(std::is_integral_v<IntType>, "fixed_point value must be an integral type");
38
39 IntType raw_value{0};
40};
41
42// Convenience factory function
43template <uint64_t Scale, uint8_t Decimals = 2, typename IntType>
44[[nodiscard]] constexpr auto fixed(IntType raw_val) noexcept {
46}
47
48// Common embedded aliases
49template <typename IntType = int32_t> using milli_view = fixed_point_view<1000, 3, IntType>; // e.g., mV -> V, ms -> s
50
51template <typename IntType = int32_t> using centi_view = fixed_point_view<100, 2, IntType>; // e.g., 0.01 increments
52
53template <typename IntType = int32_t>
54using micro_view = fixed_point_view<1000000, 6, IntType>; // e.g., us -> s, uA -> A
55
56template <typename IntType> [[nodiscard]] constexpr auto milli(IntType raw_val) noexcept {
57 return milli_view<IntType>{raw_val};
58}
59
60template <typename IntType> [[nodiscard]] constexpr auto centi(IntType raw_val) noexcept {
61 return centi_view<IntType>{raw_val};
62}
63
64template <typename IntType> [[nodiscard]] constexpr auto micro(IntType raw_val) noexcept {
65 return micro_view<IntType>{raw_val};
66}
67
68// ============================================================================
69// Formatter Specialization for fixed_point_view
70// ============================================================================
71template <uint64_t Scale, uint8_t Decimals, typename IntType>
72struct formatter<fixed_point_view<Scale, Decimals, IntType>> {
73 constexpr void parse(format_parse_context &ctx) noexcept { (void)ctx; }
74
75 void format(const fixed_point_view<Scale, Decimals, IntType> &fp, const sink &out) const noexcept {
76 using UnsignedType =
77 std::make_unsigned_t<std::conditional_t<(sizeof(IntType) < 4), uint32_t, std::make_unsigned_t<IntType>>>;
78
79 bool is_negative = false;
80 UnsignedType abs_raw = 0;
81
82 if constexpr (std::is_signed_v<IntType>) {
83 if (fp.raw_value < 0) {
84 is_negative = true;
85 // Avoid undefined behavior on INT_MIN
86 abs_raw = static_cast<UnsignedType>(0) - static_cast<UnsignedType>(fp.raw_value);
87 } else {
88 abs_raw = static_cast<UnsignedType>(fp.raw_value);
89 }
90 } else {
91 abs_raw = fp.raw_value;
92 }
93
94 const uint64_t integer_part = static_cast<uint64_t>(abs_raw / Scale);
95 const uint64_t remainder = static_cast<uint64_t>(abs_raw % Scale);
96
97 // Compute fractional representation scaled to the requested number of
98 // decimals
99 constexpr uint64_t target_mult = detail::ct_pow10(Decimals);
100 uint64_t frac_part = 0;
101
102 if constexpr (Decimals > 0) {
103 // remainder * target_mult / Scale
104 frac_part = (remainder * target_mult) / Scale;
105 }
106
107 // Output Sign (handles -0.05 correctly where integer_part is 0)
108 if (is_negative && (integer_part > 0 || frac_part > 0)) {
109 out.put('-');
110 }
111
112 // Output Integer Part
113 detail::format_unsigned<detail::radix::decimal>(out, integer_part, false, 0);
114
115 // Output Fractional Part (zero-padded to Decimals width)
116 if constexpr (Decimals > 0) {
117 out.put('.');
118 detail::format_unsigned<detail::radix::decimal>(out, frac_part, false, Decimals);
119 }
120 }
121};
122
123} // namespace microfmt
Definition microfmt.hpp:881
constexpr uint64_t ct_pow10(uint8_t exp) noexcept
Definition fixed_point.hpp:18
Definition assert.hpp:19
constexpr auto fixed(IntType raw_val) noexcept
Definition fixed_point.hpp:44
constexpr auto centi(IntType raw_val) noexcept
Definition fixed_point.hpp:60
constexpr auto micro(IntType raw_val) noexcept
Definition fixed_point.hpp:64
reloco::sink sink
Type-erased, zero-allocation output sink.
Definition microfmt.hpp:46
constexpr auto milli(IntType raw_val) noexcept
Definition fixed_point.hpp:56
Definition fixed_point.hpp:34
IntType raw_value
Definition fixed_point.hpp:39
constexpr void parse(format_parse_context &ctx) noexcept
Definition fixed_point.hpp:73
void format(const fixed_point_view< Scale, Decimals, IntType > &fp, const sink &out) const noexcept
Definition fixed_point.hpp:75
Definition microfmt.hpp:985