1use crate::fast_midpoint;
4
5const NUM_DECIMALS: usize = 16;
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 debug_assert!(0.0 < min && min < max, "Logic bug");
35
36 if !max.is_finite() {
38 return min;
39 }
40 debug_assert!(
41 min.is_finite() && max.is_finite(),
42 "min: {min:?}, max: {max:?}"
43 );
44
45 let min_exponent = min.log10();
46 let max_exponent = max.log10();
47
48 if min_exponent.floor() != max_exponent.floor() {
49 let exponent = fast_midpoint(min_exponent, max_exponent);
52 return 10.0_f64.powi(exponent.round() as i32);
53 }
54
55 if is_integer(min_exponent) {
56 return 10.0_f64.powf(min_exponent);
57 }
58 if is_integer(max_exponent) {
59 return 10.0_f64.powf(max_exponent);
60 }
61
62 let scale = NUM_DECIMALS as i32 - max_exponent.floor() as i32 - 1;
65 let scale_factor = 10.0_f64.powi(scale);
66
67 let min_str = to_decimal_string((min * scale_factor).round() as u64);
68 let max_str = to_decimal_string((max * scale_factor).round() as u64);
69
70 let mut ret_str = [0; NUM_DECIMALS];
81
82 for i in 0..NUM_DECIMALS {
83 if min_str[i] == max_str[i] {
84 ret_str[i] = min_str[i];
85 } else {
86 let mut deciding_digit_min = min_str[i];
88 let deciding_digit_max = max_str[i];
89
90 debug_assert!(
91 deciding_digit_min < deciding_digit_max,
92 "Bug in smart aim code"
93 );
94
95 let rest_of_min_is_zeroes = min_str[i + 1..].iter().all(|&c| c == 0);
96
97 if !rest_of_min_is_zeroes {
98 deciding_digit_min += 1;
101 }
102
103 let deciding_digit = if deciding_digit_min == 0 {
104 0
105 } else if deciding_digit_min <= 5 && 5 <= deciding_digit_max {
106 5 } else {
108 deciding_digit_min.midpoint(deciding_digit_max)
109 };
110
111 ret_str[i] = deciding_digit;
112
113 return from_decimal_string(ret_str) as f64 / scale_factor;
114 }
115 }
116
117 min }
119
120fn is_integer(f: f64) -> bool {
121 f.round() == f
122}
123
124fn to_decimal_string(v: u64) -> [u8; NUM_DECIMALS] {
125 let mut ret = [0; NUM_DECIMALS];
126 let mut value = v;
127 for i in (0..NUM_DECIMALS).rev() {
128 ret[i] = (value % 10) as u8;
129 value /= 10;
130 }
131 ret
132}
133
134fn from_decimal_string(s: [u8; NUM_DECIMALS]) -> u64 {
135 let mut value = 0;
136 for &c in &s {
137 debug_assert!(c <= 9, "Bad number");
138 value = value * 10 + c as u64;
139 }
140 value
141}
142
143#[expect(clippy::approx_constant)]
144#[test]
145fn test_aim() {
146 assert_eq!(
147 best_in_range_f64(0.0799999999999996, 0.09999999999999995),
148 0.08,
149 );
150 assert_eq!(best_in_range_f64(-0.2, 0.0), 0.0, "Prefer zero");
151 assert_eq!(best_in_range_f64(-10_004.23, 3.14), 0.0, "Prefer zero");
152 assert_eq!(best_in_range_f64(-0.2, 100.0), 0.0, "Prefer zero");
153 assert_eq!(best_in_range_f64(0.2, 0.0), 0.0, "Prefer zero");
154 assert_eq!(best_in_range_f64(7.8, 17.8), 10.0);
155 assert_eq!(best_in_range_f64(99.0, 300.0), 100.0);
156 assert_eq!(best_in_range_f64(-99.0, -300.0), -100.0);
157 assert_eq!(best_in_range_f64(0.4, 0.9), 0.5, "Prefer ending on 5");
158 assert_eq!(best_in_range_f64(14.1, 19.99), 15.0, "Prefer ending on 5");
159 assert_eq!(best_in_range_f64(12.3, 65.9), 50.0, "Prefer leading 5");
160 assert_eq!(best_in_range_f64(493.0, 879.0), 500.0, "Prefer leading 5");
161 assert_eq!(best_in_range_f64(0.37, 0.48), 0.40);
162 assert_eq!(best_in_range_f64(7.5, 16.3), 10.0);
165 assert_eq!(best_in_range_f64(7.5, 76.3), 10.0);
166 assert_eq!(best_in_range_f64(7.5, 763.3), 100.0);
167 assert_eq!(best_in_range_f64(7.5, 1_345.0), 100.0);
168 assert_eq!(best_in_range_f64(7.5, 123_456.0), 1000.0, "Geometric mean");
169 assert_eq!(best_in_range_f64(9.9999, 99.999), 10.0);
170 assert_eq!(best_in_range_f64(10.000, 99.999), 10.0);
171 assert_eq!(best_in_range_f64(10.001, 99.999), 50.0);
172 assert_eq!(best_in_range_f64(10.001, 100.000), 100.0);
173 assert_eq!(best_in_range_f64(99.999, 100.000), 100.0);
174 assert_eq!(best_in_range_f64(10.001, 100.001), 100.0);
175
176 const NAN: f64 = f64::NAN;
177 const INFINITY: f64 = f64::INFINITY;
178 const NEG_INFINITY: f64 = f64::NEG_INFINITY;
179 assert!(best_in_range_f64(NAN, NAN).is_nan());
180 assert_eq!(best_in_range_f64(NAN, 1.2), 1.2);
181 assert_eq!(best_in_range_f64(NAN, INFINITY), INFINITY);
182 assert_eq!(best_in_range_f64(1.2, NAN), 1.2);
183 assert_eq!(best_in_range_f64(1.2, INFINITY), 1.2);
184 assert_eq!(best_in_range_f64(INFINITY, 1.2), 1.2);
185 assert_eq!(best_in_range_f64(NEG_INFINITY, 1.2), 0.0);
186 assert_eq!(best_in_range_f64(NEG_INFINITY, -2.7), -2.7);
187 assert_eq!(best_in_range_f64(INFINITY, INFINITY), INFINITY);
188 assert_eq!(best_in_range_f64(NEG_INFINITY, NEG_INFINITY), NEG_INFINITY);
189 assert_eq!(best_in_range_f64(NEG_INFINITY, INFINITY), 0.0);
190 assert_eq!(best_in_range_f64(INFINITY, NEG_INFINITY), 0.0);
191
192 #[track_caller]
193 fn test_f64((min, max): (f64, f64), expected: f64) {
194 let aimed = best_in_range_f64(min, max);
195 assert!(
196 aimed == expected,
197 "smart_aim({min} – {max}) => {aimed}, but expected {expected}"
198 );
199 }
200 #[track_caller]
201 fn test_i64((min, max): (i64, i64), expected: i64) {
202 let aimed = best_in_range_f64(min as _, max as _);
203 assert!(
204 aimed == expected as f64,
205 "smart_aim({min} – {max}) => {aimed}, but expected {expected}"
206 );
207 }
208
209 test_i64((99, 300), 100);
210 test_i64((300, 99), 100);
211 test_i64((-99, -300), -100);
212 test_i64((-99, 123), 0); test_i64((4, 9), 5); test_i64((14, 19), 15); test_i64((12, 65), 50); test_i64((493, 879), 500); test_i64((37, 48), 40);
218 test_i64((100, 123), 100);
219 test_i64((101, 1000), 1000);
220 test_i64((999, 1000), 1000);
221 test_i64((123, 500), 500);
222 test_i64((500, 777), 500);
223 test_i64((500, 999), 500);
224 test_i64((12345, 12780), 12500);
225 test_i64((12371, 12376), 12375);
226 test_i64((12371, 12376), 12375);
227
228 test_f64((7.5, 16.3), 10.0);
229 test_f64((7.5, 76.3), 10.0);
230 test_f64((7.5, 763.3), 100.0);
231 test_f64((7.5, 1_345.0), 100.0); test_f64((7.5, 123_456.0), 1_000.0); test_f64((-0.2, 0.0), 0.0); test_f64((-10_004.23, 4.14), 0.0); test_f64((-0.2, 100.0), 0.0); test_f64((0.2, 0.0), 0.0); test_f64((7.8, 17.8), 10.0);
238 test_f64((14.1, 19.1), 15.0); test_f64((12.3, 65.9), 50.0); }