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