1pub fn round_with_precision(value: f64, precision: i16) -> f64 {
27 if value.is_infinite()
37 || value.is_nan()
38 || precision >= 0 && value.abs() >= (1_i64 << f64::MANTISSA_DIGITS) as f64
39 || precision >= f64::DIGITS as i16
40 {
41 return value;
42 }
43 if precision < -(f64::MAX_10_EXP as i16) {
45 return value * 0.0;
47 }
48 if precision > 0 {
49 let offset = 10_f64.powi(precision.into());
50 assert!((value * offset).is_finite(), "{value} * {offset} is not finite!");
51 (value * offset).round() / offset
52 } else {
53 let offset = 10_f64.powi((-precision).into());
57 (value / offset).round() * offset
58 }
59}
60
61pub fn round_int_with_precision(value: i64, precision: i16) -> Option<i64> {
83 if precision >= 0 {
84 return Some(value);
85 }
86
87 let digits = -precision as u32;
88 let Some(ten_to_digits) = 10i64.checked_pow(digits - 1) else {
89 return Some(0);
91 };
92
93 let truncated = value / ten_to_digits;
98 if truncated == 0 {
99 return Some(0);
100 }
101
102 let rounded = if (truncated % 10).abs() >= 5 {
103 truncated.checked_add(truncated.signum() * (10 - (truncated % 10).abs()))?
108 } else {
109 truncated - (truncated % 10)
111 };
112
113 rounded.checked_mul(ten_to_digits)
117}
118
119#[cfg(test)]
120mod tests {
121 use super::{round_int_with_precision as rip, round_with_precision as rp};
122
123 #[test]
124 fn test_round_with_precision_0() {
125 let round = |value| rp(value, 0);
126 assert_eq!(round(0.0), 0.0);
127 assert_eq!(round(-0.0), -0.0);
128 assert_eq!(round(0.4), 0.0);
129 assert_eq!(round(-0.4), -0.0);
130 assert_eq!(round(0.56453), 1.0);
131 assert_eq!(round(-0.56453), -1.0);
132 }
133
134 #[test]
135 fn test_round_with_precision_1() {
136 let round = |value| rp(value, 1);
137 assert_eq!(round(0.0), 0.0);
138 assert_eq!(round(-0.0), -0.0);
139 assert_eq!(round(0.4), 0.4);
140 assert_eq!(round(-0.4), -0.4);
141 assert_eq!(round(0.44), 0.4);
142 assert_eq!(round(-0.44), -0.4);
143 assert_eq!(round(0.56453), 0.6);
144 assert_eq!(round(-0.56453), -0.6);
145 assert_eq!(round(0.96453), 1.0);
146 assert_eq!(round(-0.96453), -1.0);
147 }
148
149 #[test]
150 fn test_round_with_precision_2() {
151 let round = |value| rp(value, 2);
152 assert_eq!(round(0.0), 0.0);
153 assert_eq!(round(-0.0), -0.0);
154 assert_eq!(round(0.4), 0.4);
155 assert_eq!(round(-0.4), -0.4);
156 assert_eq!(round(0.44), 0.44);
157 assert_eq!(round(-0.44), -0.44);
158 assert_eq!(round(0.444), 0.44);
159 assert_eq!(round(-0.444), -0.44);
160 assert_eq!(round(0.56553), 0.57);
161 assert_eq!(round(-0.56553), -0.57);
162 assert_eq!(round(0.99553), 1.0);
163 assert_eq!(round(-0.99553), -1.0);
164 }
165
166 #[test]
167 fn test_round_with_precision_negative_1() {
168 let round = |value| rp(value, -1);
169 assert_eq!(round(0.0), 0.0);
170 assert_eq!(round(-0.0), -0.0);
171 assert_eq!(round(0.4), 0.0);
172 assert_eq!(round(-0.4), -0.0);
173 assert_eq!(round(1234.5), 1230.0);
174 assert_eq!(round(-1234.5), -1230.0);
175 assert_eq!(round(1245.232), 1250.0);
176 assert_eq!(round(-1245.232), -1250.0);
177 }
178
179 #[test]
180 fn test_round_with_precision_negative_2() {
181 let round = |value| rp(value, -2);
182 assert_eq!(round(0.0), 0.0);
183 assert_eq!(round(-0.0), -0.0);
184 assert_eq!(round(0.4), 0.0);
185 assert_eq!(round(-0.4), -0.0);
186 assert_eq!(round(1243.232), 1200.0);
187 assert_eq!(round(-1243.232), -1200.0);
188 assert_eq!(round(1253.232), 1300.0);
189 assert_eq!(round(-1253.232), -1300.0);
190 }
191
192 #[test]
193 fn test_round_with_precision_fuzzy() {
194 let max_int = (1_i64 << f64::MANTISSA_DIGITS) as f64;
195 let max_digits = f64::DIGITS as i16;
196
197 assert_eq!(rp(f64::INFINITY, 0), f64::INFINITY);
199 assert_eq!(rp(f64::NEG_INFINITY, 0), f64::NEG_INFINITY);
200 assert!(rp(f64::NAN, 0).is_nan());
201
202 assert_eq!(rp(max_int, 0), max_int);
204 assert_eq!(rp(0.123456, max_digits), 0.123456);
205 assert_eq!(rp(max_int, max_digits), max_int);
206
207 assert_eq!(rp(max_int - 1.0, 0), max_int - 1.0);
209 assert_eq!(rp(0.123456, max_digits - 1), 0.123456);
210 assert_eq!(rp(max_int - 1.0, max_digits), max_int - 1.0);
211 assert_eq!(rp(max_int, max_digits - 1), max_int);
212 assert_eq!(rp(max_int - 1.0, max_digits - 1), max_int - 1.0);
213 }
214
215 #[test]
216 fn test_round_with_precision_fuzzy_negative() {
217 let exp10 = |exponent: i16| 10_f64.powi(exponent.into());
218 let max_digits = f64::MAX_10_EXP as i16;
219 let max_up = max_digits + 1;
220 let max_down = max_digits - 1;
221
222 assert_eq!(rp(f64::INFINITY, -1), f64::INFINITY);
224 assert_eq!(rp(f64::NEG_INFINITY, -1), f64::NEG_INFINITY);
225 assert!(rp(f64::NAN, -1).is_nan());
226
227 assert_eq!(rp(f64::MAX, -max_digits), f64::INFINITY);
229 assert_eq!(rp(f64::MIN, -max_digits), f64::NEG_INFINITY);
230 assert_eq!(rp(1.66 * exp10(max_digits), -max_digits), f64::INFINITY);
231 assert_eq!(rp(-1.66 * exp10(max_digits), -max_digits), f64::NEG_INFINITY);
232 assert_eq!(rp(1.66 * exp10(max_down), -max_digits), 0.0);
233 assert_eq!(rp(-1.66 * exp10(max_down), -max_digits), -0.0);
234 assert_eq!(rp(1234.5678, -max_digits), 0.0);
235 assert_eq!(rp(-1234.5678, -max_digits), -0.0);
236
237 assert_eq!(rp(f64::MAX, -max_up), 0.0);
239 assert_eq!(rp(f64::MIN, -max_up), -0.0);
240 assert_eq!(rp(1.66 * exp10(max_digits), -max_up), 0.0);
241 assert_eq!(rp(-1.66 * exp10(max_digits), -max_up), -0.0);
242 assert_eq!(rp(1.66 * exp10(max_down), -max_up), 0.0);
243 assert_eq!(rp(-1.66 * exp10(max_down), -max_up), -0.0);
244 assert_eq!(rp(1234.5678, -max_up), 0.0);
245 assert_eq!(rp(-1234.5678, -max_up), -0.0);
246
247 assert_eq!(rp(f64::MAX, -max_down), f64::INFINITY);
249 assert_eq!(rp(f64::MIN, -max_down), f64::NEG_INFINITY);
250 assert_eq!(rp(1.66 * exp10(max_down), -max_down), 2.0 * exp10(max_down));
251 assert_eq!(rp(-1.66 * exp10(max_down), -max_down), -2.0 * exp10(max_down));
252 assert_eq!(rp(1234.5678, -max_down), 0.0);
253 assert_eq!(rp(-1234.5678, -max_down), -0.0);
254
255 assert_eq!(
258 (rp(1.66 * exp10(max_digits), -max_down) / exp10(max_down)).floor(),
259 17.0,
260 );
261 assert_eq!(
262 (rp(-1.66 * exp10(max_digits), -max_down) / exp10(max_down)).floor(),
263 -17.0,
264 );
265 }
266
267 #[test]
268 fn test_round_int_with_precision_positive() {
269 assert_eq!(rip(0, 0), Some(0));
270 assert_eq!(rip(10, 0), Some(10));
271 assert_eq!(rip(23, 235), Some(23));
272 assert_eq!(rip(i64::MAX, 235), Some(i64::MAX));
273 }
274
275 #[test]
276 fn test_round_int_with_precision_negative_1() {
277 let round = |value| rip(value, -1);
278 assert_eq!(round(0), Some(0));
279 assert_eq!(round(3), Some(0));
280 assert_eq!(round(5), Some(10));
281 assert_eq!(round(13), Some(10));
282 assert_eq!(round(1234), Some(1230));
283 assert_eq!(round(-1234), Some(-1230));
284 assert_eq!(round(1245), Some(1250));
285 assert_eq!(round(-1245), Some(-1250));
286 assert_eq!(round(i64::MAX), None);
287 assert_eq!(round(i64::MIN), None);
288 }
289
290 #[test]
291 fn test_round_int_with_precision_negative_2() {
292 let round = |value| rip(value, -2);
293 assert_eq!(round(0), Some(0));
294 assert_eq!(round(3), Some(0));
295 assert_eq!(round(5), Some(0));
296 assert_eq!(round(13), Some(0));
297 assert_eq!(round(1245), Some(1200));
298 assert_eq!(round(-1245), Some(-1200));
299 assert_eq!(round(1253), Some(1300));
300 assert_eq!(round(-1253), Some(-1300));
301 assert_eq!(round(i64::MAX), Some(i64::MAX - 7));
302 assert_eq!(round(i64::MIN), Some(i64::MIN + 8));
303 }
304}