1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
// devela::text::fmt::num
//
//! Implemnts [`FmtNum`] for all floating-point primitives.
//
__impl_fmt_num_float!(impl float f32|u64, f64|u128); // TEMP
///
#[doc(hidden)]
#[macro_export]
macro_rules! __impl_fmt_num_float {
() => {};
// $t: floating-point primitive
// $u: unsigned primitive to cast the integral part as
(impl float $($t:ty | $u:ty),+) => {$(
impl $crate::FmtNum<$t> {
/* write */
/// Writes the floating-point number in fixed-point decimal form into `buf`
/// starting at `pos`, using exactly `fract_len` fractional digits.
///
/// Returns the number of bytes written, or `0` if the buffer is too small.
///
/// The operation is atomic: on failure, nothing is written.
/// Negative values are preceded by the `'-'` sign.
///
/// Fractional digits are truncated and zero-padded as needed.
#[rustfmt::skip]
pub const fn write(self, buf: &mut [u8], mut pos: usize, fract_len: u16) -> usize {
use $crate::{Digits, Float, is, whilst, write_at};
let f = Float(self.0);
let (neg, fabs) = is![f.is_sign_negative(), (true, f.abs()), (false, f)];
// integral part
let integ = fabs.trunc().0 as $u;
let integ_digits = Digits(integ).count_digits10() as usize;
// compute required space
let (sign_len, dot_len) = (neg as usize, (fract_len > 0) as usize);
let needed = sign_len + integ_digits + dot_len + fract_len as usize;
if needed > buf.len().saturating_sub(pos) { return 0; }
// write:
if neg { write_at![buf, +=pos, b'-']; } // sign
pos += Digits(integ).write_digits10(buf, pos); // integral digits
if fract_len > 0 {
write_at![buf, +=pos, b'.']; // dot
let multiplier = Float(10.0 as $t).const_powi(fract_len as i32).0;
let fract = (fabs.fract().0 * multiplier) as $u;
let written = Digits(fract).write_digits10(buf, pos); // frac. digits
pos += written;
let missing = fract_len as usize - written;
whilst! { _i in 0..missing; { write_at![buf, +=pos, b'0']; }}
}
needed
}
/// Writes the floating-point number in fixed-point decimal form into `buf`
/// starting at `pos`, using the given formatting configuration.
///
/// Returns the number of bytes written, or `0` if the buffer is too small.
///
/// The operation is atomic: on failure, nothing is written.
///
/// The emitted sign, any leading zero-padding and the number of fractional digits
/// are controlled by `conf`.
pub const fn write_fmt(self, buf: &mut [u8], mut pos: usize, conf: $crate::FmtNumConf)
-> usize {
use $crate::{Cmp, Digits, Float, is, whilst, write_at};
let f = Float(self.0);
let (neg, fabs) = is![f.is_sign_negative(), (true, f.abs()), (false, f)];
let emit_sign = conf.sign.emit_sign(neg);
// digit count of integral part
let integ = fabs.trunc().0 as $u;
let digit_count = Digits(integ).count_digits10() as u16;
let left_digits = if conf.pad_sign && emit_sign {
Cmp(digit_count + 1).max(conf.int) - 1
} else {
Cmp(digit_count).max(conf.int)
};
// compute required space
let (sign_len, dot_len) = (emit_sign as usize, (conf.fract > 0) as usize);
let needed = sign_len + (left_digits as usize) + dot_len + (conf.fract as usize);
// emit sign, zero padding, int digits, dot and fract digits
if emit_sign { write_at![buf, +=pos, is![neg, b'-', b'+']]; } // ←sign ↓zero-padding
whilst! { _i in 0..(left_digits - digit_count); { write_at![buf, +=pos, b'0']; }}
pos += Digits(integ).write_digits10(buf, pos); // integral digits
if conf.fract > 0 {
write_at![buf, +=pos, b'.']; // dot
let multiplier = Float(10.0 as $t).const_powi(conf.fract as i32).0;
let fract = (fabs.fract().0 * multiplier) as $u;
let written = Digits(fract).write_digits10(buf, pos); // frac. digits
pos += written;
let missing = conf.fract as usize - written;
whilst! { _i in 0..missing; { write_at![buf, +=pos, b'0']; }}
}
needed
}
/// Writes the formatted floating-point number with optional digit grouping.
///
/// Grouping is applied to the *rendered* digit sequences on both sides of the radix,
/// after sign emission, zero-padding, and precision handling have been accounted for.
///
/// # Invariants
/// - Grouping assumes 1-byte separators and ASCII digits.
/// - `conf.int` specifies the minimum width of the *entire* left block
/// (integral digits + zero padding + grouping separators), excluding the sign.
/// - `conf.fract` specifies the fractional precision before grouping is applied
/// to the rendered fractional digit sequence.
/// - On failure, nothing is written (atomic operation).
pub const fn write_group(self, buf: &mut [u8], pos: usize,
conf: $crate::FmtNumConf, group: $crate::FmtNumGroup) -> usize {
use $crate::{Boundary1d, Digits, Float, is};
// Fast path: grouping disabled on both sides.
if (group.left_len == 0 || group.left_sep.is_none())
&& (group.right_len == 0 || group.right_sep.is_none())
{
return self.write_fmt(buf, pos, conf);
}
let f = Float(self.0);
let neg = f.is_sign_negative();
let fabs = is![neg, f.abs(), f];
/* left (integral) side */
// Integral magnitude and digit count.
let integ = fabs.trunc().0 as u128;
let integ_digits = Digits(integ).count_digits10() as u16;
let emit_sign = conf.sign.emit_sign(neg);
// Target minimum width of the left block (excluding sign unless pad_sign is set).
let mut target_left = conf.int;
if conf.pad_sign && emit_sign && target_left > 0 {
target_left -= 1;
}
// Compute minimal digit count so that: digits + grouping seps >= target_left.
let left_digits = group.digits_for_grouped_width(Boundary1d::Left,
integ_digits, target_left);
/* right (fractional) side */
// Fractional digit count is fixed by configuration.
let fract_digits = conf.fract;
// Compute minimal digit count so that: digits + grouping seps >= target_right.
let right_digits = group.digits_for_grouped_width(Boundary1d::Right,
fract_digits, fract_digits);
// Adjusted configuration.
let conf2 = $crate::FmtNumConf {
int: is![conf.pad_sign && emit_sign, left_digits + 1, left_digits],
fract: right_digits,
..conf
};
// Measure final layout.
let raw_shape = self.measure_fmt(conf2);
let g_shape = self.measure_group(conf2, group);
let (raw_len, final_len) = (raw_shape.total(), g_shape.total());
if final_len > buf.len().saturating_sub(pos) { return 0; }
// Write ungrouped.
let written = self.write_fmt(buf, pos, conf2);
if written == 0 { return 0; }
/* Backward expansion */
let mut src = pos + raw_len;
let mut dst = pos + final_len;
// Counters for both sides of the radix.
let mut left_left = raw_shape.left;
let mut right_left = raw_shape.right;
let mut left_group = 0u16;
let mut right_group = 0u16;
let left_len = group.left_len as u16;
let right_len = group.right_len as u16;
let has_left = group.left_len != 0 && group.left_sep.is_some();
let has_right = group.right_len != 0 && group.right_sep.is_some();
while src > pos {
src -= 1;
dst -= 1;
let b = buf[src];
buf[dst] = b;
// Fractional side (right of radix)
if b >= b'0' && b <= b'9' && right_left > 0 {
right_left -= 1;
if has_right {
right_group += 1;
if right_left > 0 && right_group == right_len {
dst -= 1;
buf[dst] = $crate::unwrap![some group.right_sep] as u8;
right_group = 0;
}
}
continue;
}
// Integral side (left of radix)
if b >= b'0' && b <= b'9' && left_left > 0 {
left_left -= 1;
if has_left {
left_group += 1;
if left_left > 0 && left_group == left_len {
dst -= 1;
buf[dst] = $crate::unwrap![some group.left_sep] as u8;
left_group = 0;
}
}
}
}
final_len
}
/* measure */
/// Returns the measured shape of the floating-point to be formatted.
pub const fn measure(self, fract_len: u16) -> $crate::FmtNumShape {
use $crate::{Digits, Float, is};
let f = Float(self.0);
let (neg, fabs) = is![f.is_sign_negative(), (true, f.abs()), (false, f)];
let int_digits = Digits(fabs.trunc().0 as $u).count_digits10() as u16;
$crate::FmtNumShape::new(neg as u16, int_digits, fract_len)
}
/// Returns the measured shape of the floating-point to be formatted,
/// using the given formatting configuration.
pub const fn measure_fmt(self, conf: $crate::FmtNumConf) -> $crate::FmtNumShape {
use $crate::{Cmp, Digits, Float, is};
let f = Float(self.0);
let (neg, fabs) = is![f.is_sign_negative(), (true, f.abs()), (false, f)];
let prefix = conf.sign.emit_sign(neg) as u16;
let int_digits = Digits(fabs.trunc().0 as $u).count_digits10() as u16;
let left = if conf.pad_sign && prefix > 0 { Cmp(int_digits + 1).max(conf.int) - 1 }
else { Cmp(int_digits).max(conf.int) };
$crate::FmtNumShape::new(prefix, left, conf.fract)
}
/// Returns the measured shape of the number after applying digit grouping.
///
/// This first measures the formatted number using `measure_fmt`, then
/// accounts for any additional separator glyphs introduced by grouping.
///
/// Grouping assumes 1-byte separators and ASCII digits.
pub const fn measure_group(self, conf: $crate::FmtNumConf, group: $crate::FmtNumGroup)
-> $crate::FmtNumShape {
let mut shape = self.measure_fmt(conf);
if group.left_len > 0 && group.left_sep.is_some() && shape.left > 0 {
let groups = (shape.left.saturating_sub(1)) / group.left_len as u16;
shape.left += groups;
}
if group.right_len > 0 && group.right_sep.is_some() && shape.right > 0 {
let groups = (shape.right.saturating_sub(1)) / group.right_len as u16;
shape.right += groups;
}
shape
}
// TODO
// pub const fn write16(self, buf: &mut [u8], pos: usize) -> usize {}
// pub const fn measure16(self) -> $crate::FmtNumShape {}
/* as_bytes */
/// Formats the number into a provided buffer and returns it as a byte slice.
///
/// This operation is atomic: if the buffer is too small, nothing is writte.
#[inline(always)]
pub const fn as_bytes_into<'b>(&self, buf: &'b mut [u8], fract_len: u16) -> &'b [u8] {
let len = self.write(buf, 0, fract_len); $crate::Slice::range_to(buf, len)
}
/// Formats the number into a provided buffer and returns it as a byte slice,
/// using the given formatting configuration.
///
/// This operation is atomic: if the buffer is too small, nothing is writte.
#[inline(always)]
pub const fn as_bytes_into_fmt<'b>(&self, buf: &'b mut [u8], conf: $crate::FmtNumConf)
-> &'b [u8] {
let len = self.write_fmt(buf, 0, conf); $crate::Slice::range_to(buf, len)
}
/// Formats the number into a provided buffer and returns it as a byte slice.
///
/// This operation is atomic: if the buffer is too small, nothing is written
/// and it returns `None`.
#[inline(always)]
pub const fn as_bytes_into_checked<'b>(&self, buf: &'b mut [u8], fract_len: u16)
-> Option<&'b [u8]> {
let len = self.write(buf, 0, fract_len);
if len == 0 { None } else { Some($crate::Slice::range_to(buf, len)) }
}
/// Formats the number into a provided buffer and returns it as a byte slice,
/// using the given formatting configuration.
///
/// This operation is atomic: if the buffer is too small, nothing is written
/// and it returns `None`.
#[inline(always)]
pub const fn as_bytes_into_checked_fmt<'b>(&self, buf: &'b mut [u8],
conf: $crate::FmtNumConf) -> Option<&'b [u8]> {
let len = self.write_fmt(buf, 0, conf);
if len == 0 { None } else { Some($crate::Slice::range_to(buf, len)) }
}
/* as_str */
#[inline(always)]
const fn _as_str(slice: &[u8]) -> &str {
#[cfg(any(feature = "safe_text", not(feature = "unsafe_str")))] // safe
return $crate::unwrap![ok_guaranteed_or_ub $crate::Str::from_utf8(slice)];
#[cfg(all(not(feature = "safe_text"), feature = "unsafe_str"))] // unsafe
// SAFETY: the ASCII bytes are always valid utf-8
unsafe { $crate::Str::from_utf8_unchecked(slice) }
}
/// Formats the number into a provided buffer and returns it as a string slice.
///
/// This operation is atomic: if the buffer is too small, nothing is written.
/// # Features
/// Uses the `unsafe_str` feature to avoid duplicated validation.
#[inline(always)]
pub const fn as_str_into<'b>(&self, buf: &'b mut [u8], fract_len: u16) -> &'b str {
let len = self.write(buf, 0, fract_len);
Self::_as_str($crate::Slice::range_to(buf, len))
}
/// Formats the number into a provided buffer and returns it as a string slice,
/// using the given formatting configuration.
///
/// This operation is atomic: if the buffer is too small, nothing is written.
/// # Features
/// Uses the `unsafe_str` feature to avoid duplicated validation.
#[inline(always)]
pub const fn as_str_into_fmt<'b>(&self, buf: &'b mut [u8],
conf: $crate::FmtNumConf) -> &'b str {
let len = self.write_fmt(buf, 0, conf);
Self::_as_str($crate::Slice::range_to(buf, len))
}
/// Formats the number into a provided buffer and returns it as a string slice.
///
/// This operation is atomic: if the buffer is too small, nothing is written
/// and it returns `None`.
/// # Features
/// Uses the `unsafe_str` feature to avoid duplicated validation.
#[inline(always)]
pub const fn as_str_into_checked<'b>(&self, buf: &'b mut [u8], fract_len: u16)
-> Option<&'b str> {
let len = self.write(buf, 0, fract_len);
$crate::is![len == 0, None, Some(Self::_as_str($crate::Slice::range_to(buf, len)))]
}
/// Formats the number into a provided buffer and returns it as a string slice,
/// using the given formatting configuration.
///
/// This operation is atomic: if the buffer is too small, nothing is written
/// and it returns `None`.
/// # Features
/// Uses the `unsafe_str` feature to avoid duplicated validation.
#[inline(always)]
pub const fn as_str_into_checked_fmt<'b>(&self, buf: &'b mut [u8],
conf: $crate::FmtNumConf) -> Option<&'b str> {
let len = self.write_fmt(buf, 0, conf);
$crate::is![len == 0, None, Some(Self::_as_str($crate::Slice::range_to(buf, len)))]
}
/* as_string */
/// Converts the number into an owned fixed-size string.
///
/// This operation is atomic: if the buffer is too small, it returns an empty string.
pub const fn as_string<const N: usize>(&self, fract_len: u16) -> $crate::StringU8<N> {
let mut buf = [0u8; N]; let len = self.write(&mut buf, 0, fract_len);
$crate::StringU8::<N>::_from_array_len_trusted(buf, len as u8)
}
/// Converts the number into an owned fixed-size string,
/// using the given formatting configuration.
///
/// This operation is atomic: if the buffer is too small, it returns an empty string.
pub const fn as_string_fmt<const N: usize>(&self, conf: $crate::FmtNumConf)
-> $crate::StringU8<N> {
let mut buf = [0u8; N]; let len = self.write_fmt(&mut buf, 0, conf);
$crate::StringU8::<N>::_from_array_len_trusted(buf, len as u8)
}
/// Converts the number into an owned fixed-size string.
///
/// This operation is atomic: if the buffer is too small, it returns `None`.
pub const fn as_string_checked<const N: usize>(&self, fract_len: u16)
-> Option<$crate::StringU8<N>> {
let mut buf = [0u8; N]; let len = self.write(&mut buf, 0, fract_len);
if len == 0 { None }
else { Some($crate::StringU8::<N>::_from_array_len_trusted(buf, len as u8)) }
}
/// Converts the number into an owned fixed-size string,
/// using the given formatting configuration.
///
/// This operation is atomic: if the buffer is too small, it returns `None`.
pub const fn as_string_checked_fmt<const N: usize>(&self, conf: $crate::FmtNumConf)
-> Option<$crate::StringU8<N>> {
let mut buf = [0u8; N]; let len = self.write_fmt(&mut buf, 0, conf);
if len == 0 { None }
else { Some($crate::StringU8::<N>::_from_array_len_trusted(buf, len as u8)) }
}
}
)+ };
}
pub use __impl_fmt_num_float;