microfmt
Zero-allocation C++ formatting for constrained environments
Loading...
Searching...
No Matches
floating.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
10#include "../microfmt.hpp"
11#include <cstddef>
12#include <cstdio>
13#include <string_view>
14#include <type_traits>
15
16namespace microfmt {
17
18namespace detail {
19
27 char sign{'\0'}; // '+', ' ', or '\0'
31 bool alt_form{false}; // '#' (force decimal point)
35 int precision{-1}; // -1 if not specified
39 char type{'g'}; // 'f', 'F', 'e', 'E', 'g', 'G', 'a', 'A'
40};
41
47inline constexpr parsed_float_spec parse_float_spec(microfmt::string_view spec) noexcept {
49 if (spec.empty()) {
50 return res;
51 }
52
53 size_t pos = 0;
54 const size_t len = spec.size();
55
56 // Sign option: ['+' | '-' | ' ']
57 if (pos < len && (spec[pos] == '+' || spec[pos] == '-' || spec[pos] == ' ')) {
58 if (spec[pos] != '-') {
59 res.sign = spec[pos];
60 }
61 pos++;
62 }
63
64 // Alternate form: ['#']
65 if (pos < len && spec[pos] == '#') {
66 res.alt_form = true;
67 pos++;
68 }
69
70 // Precision: ['.' precision]
71 if (pos < len && spec[pos] == '.') {
72 pos++;
73 res.precision = 0;
74 while (pos < len && spec[pos] >= '0' && spec[pos] <= '9') {
75 res.precision = res.precision * 10 + (spec[pos++] - '0');
76 }
77 }
78
79 // Type presentation: [f|F|e|E|g|G|a|A]
80 if (pos < len) {
81 const char ch = spec[pos];
82 if (ch == 'f' || ch == 'F' || ch == 'e' || ch == 'E' || ch == 'g' || ch == 'G' || ch == 'a' || ch == 'A') {
83 res.type = ch;
84 }
85 }
86
87 return res;
88}
89
90// Build a standard printf format string (e.g., "%+#.4f" or "%Lg")
98template <typename T>
99RELOCO_UNSAFE_BUFFER_USAGE inline size_t build_printf_float_format(char *dest, const parsed_float_spec &spec) noexcept {
100 size_t idx = 0;
101 dest[idx++] = '%';
102
103 if (spec.sign != '\0') {
104 dest[idx++] = spec.sign;
105 }
106 if (spec.alt_form) {
107 dest[idx++] = '#';
108 }
109 if (spec.precision >= 0) {
110 dest[idx++] = '.';
111 dest[idx++] = '*'; // dynamic precision passed via argument
112 }
113
114 if constexpr (std::is_same_v<T, long double>) {
115 dest[idx++] = 'L';
116 }
117
118 dest[idx++] = spec.type;
119 dest[idx] = '\0';
120 return idx;
121}
122
134// low-level formatting: raw character pointers and integer width conversions bounded by the buffer sizes checked here.
135RELOCO_BEGIN_UNSAFE_CODE
136template <typename T>
137RELOCO_UNSAFE_BUFFER_USAGE inline void format_float_via_printf(T val, const parsed_float_spec &spec,
138 const sink &out) noexcept {
139 char fmt_buf[16] = {};
140 build_printf_float_format<T>(fmt_buf, spec);
141
142 char buf[128] = {};
143 const auto print_float = [&spec, &fmt_buf, &val](char *dest, size_t size) noexcept {
144 if (spec.precision >= 0)
145 return std::snprintf(dest, size, fmt_buf, spec.precision, val);
146 return std::snprintf(dest, size, fmt_buf, val);
147 };
148 const int written = print_float(buf, sizeof(buf));
149
150 if (written > 0) {
151 if (static_cast<size_t>(written) < sizeof(buf)) {
152 out.write(microfmt::string_view(buf, static_cast<size_t>(written)));
153 } else {
154 // Stack buffer overflow fallback (rare for extreme precision)
155 size_t heap_size = static_cast<size_t>(written) + 1;
156 auto *heap_buf = new (std::nothrow) char[heap_size];
157 if (heap_buf != nullptr) {
158 (void)print_float(heap_buf, heap_size);
159 out.write(microfmt::string_view(heap_buf, static_cast<size_t>(written)));
160 delete[] heap_buf;
161 }
162 }
163 }
164}
166
167} // namespace detail
168
169// ============================================================================
170// Formatter Specializations
171// ============================================================================
172
176template <> struct formatter<float> {
181
186 constexpr void parse(format_parse_context &ctx) noexcept { spec_ = detail::parse_float_spec(ctx.spec()); }
187
193 void format(float val, const sink &out) const noexcept {
194 // Promoted to double for standard %f / %g printf conversions
195 RELOCO_BEGIN_UNSAFE_BUFFER_USAGE;
196
197 detail::format_float_via_printf<double>(static_cast<double>(val), spec_, out);
198
199 RELOCO_END_UNSAFE_BUFFER_USAGE;
200 }
201};
202
206template <> struct formatter<double> {
211
216 constexpr void parse(format_parse_context &ctx) noexcept { spec_ = detail::parse_float_spec(ctx.spec()); }
217
223 void format(double val, const sink &out) const noexcept {
224 RELOCO_BEGIN_UNSAFE_BUFFER_USAGE;
225
226 detail::format_float_via_printf<double>(val, spec_, out);
227
228 RELOCO_END_UNSAFE_BUFFER_USAGE;
229 }
230};
231
236template <> struct formatter<long double> {
241
246 constexpr void parse(format_parse_context &ctx) noexcept { spec_ = detail::parse_float_spec(ctx.spec()); }
247
253 void format(long double val, const sink &out) const noexcept {
254 RELOCO_BEGIN_UNSAFE_BUFFER_USAGE;
255
256 detail::format_float_via_printf<long double>(val, spec_, out);
257
258 RELOCO_END_UNSAFE_BUFFER_USAGE;
259 }
260};
261
262} // namespace microfmt
Definition microfmt.hpp:881
#define MICROFMT_API_CLASS
Definition compat.hpp:181
RELOCO_UNSAFE_BUFFER_USAGE size_t build_printf_float_format(char *dest, const parsed_float_spec &spec) noexcept
Builds a printf-style format string into a caller buffer.
Definition floating.hpp:99
constexpr parsed_float_spec parse_float_spec(microfmt::string_view spec) noexcept
Parses a floating-point format specifier string.
Definition floating.hpp:47
RELOCO_BEGIN_UNSAFE_CODE RELOCO_UNSAFE_BUFFER_USAGE void format_float_via_printf(T val, const parsed_float_spec &spec, const sink &out) noexcept
Formats a floating-point value via snprintf into the given sink.
Definition floating.hpp:137
Definition assert.hpp:19
reloco::sink sink
Type-erased, zero-allocation output sink.
Definition microfmt.hpp:46
RELOCO_END_UNSAFE_CODE
Definition remote_object.hpp:132
Parsed floating-point format specifier state.
Definition floating.hpp:23
char sign
Optional sign character: ‘’+',' 'or'\0'` (none).
Definition floating.hpp:27
constexpr void parse(format_parse_context &ctx) noexcept
Parses the floating-point format specifier.
Definition floating.hpp:216
void format(double val, const sink &out) const noexcept
Renders a double value.
Definition floating.hpp:223
void format(float val, const sink &out) const noexcept
Renders a float value.
Definition floating.hpp:193
constexpr void parse(format_parse_context &ctx) noexcept
Parses the floating-point format specifier.
Definition floating.hpp:186
void format(long double val, const sink &out) const noexcept
Renders a long double value.
Definition floating.hpp:253
constexpr void parse(format_parse_context &ctx) noexcept
Parses the floating-point format specifier.
Definition floating.hpp:246
Definition microfmt.hpp:985