1pub fn span_index_for_breakpoints(
4 breakpoints: &[f64],
5 value: f64,
6 label: &str,
7) -> Result<usize, String> {
8 if !value.is_finite() {
9 return Err(format!("{label} requires finite value, got {value}"));
10 }
11 if breakpoints.len() < 2 {
12 return Err(format!("{label} requires at least two breakpoints"));
13 }
14 let last_idx = breakpoints.len() - 1;
15 if value <= breakpoints[0] {
16 return Ok(0);
17 }
18 if value >= breakpoints[last_idx] {
19 return Ok(last_idx - 1);
20 }
21 let insertion_idx = breakpoints.partition_point(|point| *point <= value);
22 Ok((insertion_idx - 1).min(last_idx - 1))
23}
24
25#[cfg(test)]
26mod tests {
27 use super::span_index_for_breakpoints;
28
29 #[test]
36 fn internal_breakpoints_use_right_hand_span() {
37 let breakpoints = [-1.5, -0.9, 0.4, 2.0];
38 assert_eq!(
39 span_index_for_breakpoints(&breakpoints, -1.5, "test span lookup").unwrap(),
40 0
41 );
42 assert_eq!(
43 span_index_for_breakpoints(&breakpoints, -0.9, "test span lookup").unwrap(),
44 1
45 );
46 assert_eq!(
47 span_index_for_breakpoints(&breakpoints, 0.4, "test span lookup").unwrap(),
48 2
49 );
50 assert_eq!(
51 span_index_for_breakpoints(&breakpoints, 2.0, "test span lookup").unwrap(),
52 2
53 );
54 }
55}