reifydb_value/util/
float_format.rs1pub fn format_f64(v: f64) -> String {
5 if !v.is_finite() {
6 return v.to_string();
7 }
8 if v == 0.0 {
9 return "0".to_string();
10 }
11 let s = v.to_string();
12
13 if !s.contains('.') || count_significant_digits(&s) <= 15 {
14 return s;
15 }
16 let magnitude = v.abs().log10().floor() as i32;
17 let decimal_places = (14 - magnitude).max(0) as usize;
18 let s = format!("{:.prec$}", v, prec = decimal_places);
19 if s.contains('.') {
20 s.trim_end_matches('0').trim_end_matches('.').to_string()
21 } else {
22 s
23 }
24}
25
26pub fn format_f32(v: f32) -> String {
27 if !v.is_finite() {
28 return v.to_string();
29 }
30 if v == 0.0 {
31 return "0".to_string();
32 }
33 let s = v.to_string();
34
35 if !s.contains('.') || count_significant_digits(&s) <= 7 {
36 return s;
37 }
38 let magnitude = (v.abs() as f64).log10().floor() as i32;
39 let decimal_places = (6 - magnitude).max(0) as usize;
40 let s = format!("{:.prec$}", v, prec = decimal_places);
41 if s.contains('.') {
42 s.trim_end_matches('0').trim_end_matches('.').to_string()
43 } else {
44 s
45 }
46}
47
48fn count_significant_digits(s: &str) -> usize {
49 let s = s.strip_prefix('-').unwrap_or(s);
50
51 let s = if let Some(pos) = s.find(['e', 'E']) {
52 &s[..pos]
53 } else {
54 s
55 };
56 let s = s.trim_start_matches('0');
57 let s = s.strip_prefix('.').map(|rest| rest.trim_start_matches('0')).unwrap_or(s);
58 s.chars().filter(|c| c.is_ascii_digit()).count()
59}
60
61#[cfg(test)]
62mod tests {
63 use std::{f32, f64};
64
65 use super::*;
66
67 #[test]
68 fn test_format_f64_special_values() {
69 assert_eq!(format_f64(<f64>::INFINITY), "inf");
70 assert_eq!(format_f64(<f64>::NEG_INFINITY), "-inf");
71 assert_eq!(format_f64(<f64>::NAN), "NaN");
72 assert_eq!(format_f64(0.0), "0");
73 assert_eq!(format_f64(-0.0), "0");
74 }
75
76 #[test]
77 #[allow(clippy::approx_constant)]
78 fn test_format_f64_small_values() {
79 assert_eq!(format_f64(1.0), "1");
80 assert_eq!(format_f64(-1.0), "-1");
81 assert_eq!(format_f64(3.14), "3.14");
82 assert_eq!(format_f64(0.1), "0.1");
83 assert_eq!(format_f64(42.0), "42");
84 }
85
86 #[test]
87 fn test_format_f64_15_sig_digits() {
88 let e = f64::consts::E;
91 let s = format_f64(e);
92 assert!(count_significant_digits(&s) <= 15, "got: {}", s);
93 }
94
95 #[test]
96 fn test_format_f64_preserves_short_values() {
97 assert_eq!(format_f64(1.5), "1.5");
99 assert_eq!(format_f64(100.0), "100");
100 assert_eq!(format_f64(0.001), "0.001");
101 assert_eq!(format_f64(123456789012345.0), "123456789012345");
102 }
103
104 #[test]
105 fn test_format_f64_large_values() {
106 assert_eq!(format_f64(1e15), "1000000000000000");
108 let v = 1.234567890123456e10;
109 let s = format_f64(v);
110 assert!(count_significant_digits(&s) <= 15, "got: {}", s);
111 }
112
113 #[test]
114 fn test_format_f64_very_small_values() {
115 let v = 1e-10;
116 let s = format_f64(v);
117 assert!(count_significant_digits(&s) <= 15, "got: {}", s);
118 }
119
120 #[test]
121 fn test_format_f64_negative() {
122 let v = -f64::consts::PI;
123 let s = format_f64(v);
124 assert!(s.starts_with('-'));
125 assert!(count_significant_digits(&s) <= 15, "got: {}", s);
126 }
127
128 #[test]
129 fn test_format_f32_special_values() {
130 assert_eq!(format_f32(<f32>::INFINITY), "inf");
131 assert_eq!(format_f32(<f32>::NEG_INFINITY), "-inf");
132 assert_eq!(format_f32(<f32>::NAN), "NaN");
133 assert_eq!(format_f32(0.0f32), "0");
134 assert_eq!(format_f32(-0.0f32), "0");
135 }
136
137 #[test]
138 #[allow(clippy::approx_constant)]
139 fn test_format_f32_small_values() {
140 assert_eq!(format_f32(1.0f32), "1");
141 assert_eq!(format_f32(3.14f32), "3.14");
142 assert_eq!(format_f32(0.1f32), "0.1");
143 }
144
145 #[test]
146 fn test_format_f32_7_sig_digits() {
147 let v = f32::consts::E;
148 let s = format_f32(v);
149 assert!(count_significant_digits(&s) <= 7, "got: {}", s);
150 }
151
152 #[test]
153 fn test_count_significant_digits() {
154 assert_eq!(count_significant_digits("0"), 0);
155 assert_eq!(count_significant_digits("1"), 1);
156 assert_eq!(count_significant_digits("3.14"), 3);
157 assert_eq!(count_significant_digits("0.001"), 1);
158 assert_eq!(count_significant_digits("100"), 3);
159 assert_eq!(count_significant_digits("-3.14"), 3);
160 assert_eq!(count_significant_digits("2.718281828459045"), 16);
161 assert_eq!(count_significant_digits("2.7182818284590455"), 17);
162 }
163
164 #[test]
165 fn test_format_f64_cross_platform_equivalence() {
166 let values = [
168 f64::consts::E,
169 f64::consts::PI,
170 f64::consts::LN_2,
171 f64::consts::SQRT_2,
172 1.0f64 / 3.0,
173 2.0f64.sqrt(),
174 ];
175 for v in values {
176 let s = format_f64(v);
177 assert!(
178 count_significant_digits(&s) <= 15,
179 "value {} formatted as {} has {} sig digits",
180 v,
181 s,
182 count_significant_digits(&s)
183 );
184 }
185 }
186
187 #[test]
188 fn test_no_trailing_zeros() {
189 let v = 1.2f64;
191 let s = format_f64(v);
192 assert!(!s.ends_with('0') || s == "0", "got: {}", s);
193 }
194}