1#![warn(missing_docs)]
2const SUFFIXES: [char; 4] = ['k', 'M', 'B', 'T'];
10
11pub trait PrettyNumber {
13 fn pretty_format(self) -> String;
38}
39
40impl<N: Into<i64>> PrettyNumber for N {
41 fn pretty_format(self) -> String {
42 let number: i64 = self.into();
43
44 if number.abs() < 1000 {
45 number.to_string()
46 } else {
47 let sign: i8 = if number < 0 { -1 } else { 1 };
48 let mut number_as_float = number.abs() as f32;
49 for suffix in SUFFIXES {
50 number_as_float /= 1000f32;
51
52 if number_as_float < 1000f32 {
53 return format!(
54 "{:.*}{suffix}",
55 if (number_as_float - number_as_float.floor()) < 0.1
56 || number_as_float >= 100f32
57 {
58 0
59 } else {
60 1
61 },
62 sign as f32 * number_as_float
63 );
64 }
65 }
66
67 panic!("Number {number} is larger than 1 quadrillion!");
68 }
69 }
70}
71
72#[cfg(test)]
73mod test {
74 use crate::PrettyNumber;
75 use rstest::rstest;
76
77 #[rstest]
78 #[case(7, "7")]
79 #[case(42, "42")]
80 #[case(717, "717")]
81 #[case(-5, "-5")]
82 #[case(-76, "-76")]
83 #[case(-224, "-224")]
84 #[case(1_001, "1k")]
85 #[case(1_624, "1.6k")]
86 #[case(-5_020, "-5k")]
87 #[case(-9_505, "-9.5k")]
88 #[case(19_007, "19k")]
89 #[case(73_444, "73.4k")]
90 #[case(-55_033, "-55k")]
91 #[case(-42_780, "-42.8k")]
92 #[case(469_070, "469k")]
93 #[case(945_661, "946k")]
94 #[case(-223_090, "-223k")]
95 #[case(-671_522, "-672k")]
96 #[case(3_001_500, "3M")]
97 #[case(7_926_400, "7.9M")]
98 #[case(-4_030_115, "-4M")]
99 #[case(-3_333_221, "-3.3M")]
100 #[case(75_032_115, "75M")]
101 #[case(23_333_452, "23.3M")]
102 #[case(-54_012_560, "-54M")]
103 #[case(-11_740_662, "-11.7M")]
104 #[case(555_067_885, "555M")]
105 #[case(352_344_120, "352M")]
106 #[case(-222_000_554, "-222M")]
107 #[case(-434_875_500, "-435M")]
108 #[case(2_004_254_578, "2B")]
109 #[case(7_667_973_223, "7.7B")]
110 #[case(-4_002_154_900, "-4B")]
111 #[case(-6_534_664_725, "-6.5B")]
112 #[case(87_050_671_768, "87B")]
113 #[case(44_444_333_222, "44.4B")]
114 #[case(-32_010_345_093, "-32B")]
115 #[case(-65_420_132_543, "-65.4B")]
116 #[case(899_055_111_032, "899B")]
117 #[case(723_999_324_999, "724B")]
118 #[case(-666_000_142_543, "-666B")]
119 #[case(-400_601_897_231, "-401B")]
120 #[case(5_000_023_667_158, "5T")]
121 #[case(1_222_333_444_555, "1.2T")]
122 #[case(-4_000_354_984_333, "-4T")]
123 #[case(-6_923_000_178_126, "-6.9T")]
124 #[case(66_001_789_809_223, "66T")]
125 #[case(93_723_000_151_300, "93.7T")]
126 #[case(-50_032_745_113_006, "-50T")]
127 #[case(-11_444_653_221_094, "-11.4T")]
128 #[case(343_003_766_091_322, "343T")]
129 #[case(357_455_634_091_722, "357T")]
130 #[case(-567_023_400_999_234, "-567T")]
131 #[case(-871_567_223_222_546, "-872T")]
132 fn pretty_format_test(#[case] input: i64, #[case] expected: &str) {
133 assert_eq!(input.pretty_format().as_str(), expected);
134 }
135
136 #[rstest]
137 #[case(1_000_000_000_000_000)]
138 #[case(-1_000_000_000_000_000)]
139 #[should_panic]
140 fn format_quadrillion_should_panic(#[case] num: i64) {
141 let _ = num.pretty_format();
142 }
143}