microfmt
Zero-allocation C++ formatting for constrained environments
Loading...
Searching...
No Matches
units.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 <string_view>
12#include <type_traits>
13
14namespace microfmt {
15
16// ============================================================================
17// Unit Scaling Modes
18// ============================================================================
19
20enum class scale_base : uint16_t {
21 decimal = 1000, // SI: Hz, V, A, s, m (k, M, G, T)
22 binary = 1024 // IEC: Bytes (KiB, MiB, GiB, TiB)
23};
24
25// ============================================================================
26// Unit View Descriptors
27// ============================================================================
28
29// Auto-scaled View (e.g., 1048576 B -> "1.00 MiB" or 50000000 Hz -> "50.00
30// MHz")
31template <typename T> struct RELOCO_POINTER auto_unit_view {
32 T value{0};
33 microfmt::string_view unit_symbol{};
34 scale_base base{scale_base::decimal};
35 uint8_t precision{2}; // Decimal places
36};
37
38// Explicit Fixed Unit View (e.g., 42 "mA" -> "42 mA")
39template <typename T> struct RELOCO_POINTER unit_view {
40 T value{0};
41 microfmt::string_view unit_symbol{};
42};
43
44// ============================================================================
45// Factory Helpers
46// ============================================================================
47
48// Auto-scaling SI decimal units (Hz, W, V, etc.)
49template <typename T, std::enable_if_t<std::is_integral_v<T>, int> = 0>
50[[nodiscard]] constexpr auto auto_si(T val, microfmt::string_view unit, uint8_t precision = 2) noexcept {
51 return auto_unit_view<T>{val, unit, scale_base::decimal, precision};
52}
53
54// Auto-scaling IEC binary units (B / Bytes)
55template <typename T, std::enable_if_t<std::is_integral_v<T>, int> = 0>
56[[nodiscard]] constexpr auto auto_bytes(T bytes, uint8_t precision = 2) noexcept {
57 return auto_unit_view<T>{bytes, "B", scale_base::binary, precision};
58}
59
60// Fixed unit tag (no scaling)
61template <typename T> [[nodiscard]] constexpr auto with_unit(T val, microfmt::string_view unit) noexcept {
62 return unit_view<T>{val, unit};
63}
64
65// Frequency convenience helper
66template <typename T, std::enable_if_t<std::is_integral_v<T>, int> = 0>
67[[nodiscard]] constexpr auto hertz(T hz, uint8_t precision = 2) noexcept {
68 return auto_unit_view<T>{hz, "Hz", scale_base::decimal, precision};
69}
70
71// ============================================================================
72// Formatter Specializations
73// ============================================================================
74
75// --- Fixed Unit Formatter ---
76template <typename T> struct formatter<unit_view<T>> {
77 microfmt::string_view forwarded_spec{""};
78
79 constexpr void parse(format_parse_context &ctx) noexcept { forwarded_spec = ctx.spec(); }
80
81 void format(const unit_view<T> &uv, const sink &out) const noexcept {
82 formatter<T> val_fmt;
83 format_parse_context val_ctx(forwarded_spec);
84 val_fmt.parse(val_ctx);
85 val_fmt.format(uv.value, out);
86
87 out.put(' ');
88 out.write(uv.unit_symbol);
89 }
90};
91
92// --- Auto-Scaling Formatter (Zero-Float Fixed-Point Scaling) ---
93template <typename T> struct formatter<auto_unit_view<T>> {
94 uint8_t parse_precision{255}; // 255 = not set via format string
95
96 // time-field arithmetic converts between C/POSIX integer widths whose ranges are bounded by the format.
97 RELOCO_BEGIN_UNSAFE_CODE
98 constexpr void parse(format_parse_context &ctx) noexcept {
99 // Parse precision from format string, e.g. "{:.1}" or "{:.3}"
100 auto spec = ctx.spec();
101 for (size_t i = 0; i < spec.size(); ++i) {
102 if (spec[i] == '.' && (i + 1) < spec.size()) {
103 char next = spec[i + 1];
104 if (next >= '0' && next <= '9') {
105 parse_precision = static_cast<uint8_t>(next - '0');
106 }
107 }
108 }
109 }
111
112 void format(const auto_unit_view<T> &auv, const sink &out) const noexcept {
113 static constexpr reloco::array<string_view, 6> SI_PREFIXES = {"", "k", "M", "G", "T", "P"};
114 static constexpr reloco::array<string_view, 6> IEC_PREFIXES = {"", "Ki", "Mi", "Gi", "Ti", "Pi"};
115
116 const uint32_t base_div = static_cast<uint32_t>(auv.base);
117 const auto &prefixes = (auv.base == scale_base::binary) ? IEC_PREFIXES : SI_PREFIXES;
118
119 const bool is_negative = auv.value < 0;
120 uint64_t abs_val = is_negative ? static_cast<uint64_t>(-auv.value) : static_cast<uint64_t>(auv.value);
121
122 size_t scale_idx = 0;
123 uint64_t remainder = 0;
124 uint64_t current_div = 1;
125
126 // Scale up until value fits under the base divisor
127 while (abs_val >= base_div && scale_idx < 5) {
128 remainder = abs_val % base_div;
129 abs_val /= base_div;
130 current_div = base_div;
131 scale_idx++;
132 }
133
134 if (is_negative) {
135 out.put('-');
136 }
137
138 // Whole part
139 detail::format_unsigned<detail::radix::decimal>(out, abs_val, false, 0);
140
141 const uint8_t prec = (parse_precision != 255) ? parse_precision : auv.precision;
142
143 // Fractional part via pure integer arithmetic
144 if (prec > 0 && scale_idx > 0) {
145 out.put('.');
146 uint64_t frac_multiplier = 1;
147 for (uint8_t i = 0; i < prec; ++i)
148 frac_multiplier *= 10;
149
150 uint64_t frac_part = (remainder * frac_multiplier) / current_div;
151 detail::format_unsigned<detail::radix::decimal>(out, frac_part, false, prec);
152 }
153
154 out.put(' ');
155 out.write(prefixes[scale_idx]);
156 out.write(auv.unit_symbol);
157 }
158};
159
160} // namespace microfmt
Definition microfmt.hpp:881
Definition assert.hpp:19
constexpr auto with_unit(T val, microfmt::string_view unit) noexcept
Definition units.hpp:61
constexpr auto auto_bytes(T bytes, uint8_t precision=2) noexcept
Definition units.hpp:56
reloco::sink sink
Type-erased, zero-allocation output sink.
Definition microfmt.hpp:46
scale_base
Definition units.hpp:20
constexpr auto hertz(T hz, uint8_t precision=2) noexcept
Definition units.hpp:67
constexpr bytes_view bytes(uint64_t count) noexcept
Definition format_helpers.hpp:82
RELOCO_END_UNSAFE_CODE
Definition remote_object.hpp:132
constexpr auto auto_si(T val, microfmt::string_view unit, uint8_t precision=2) noexcept
Definition units.hpp:50
Definition units.hpp:31
RELOCO_BEGIN_UNSAFE_CODE constexpr void parse(format_parse_context &ctx) noexcept
Definition units.hpp:98
RELOCO_END_UNSAFE_CODE void format(const auto_unit_view< T > &auv, const sink &out) const noexcept
Definition units.hpp:112
constexpr void parse(format_parse_context &ctx) noexcept
Definition units.hpp:79
void format(const unit_view< T > &uv, const sink &out) const noexcept
Definition units.hpp:81
Definition microfmt.hpp:985
Definition units.hpp:39