1#![cfg_attr(not(feature = "std"), no_std)]
4
5use core::fmt;
6
7#[derive(Debug, Clone, Copy)]
8pub struct Formatter<const BASE: usize = 0, const DECIMALS: usize = 2> {
13 separator: &'static str,
17
18 units: &'static [&'static str],
23
24 custom_unit: Option<&'static str>,
26}
27
28impl Formatter {
29 pub const SI: Formatter<1000, 2> = Formatter::new(&["K", "M", "G", "T", "P", "E", "Z", "Y"]);
31
32 pub const BINARY: Formatter<1024, 2> =
34 Formatter::new(&["Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi", "Yi"]);
35
36 pub const CHINESE: Formatter<10000, 2> =
38 Formatter::new(&["万", "亿", "兆", "京", "垓", "秭", "穰", "沟"]);
39}
40
41impl<const BASE: usize, const DECIMALS: usize> Formatter<BASE, DECIMALS> {
42 #[inline]
43 pub const fn new(units: &'static [&'static str]) -> Self {
45 Self {
46 separator: " ",
47 units,
48 custom_unit: None,
49 }
50 }
51
52 #[inline]
53 pub const fn with_separator(self, separator: &'static str) -> Self {
55 Self { separator, ..self }
56 }
57
58 #[inline]
59 pub const fn with_scales<const N_BASE: usize>(
61 self,
62 units: &'static [&'static str],
63 ) -> Formatter<N_BASE, DECIMALS> {
64 debug_assert!(BASE > 0, "BASE CANNOT BE 0");
66
67 Formatter {
68 separator: self.separator,
69 units,
70 custom_unit: self.custom_unit,
71 }
72 }
73
74 #[inline]
75 pub const fn with_custom_unit(self, custom_unit: &'static str) -> Self {
77 Self {
78 custom_unit: Some(custom_unit),
79 ..self
80 }
81 }
82
83 #[inline]
84 pub const fn with_decimals<const N_DECIMALS: usize>(self) -> Formatter<BASE, N_DECIMALS> {
86 debug_assert!(
88 N_DECIMALS <= f64::DIGITS as usize,
89 "DECIMALS too large, for RELEASE profile will make use of f64::DIGITS",
90 );
91
92 Formatter {
93 separator: self.separator,
94 units: self.units,
95 custom_unit: self.custom_unit,
96 }
97 }
98
99 #[inline]
100 pub fn format(&self, number: impl NumberT) -> FormatResult<DECIMALS> {
115 if let Some(integer) = number.integer() {
116 self.format_general(integer, number.fraction())
117 .set_result_is_negative(number.is_negative())
118 } else {
119 #[cfg(feature = "std")]
120 {
121 self.format_float(
122 number
123 .fraction()
124 .expect("must be floating number which is too large"),
125 )
126 }
127
128 #[cfg(not(feature = "std"))]
129 #[allow(unsafe_code)]
130 unsafe {
131 core::hint::unreachable_unchecked()
132 }
133 }
134 }
135
136 #[inline]
137 pub fn format_int(&self, number: impl Into<i128>) -> FormatResult<DECIMALS> {
143 let number: i128 = number.into();
144
145 self.format_general(number.unsigned_abs(), None)
146 .set_result_is_negative(number.is_negative())
147 }
148
149 #[inline]
150 pub fn format_uint(&self, number: impl Into<u128>) -> FormatResult<DECIMALS> {
153 self.format_general(number.into(), None)
154 }
155
156 pub fn format_general(&self, integer: u128, fraction: Option<f64>) -> FormatResult<DECIMALS> {
172 let base = BASE as u128;
173
174 if integer < base {
175 return FormatType::General {
176 integer,
177 fraction,
178 unit: None,
179 }
180 .formatter_result(self);
181 }
182
183 let mut index: usize = 0;
184 let mut value = integer;
185
186 while value >= base {
187 value /= base;
188 index += 1;
189 }
190
191 match self.units.get(index - 1) {
192 Some(&unit) => {
193 let leftover = {
194 let leftover_exp = (base).pow(index as u32);
195 (integer - value * leftover_exp) as f64 / leftover_exp as f64
196 };
197
198 #[cfg(feature = "std")]
199 {
200 let leftover_fraction = fraction.unwrap_or(0.0) + leftover.fract();
202
203 FormatType::General {
204 integer: value
205 + leftover.trunc() as u128
206 + leftover_fraction.trunc() as u128,
207 fraction: Some(leftover_fraction.fract()),
208 unit: Some(unit),
209 }
210 }
211
212 #[cfg(not(feature = "std"))]
213 {
214 let mut leftover = leftover;
215
216 let mut integer = value;
218 while leftover >= 1.0 {
219 leftover -= 1.0;
220 integer += 1;
221 }
222
223 let mut fraction = leftover + fraction.unwrap_or(0.0);
224 while fraction >= 1.0 {
225 fraction -= 1.0;
226 integer += 1;
227 }
228
229 FormatType::General {
230 integer,
231 fraction: Some(fraction),
232 unit: Some(unit),
233 }
234 }
235 }
236 None => {
237 let mut exponent: usize = 0;
238 let mut value = integer;
239 let target_len = 10usize.pow((DECIMALS as u32).min(f64::DIGITS) + 1);
241
242 loop {
243 value /= 10;
244 exponent += 1;
245
246 if value < target_len as _ {
247 break;
248 }
249 }
250
251 {
253 let mut value = value;
254 loop {
255 value /= 10;
256 exponent += 1;
257
258 if value < 10 {
259 break;
260 }
261 }
262 }
263
264 FormatType::Scientific {
265 coefficient: value as f64 / (target_len / 10) as f64,
266 exponent,
267 }
268 }
269 }
270 .formatter_result(self)
271 }
272
273 #[cfg(feature = "std")]
274 pub fn format_float(&self, number: f64) -> FormatResult<DECIMALS> {
283 let base = BASE as f64;
284 if number < base {
285 return FormatType::Float { number, unit: None }.formatter_result(self);
286 }
287
288 let mut index: usize = 0;
289 let mut value = number;
290
291 while value >= base {
292 value /= base;
293 index += 1;
294 }
295
296 match self.units.get(index - 1) {
297 Some(&unit) => {
298 let leftover = {
299 let leftover_exp = base.powi(index as i32);
300 (number - value * leftover_exp) / leftover_exp
301 };
302
303 FormatType::Float {
304 number: value + leftover,
305 unit: Some(unit),
306 }
307 }
308 None => {
309 let value = number.log10();
310
311 FormatType::Scientific {
312 coefficient: 10.0f64.powf(value.fract()),
313 exponent: value.trunc() as _,
314 }
315 }
316 }
317 .formatter_result(self)
318 }
319}
320
321#[allow(private_bounds)]
322pub trait NumberT: number_sealed::NumberT {}
337
338impl<T: number_sealed::NumberT> NumberT for T {}
339
340mod number_sealed {
341 pub(super) trait NumberT: Copy {
342 fn is_negative(self) -> bool;
343
344 fn integer(self) -> Option<u128>;
345
346 #[inline]
347 fn fraction(self) -> Option<f64> {
348 None
349 }
350 }
351
352 macro_rules! impl_number_trait {
353 (UINT: $($ty:ident),+) => {
354 $(
355 impl NumberT for $ty {
356 #[inline]
357 fn is_negative(self) -> bool {
358 false
359 }
360
361 #[inline]
362 fn integer(self) -> Option<u128> {
363 Some(self as _)
364 }
365 }
366 )+
367 };
368 (INT: $($ty:ident),+) => {
369 $(
370 impl NumberT for $ty {
371 #[inline]
372 fn is_negative(self) -> bool {
373 self < 0
374 }
375
376 #[inline]
377 fn integer(self) -> Option<u128> {
378 Some(self.unsigned_abs() as _)
379 }
380 }
381 )+
382 };
383 }
384
385 impl_number_trait!(UINT: u8, u16, u32, u64, usize, u128);
386 impl_number_trait!(INT: i8, i16, i32, i64, isize, i128);
387
388 #[cfg(feature = "std")]
389 impl NumberT for f32 {
390 #[inline]
391 fn is_negative(self) -> bool {
392 self < 0.0
393 }
394
395 #[inline]
396 fn integer(self) -> Option<u128> {
397 Some(self.trunc() as _)
398 }
399
400 #[inline]
401 fn fraction(self) -> Option<f64> {
402 Some(self.fract() as _)
403 }
404 }
405
406 #[cfg(feature = "std")]
407 impl NumberT for f64 {
408 #[inline]
409 fn is_negative(self) -> bool {
410 self < 0.0
411 }
412
413 #[inline]
414 fn integer(self) -> Option<u128> {
415 if self < 3.40282366920938e+38 {
416 Some(self.trunc() as _)
417 } else {
418 None
419 }
420 }
421
422 #[inline]
423 fn fraction(self) -> Option<f64> {
424 if self < 3.40282366920938e+38 {
425 Some(self.fract() as _)
426 } else {
427 Some(self as _)
428 }
429 }
430 }
431}
432
433#[derive(Debug, Clone, Copy)]
434pub struct FormatResult<const DECIMALS: usize> {
438 result: FormatType<DECIMALS>,
439 result_is_negative: bool,
440 separator: &'static str,
441 custom_unit: Option<&'static str>,
442}
443
444impl<const DECIMALS: usize> fmt::Display for FormatResult<DECIMALS> {
445 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
446 if self.result_is_negative {
447 write!(f, "-")?;
448 }
449
450 match self.result {
451 FormatType::General {
452 integer,
453 fraction: _fraction,
454 unit,
455 } => {
456 write!(f, "{integer}")?;
457
458 #[cfg(feature = "std")]
460 {
461 let full_fraction = _fraction.map(|fraction| format!("{fraction:.15}"));
462 let fraction = full_fraction
463 .as_ref()
464 .map(|full_fraction| {
465 let digits = (f64::DIGITS as usize).min(DECIMALS);
466 &full_fraction[1..digits + 2]
467 })
468 .unwrap_or_default();
469 write!(f, "{fraction}")?;
470 };
471
472 if unit.is_some() {
473 write!(f, "{}{}", self.separator, unit.unwrap())?;
474 }
475
476 if self.custom_unit.is_some() {
477 if unit.is_none() {
478 write!(f, "{}", self.separator)?;
479 }
480
481 write!(f, "{}", self.custom_unit.unwrap())?;
482 };
483 }
484 #[cfg(feature = "std")]
485 FormatType::Float { number, unit } => {
486 let number = format!("{number:.15}");
488 let digits = (f64::DIGITS as usize).min(DECIMALS);
489 let number = &number[1..digits + 2];
490 write!(f, "{number}")?;
491
492 if unit.is_some() {
493 write!(f, "{}{}", self.separator, unit.unwrap())?;
494 }
495
496 if self.custom_unit.is_some() {
497 if unit.is_none() {
498 write!(f, "{}", self.separator)?;
499 }
500
501 write!(f, "{}", self.custom_unit.unwrap())?;
502 };
503 }
504 FormatType::Scientific {
505 coefficient,
506 exponent,
507 } => {
508 #[cfg(not(feature = "std"))]
509 write!(f, "{coefficient}")?;
510
511 #[cfg(feature = "std")]
512 {
513 let coefficient = format!("{coefficient:.15}");
515 let digits = (f64::DIGITS as usize).min(DECIMALS);
516 let coefficient = &coefficient[..digits + 2];
517 write!(f, "{coefficient}")?;
518 }
519
520 write!(f, "e{exponent}")?;
521
522 if self.custom_unit.is_some() {
523 write!(f, "{}{}", self.separator, self.custom_unit.unwrap())?;
524 };
525 }
526 };
527
528 Ok(())
529 }
530}
531
532impl<const DECIMALS: usize> FormatResult<DECIMALS> {
533 #[inline]
534 const fn set_result_is_negative(self, result_is_negative: bool) -> Self {
535 Self {
536 result_is_negative,
537 ..self
538 }
539 }
540}
541
542#[derive(Debug, Clone, Copy)]
543enum FormatType<const DECIMALS: usize> {
544 General {
546 integer: u128,
548
549 fraction: Option<f64>,
551
552 unit: Option<&'static str>,
554 },
555
556 #[cfg(feature = "std")]
557 Float {
559 number: f64,
561
562 unit: Option<&'static str>,
564 },
565
566 Scientific {
568 coefficient: f64,
570 exponent: usize,
572 },
573}
574
575impl<const DECIMALS: usize> FormatType<DECIMALS> {
576 #[inline]
577 const fn formatter_result<const BASE: usize>(
578 self,
579 formatter: &Formatter<BASE, DECIMALS>,
580 ) -> FormatResult<DECIMALS> {
581 FormatResult {
582 result: self,
583 result_is_negative: false,
584 separator: formatter.separator,
585 custom_unit: formatter.custom_unit,
586 }
587 }
588}