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