1use crate::fast_midpoint;
4
5const NUM_DECIMALS: usize = 15;
6
7pub fn best_in_range_f64(min: f64, max: f64) -> f64 {
13 if min.is_nan() {
15 return max;
16 }
17 if max.is_nan() {
18 return min;
19 }
20
21 if max < min {
22 return best_in_range_f64(max, min);
23 }
24 if min == max {
25 return min;
26 }
27 if min <= 0.0 && 0.0 <= max {
28 return 0.0; }
30 if min < 0.0 {
31 return -best_in_range_f64(-max, -min);
32 }
33
34 if !max.is_finite() {
36 return min;
37 }
38 debug_assert!(
39 min.is_finite() && max.is_finite(),
40 "min: {min:?}, max: {max:?}"
41 );
42
43 let min_exponent = min.log10();
44 let max_exponent = max.log10();
45
46 if min_exponent.floor() != max_exponent.floor() {
47 let exponent = fast_midpoint(min_exponent, max_exponent);
49 return 10.0_f64.powi(exponent.round() as i32);
50 }
51
52 if is_integer(min_exponent) {
53 return 10.0_f64.powf(min_exponent);
54 }
55 if is_integer(max_exponent) {
56 return 10.0_f64.powf(max_exponent);
57 }
58
59 let exp_factor = 10.0_f64.powi(max_exponent.floor() as i32);
60
61 let min_str = to_decimal_string(min / exp_factor);
62 let max_str = to_decimal_string(max / exp_factor);
63
64 let mut ret_str = [0; NUM_DECIMALS];
65
66 let mut i = 0;
68 while i < NUM_DECIMALS && max_str[i] == min_str[i] {
69 ret_str[i] = max_str[i];
70 i += 1;
71 }
72
73 if i < NUM_DECIMALS {
74 ret_str[i] = simplest_digit_closed_range(min_str[i] + 1, max_str[i]);
77 }
78
79 from_decimal_string(&ret_str) * exp_factor
80}
81
82fn is_integer(f: f64) -> bool {
83 f.round() == f
84}
85
86fn to_decimal_string(v: f64) -> [i32; NUM_DECIMALS] {
87 debug_assert!(v < 10.0, "{v:?}");
88 let mut digits = [0; NUM_DECIMALS];
89 let mut v = v.abs();
90 for r in &mut digits {
91 let digit = v.floor();
92 *r = digit as i32;
93 v -= digit;
94 v *= 10.0;
95 }
96 digits
97}
98
99fn from_decimal_string(s: &[i32]) -> f64 {
100 let mut ret: f64 = 0.0;
101 for (i, &digit) in s.iter().enumerate() {
102 ret += (digit as f64) * 10.0_f64.powi(-(i as i32));
103 }
104 ret
105}
106
107fn simplest_digit_closed_range(min: i32, max: i32) -> i32 {
109 debug_assert!(
110 1 <= min && min <= max && max <= 9,
111 "min should be in [1, 9], but was {min:?} and max should be in [min, 9], but was {max:?}"
112 );
113 if min <= 5 && 5 <= max {
114 5
115 } else {
116 min.midpoint(max)
117 }
118}
119
120#[expect(clippy::approx_constant)]
121#[test]
122fn test_aim() {
123 assert_eq!(best_in_range_f64(-0.2, 0.0), 0.0, "Prefer zero");
124 assert_eq!(best_in_range_f64(-10_004.23, 3.14), 0.0, "Prefer zero");
125 assert_eq!(best_in_range_f64(-0.2, 100.0), 0.0, "Prefer zero");
126 assert_eq!(best_in_range_f64(0.2, 0.0), 0.0, "Prefer zero");
127 assert_eq!(best_in_range_f64(7.8, 17.8), 10.0);
128 assert_eq!(best_in_range_f64(99.0, 300.0), 100.0);
129 assert_eq!(best_in_range_f64(-99.0, -300.0), -100.0);
130 assert_eq!(best_in_range_f64(0.4, 0.9), 0.5, "Prefer ending on 5");
131 assert_eq!(best_in_range_f64(14.1, 19.99), 15.0, "Prefer ending on 5");
132 assert_eq!(best_in_range_f64(12.3, 65.9), 50.0, "Prefer leading 5");
133 assert_eq!(best_in_range_f64(493.0, 879.0), 500.0, "Prefer leading 5");
134 assert_eq!(best_in_range_f64(0.37, 0.48), 0.40);
135 assert_eq!(best_in_range_f64(7.5, 16.3), 10.0);
138 assert_eq!(best_in_range_f64(7.5, 76.3), 10.0);
139 assert_eq!(best_in_range_f64(7.5, 763.3), 100.0);
140 assert_eq!(best_in_range_f64(7.5, 1_345.0), 100.0);
141 assert_eq!(best_in_range_f64(7.5, 123_456.0), 1000.0, "Geometric mean");
142 assert_eq!(best_in_range_f64(9.9999, 99.999), 10.0);
143 assert_eq!(best_in_range_f64(10.000, 99.999), 10.0);
144 assert_eq!(best_in_range_f64(10.001, 99.999), 50.0);
145 assert_eq!(best_in_range_f64(10.001, 100.000), 100.0);
146 assert_eq!(best_in_range_f64(99.999, 100.000), 100.0);
147 assert_eq!(best_in_range_f64(10.001, 100.001), 100.0);
148
149 const NAN: f64 = f64::NAN;
150 const INFINITY: f64 = f64::INFINITY;
151 const NEG_INFINITY: f64 = f64::NEG_INFINITY;
152 assert!(best_in_range_f64(NAN, NAN).is_nan());
153 assert_eq!(best_in_range_f64(NAN, 1.2), 1.2);
154 assert_eq!(best_in_range_f64(NAN, INFINITY), INFINITY);
155 assert_eq!(best_in_range_f64(1.2, NAN), 1.2);
156 assert_eq!(best_in_range_f64(1.2, INFINITY), 1.2);
157 assert_eq!(best_in_range_f64(INFINITY, 1.2), 1.2);
158 assert_eq!(best_in_range_f64(NEG_INFINITY, 1.2), 0.0);
159 assert_eq!(best_in_range_f64(NEG_INFINITY, -2.7), -2.7);
160 assert_eq!(best_in_range_f64(INFINITY, INFINITY), INFINITY);
161 assert_eq!(best_in_range_f64(NEG_INFINITY, NEG_INFINITY), NEG_INFINITY);
162 assert_eq!(best_in_range_f64(NEG_INFINITY, INFINITY), 0.0);
163 assert_eq!(best_in_range_f64(INFINITY, NEG_INFINITY), 0.0);
164}