use core::f32::consts::PI;
use range_set_blaze::{
FiniteF32, Integer, RangeMapBlaze, RangeSetBlaze,
finite::{FiniteRangeExt, ff32},
};
use rayon::prelude::*;
use std::{num::NonZero, ops::BitOr, thread::available_parallelism};
const TARGET_ERROR: f64 = 1e-7;
fn main() {
let scope = RangeSetBlaze::from_iter([ff32(-PI)..=ff32(PI)]);
let num_chunks = available_parallelism().map_or(1, NonZero::get);
let term_map: RangeMapBlaze<FiniteF32, u8> = chunks(&scope, num_chunks)
.into_par_iter()
.map(|chunk| chunk.iter().map(|x| (x, terms_needed(x))).collect())
.reduce(RangeMapBlaze::new, BitOr::bitor);
println!(
"Taylor-series terms needed for cos(x) to guarantee an error under one f32 epsilon, for f32 in [-pi, pi]:\n\
(target absolute error: {TARGET_ERROR}; taylor/std may still disagree in the last printed digit -- display-rounding, not a bug)"
);
for (range, n) in term_map.range_values() {
let (start, end) = range.into_primitive_inner();
let mid = f32::midpoint(start, end);
let taylor = taylor_cos(mid, *n);
let std = mid.cos();
println!(
" [{start:>14e}, {end:>14e}] -> {n} term(s) (cos(mid): taylor={taylor:.7}, std={std:.7})",
);
}
println!(
"\n{} disjoint ranges cover all {} in-scope f32 values.",
separate_with_underscores(&term_map.range_values().count()),
separate_with_underscores(&term_map.len())
);
println!(
"Mean terms per in-scope f32 value (each value weighted equally): {:.2}",
mean_terms(&term_map)
);
}
fn separate_with_underscores(value: &impl ToString) -> String {
let mut value = value.to_string();
let mut index = value.len();
while index > 3 {
index -= 3;
value.insert(index, '_');
}
value
}
fn terms_needed(x: FiniteF32) -> u8 {
let x = f64::from(x.into_inner());
let xx = x * x;
let mut term_magnitude = 1.0_f64; for n in 1..=u8::MAX {
term_magnitude *= xx / f64::from(2 * u32::from(n) * (2 * u32::from(n) - 1));
if term_magnitude <= TARGET_ERROR {
return n;
}
}
panic!("u8::MAX terms not enough for x={x}")
}
fn chunks(scope: &RangeSetBlaze<FiniteF32>, n: usize) -> Vec<RangeSetBlaze<FiniteF32>> {
let Some(mut start) = scope.first() else {
return Vec::new();
};
let n = u32::try_from(n).unwrap_or(u32::MAX).clamp(1, scope.len());
let (base_len, remainder) = (scope.len() / n, scope.len() % n);
(0..n)
.map(|i| {
let len = base_len + u32::from(i < remainder);
let end = start.inclusive_end_from_start(len);
let chunk = RangeSetBlaze::from_iter([start..=end]);
start = end.after();
chunk
})
.collect()
}
#[allow(clippy::cast_precision_loss)]
fn taylor_cos(x: f32, terms: u8) -> f32 {
let xx = x * x;
let mut term = 1.0_f32;
let mut sum = 1.0_f32;
for k in 1..terms {
term *= -xx / (2 * u32::from(k) * (2 * u32::from(k) - 1)) as f32;
sum += term;
}
sum
}
fn mean_terms(term_map: &RangeMapBlaze<FiniteF32, u8>) -> f64 {
let mut weighted_sum = 0.0;
let mut total_count = 0.0;
for (range, n) in term_map.range_values() {
let len = FiniteF32::safe_len_to_f64_lossy(FiniteF32::safe_len(&range));
weighted_sum = len.mul_add(f64::from(*n), weighted_sum);
total_count += len;
}
weighted_sum / total_count
}