soroban_cli/
fixed_point.rs1use std::fmt::{self, Display};
2
3#[derive(Clone, Copy, Debug)]
12pub struct FixedPoint {
13 value: i128,
14 decimals: u32,
15}
16
17impl FixedPoint {
18 #[must_use]
19 pub fn new(value: i128, decimals: u32) -> Self {
20 Self { value, decimals }
21 }
22}
23
24impl Display for FixedPoint {
25 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
28 if self.decimals == 0 {
29 return write!(f, "{}", self.value);
30 }
31
32 let decimals = self.decimals as usize;
33 let digits = self.value.unsigned_abs().to_string();
36 let digits = if digits.len() <= decimals {
39 format!("{digits:0>width$}", width = decimals + 1)
40 } else {
41 digits
42 };
43
44 let point = digits.len() - decimals;
45 let integer_part = &digits[..point];
46 let fractional_part = digits[point..].trim_end_matches('0');
47 let sign = if self.value < 0 { "-" } else { "" };
48
49 if fractional_part.is_empty() {
50 write!(f, "{sign}{integer_part}")
51 } else {
52 write!(f, "{sign}{integer_part}.{fractional_part}")
53 }
54 }
55}
56
57#[cfg(test)]
58mod tests {
59 use super::*;
60
61 fn fmt(value: i128, decimals: u32) -> String {
62 FixedPoint::new(value, decimals).to_string()
63 }
64
65 #[test]
66 #[allow(clippy::unreadable_literal)]
67 fn formats_typical_values() {
68 assert_eq!(fmt(0, 7), "0");
69 assert_eq!(fmt(1234567, 7), "0.1234567");
70 assert_eq!(fmt(12345000, 7), "1.2345");
71 assert_eq!(fmt(10000000, 7), "1");
72 assert_eq!(fmt(123456789012345, 7), "12345678.9012345");
73 assert_eq!(fmt(1, 7), "0.0000001");
74 assert_eq!(fmt(12345, 0), "12345");
75 assert_eq!(fmt(12345, 1), "1234.5");
76 }
77
78 #[test]
79 #[allow(clippy::unreadable_literal)]
80 fn formats_negative_values() {
81 assert_eq!(fmt(-1234567, 7), "-0.1234567");
82 assert_eq!(fmt(-12345000, 7), "-1.2345");
83 assert_eq!(fmt(-10000000, 7), "-1");
84 assert_eq!(fmt(-5, 0), "-5");
85 }
86
87 #[test]
88 fn handles_i128_bounds_without_overflow() {
89 assert_eq!(
90 fmt(i128::MIN, 18),
91 "-170141183460469231731.687303715884105728"
92 );
93 assert_eq!(
94 fmt(i128::MAX, 18),
95 "170141183460469231731.687303715884105727"
96 );
97 }
98
99 #[test]
100 fn large_decimals_do_not_overflow() {
101 assert_eq!(fmt(1, 39), "0.000000000000000000000000000000000000001");
104 assert_eq!(fmt(123, 255).chars().take(2).collect::<String>(), "0.");
105 }
106}