1const MAX_START_CANDIDATES: f64 = 10_000.0;
4
5pub fn is_degenerate_range(dmin: f64, dmax: f64) -> bool {
8 let range = (dmax - dmin).abs();
9 range < f64::EPSILON || range <= 1e-10 * dmin.abs().max(dmax.abs())
10}
11
12pub fn stepped_breaks(start: f64, end: f64, step: f64) -> Vec<f64> {
17 const MAX_BREAKS: f64 = 1_000.0;
18 if !(start.is_finite() && end.is_finite() && step.is_finite()) || step <= 0.0 {
19 return vec![];
20 }
21 let n = ((end + step * 0.001 - start) / step).floor();
22 if !(0.0..=MAX_BREAKS).contains(&n) {
23 return vec![];
24 }
25 (0..=n as usize).map(|i| start + i as f64 * step).collect()
26}
27
28pub fn extended_breaks(dmin: f64, dmax: f64, m: usize) -> Vec<f64> {
32 if !(dmin.is_finite() && dmax.is_finite()) || dmax <= dmin || m < 2 {
33 return vec![];
34 }
35 if is_degenerate_range(dmin, dmax) {
39 return vec![];
40 }
41 const Q: [f64; 6] = [1.0, 5.0, 2.0, 2.5, 4.0, 3.0];
44 const W: [f64; 4] = [0.25, 0.2, 0.5, 0.05];
45 let m = m as f64;
46
47 let simplicity = |qi: usize, j: f64, lmin: f64, lmax: f64, step: f64| {
48 let eps = 1e-10;
49 let modulo = lmin.rem_euclid(step);
50 let v = if (modulo < eps || step - modulo < eps) && lmin <= 0.0 && lmax >= 0.0 {
51 1.0
52 } else {
53 0.0
54 };
55 1.0 - qi as f64 / (Q.len() as f64 - 1.0) - j + v
56 };
57 let simplicity_max = |qi: usize, j: f64| 1.0 - qi as f64 / (Q.len() as f64 - 1.0) - j + 1.0;
58 let coverage = |lmin: f64, lmax: f64| {
59 let r = dmax - dmin;
60 1.0 - 0.5 * ((dmax - lmax).powi(2) + (dmin - lmin).powi(2)) / (0.1 * r).powi(2)
61 };
62 let coverage_max = |span: f64| {
63 let r = dmax - dmin;
64 if span > r {
65 let half = (span - r) / 2.0;
66 1.0 - 0.5 * (half * half + half * half) / (0.1 * r).powi(2)
67 } else {
68 1.0
69 }
70 };
71 let density = |k: f64, lmin: f64, lmax: f64| {
72 let r = (k - 1.0) / (lmax - lmin);
73 let rt = (m - 1.0) / (lmax.max(dmax) - lmin.min(dmin));
74 2.0 - (r / rt).max(rt / r)
75 };
76 let density_max = |k: f64| {
77 if k >= m {
78 2.0 - (k - 1.0) / (m - 1.0)
79 } else {
80 1.0
81 }
82 };
83
84 let mut best_score = -2.0;
85 let mut best = (dmin, dmax, (dmax - dmin) / (m - 1.0));
86
87 for j_i in 1..=4u32 {
88 let j = j_i as f64;
89 for (qi, &q) in Q.iter().enumerate() {
90 let sm = simplicity_max(qi, j);
91 if W[0] * sm + W[1] + W[2] + W[3] < best_score {
92 break;
93 }
94 for k_i in 2..=(2.0 * m + 4.0) as usize {
95 let k = k_i as f64;
96 let dm = density_max(k);
97 if W[0] * sm + W[1] + W[2] * dm + W[3] < best_score {
98 break;
99 }
100 let delta = (dmax - dmin) / (k + 1.0) / j / q;
101 let z0 = delta.log10().ceil() as i32;
102 for z in z0..(z0 + 6) {
103 let step = j * q * 10f64.powi(z);
104 let cm = coverage_max(step * (k - 1.0));
105 if W[0] * sm + W[1] * cm + W[2] * dm + W[3] < best_score {
106 break;
107 }
108 let min_start = (dmax / step).floor() * j - (k - 1.0) * j;
109 let max_start = (dmin / step).ceil() * j;
110 if min_start > max_start {
111 continue;
112 }
113 let n_starts = max_start - min_start;
116 if !n_starts.is_finite() || n_starts > MAX_START_CANDIDATES {
117 continue;
118 }
119 for si in 0..=(n_starts as u64) {
120 let start = min_start + si as f64;
121 let lmin = start * step / j;
122 let lmax = lmin + step * (k - 1.0);
123 let s = simplicity(qi, j, lmin, lmax, step);
124 let c = coverage(lmin, lmax);
125 let g = density(k, lmin, lmax);
126 let score = W[0] * s + W[1] * c + W[2] * g + W[3];
127 if score > best_score {
128 best_score = score;
129 best = (lmin, lmax, step);
130 }
131 }
132 }
133 }
134 }
135 }
136
137 let (lmin, lmax, step) = best;
138 if step <= 0.0 {
139 return vec![];
140 }
141 let n = ((lmax - lmin) / step).round() as usize;
142 (0..=n).map(|i| lmin + i as f64 * step).collect()
143}
144
145pub fn nice_step(raw: f64) -> f64 {
147 let magnitude = 10f64.powf(raw.abs().log10().floor());
148 let fraction = raw / magnitude;
149
150 let nice = if fraction <= 1.5 {
151 1.0
152 } else if fraction <= 3.5 {
153 2.0
154 } else if fraction <= 7.5 {
155 5.0
156 } else {
157 10.0
158 };
159
160 nice * magnitude
161}
162
163pub fn format_number(v: f64) -> String {
165 if v == v.round() && v.abs() < 1e10 {
166 format!("{}", v as i64)
167 } else {
168 let s = format!("{:.2}", v);
169 s.trim_end_matches('0').trim_end_matches('.').to_string()
170 }
171}