use std::collections::HashMap;
use crate::native::core::choices::IntegerChoice;
use crate::native::core::{
ChoiceKind, ChoiceNode, ChoiceValue, FloatChoice, float_to_index, index_to_float, sort_key,
};
use super::{ShrinkRun, Shrinker, bin_search_down, find_integer};
const MAX_PRECISE_INTEGER: f64 = (1u64 << 53) as f64;
pub(super) fn as_integer_ratio(v: f64) -> Option<(u128, u128)> {
debug_assert!(v.is_finite() && v > 0.0);
let bits = v.to_bits();
let biased_exp = ((bits >> 52) & 0x7FF) as i32;
let mantissa_bits = bits & ((1u64 << 52) - 1);
let (mut num, mut exp) = if biased_exp == 0 {
(u128::from(mantissa_bits), -1074i32)
} else {
(
u128::from((1u64 << 52) | mantissa_bits),
biased_exp - 1023 - 52,
)
};
let trailing = num.trailing_zeros() as i32;
num >>= trailing;
exp += trailing;
if exp >= 0 {
let shifted = num.checked_shl(exp as u32)?;
Some((shifted, 1))
} else {
let n = 1u128.checked_shl((-exp) as u32)?;
Some((num, n))
}
}
impl<'a> Shrinker<'a> {
pub(super) fn shrink_floats(&mut self) {
let mut i = 0;
while i < self.current_nodes.len() {
let node = &self.current_nodes[i];
if let (ChoiceKind::Float(fc), ChoiceValue::Float(v)) = (&node.kind, &node.value) {
let v = *v;
let fc = fc.clone();
let s = fc.simplest();
if ChoiceValue::Float(s) != ChoiceValue::Float(v) {
self.replace(&HashMap::from([(i, ChoiceValue::Float(s))]));
}
let v = self.float_at(i);
if v.is_infinite() {
if v < 0.0 && fc.validate(f64::INFINITY) {
self.replace(&HashMap::from([(i, ChoiceValue::Float(f64::INFINITY))]));
}
let v = self.float_at(i);
if v.is_infinite() {
let cand = if v > 0.0 { f64::MAX } else { -f64::MAX };
if fc.validate(cand) {
self.replace(&HashMap::from([(i, ChoiceValue::Float(cand))]));
}
}
}
let v = self.float_at(i);
if v.is_nan() {
let mut stepped = false;
for cand in [f64::MAX, f64::INFINITY] {
if fc.validate(cand)
&& self.replace(&HashMap::from([(i, ChoiceValue::Float(cand))]))
{
stepped = true;
break;
}
}
if !stepped && v.to_bits() != f64::NAN.to_bits() && fc.validate(f64::NAN) {
let mut attempt: Vec<ChoiceNode> = self.current_nodes.clone();
attempt[i] = attempt[i].with_value(ChoiceValue::Float(f64::NAN));
let (is_interesting, actual_nodes) =
(self.test_fn)(ShrinkRun::Full(&attempt));
if is_interesting
&& sort_key(&actual_nodes) <= sort_key(&self.current_nodes)
{
self.current_nodes = actual_nodes;
}
}
}
let v = self.float_at(i);
if v.is_nan() {
i += 1;
continue;
}
if v.is_sign_negative() {
let neg = -v;
if fc.validate(neg) {
self.replace(&HashMap::from([(i, ChoiceValue::Float(neg))]));
}
}
let v = self.float_at(i);
let v_abs = v.abs();
let current_idx = float_to_index(v_abs);
let is_neg = v.is_sign_negative();
if current_idx >= (1u64 << 63) {
let (int_lo, int_hi) = if is_neg {
let lo = if fc.max_value <= 0.0 {
(-fc.max_value).ceil() as i128
} else {
0
};
let hi = (-fc.min_value).floor() as i128;
(lo, hi)
} else {
let lo = fc.min_value.max(0.0).ceil() as i128;
let hi = fc.max_value.min(f64::MAX).floor() as i128;
(lo, hi)
};
if int_lo >= 0 && int_lo <= int_hi {
let i_capture = i;
let is_neg_capture = is_neg;
bin_search_down(int_lo, int_hi, &mut |n| {
let candidate = if is_neg_capture {
-(n as f64)
} else {
n as f64
};
self.replace(&HashMap::from([(
i_capture,
ChoiceValue::Float(candidate),
)]))
});
}
}
let v = self.float_at(i);
let v_abs = v.abs();
let current_idx = float_to_index(v_abs);
let is_neg = v.is_sign_negative();
if current_idx > 0 {
bin_search_down(0, current_idx as i128, &mut |idx| {
let candidate_mag = index_to_float(idx as u64);
let candidate = if is_neg {
-candidate_mag
} else {
candidate_mag
};
if fc.validate(candidate) {
self.replace(&HashMap::from([(i, ChoiceValue::Float(candidate))]))
} else {
false
}
});
}
let v = self.float_at(i);
if v.is_finite() && v != 0.0 {
let is_neg = v.is_sign_negative();
if let Some((m, n)) = as_integer_ratio(v.abs()) {
let k_init = m / n;
let r = m % n;
if k_init > 0 {
bin_search_down(0, k_init as i128, &mut |k| {
let num_sum = (k as u128) * n + r;
let candidate_abs = (num_sum as f64) / (n as f64);
let candidate = if is_neg {
-candidate_abs
} else {
candidate_abs
};
if !fc.validate(candidate) {
return false;
}
let prev_key = sort_key(&self.current_nodes);
self.replace(&HashMap::from([(i, ChoiceValue::Float(candidate))]));
sort_key(&self.current_nodes) < prev_key
});
}
}
}
}
i += 1;
}
}
fn float_at(&self, i: usize) -> f64 {
match self.current_nodes[i].value {
ChoiceValue::Float(f) => f,
_ => unreachable!("kind/value invariant violated: outer match guaranteed this variant"),
}
}
pub(super) fn redistribute_numeric_pairs(&mut self) {
let len = self.current_nodes.len();
for i in 0..len {
for gap in 1..=4 {
if i + gap >= self.current_nodes.len() {
break;
}
let j = i + gap;
if !is_float_or_integer(&self.current_nodes[i].kind)
|| !is_float_or_integer(&self.current_nodes[j].kind)
{
continue;
}
if matches!(
(&self.current_nodes[i].kind, &self.current_nodes[j].kind),
(ChoiceKind::Integer(_), ChoiceKind::Integer(_))
) {
continue;
}
if !can_choose_for_redistribute(&self.current_nodes[i])
|| !can_choose_for_redistribute(&self.current_nodes[j])
{
continue;
}
if is_trivial(&self.current_nodes[i]) {
continue;
}
redistribute_pair(self, i, j);
}
}
}
}
fn can_choose_for_redistribute(node: &ChoiceNode) -> bool {
match (&node.kind, &node.value) {
(ChoiceKind::Float(_), ChoiceValue::Float(f)) => {
f.is_finite() && f.abs() < MAX_PRECISE_INTEGER
}
(ChoiceKind::Integer(_), ChoiceValue::Integer(_)) => true,
_ => unreachable!("filtered by is_float_or_integer; ChoiceNode invariant otherwise"),
}
}
fn is_float_or_integer(k: &ChoiceKind) -> bool {
match k {
ChoiceKind::Float(_) | ChoiceKind::Integer(_) => true,
ChoiceKind::Boolean(_) => false,
}
}
fn is_trivial(node: &ChoiceNode) -> bool {
match (&node.kind, &node.value) {
(ChoiceKind::Integer(ic), ChoiceValue::Integer(v)) => *v == ic.simplest(),
(ChoiceKind::Float(fc), ChoiceValue::Float(v)) => !v.is_finite() || *v == fc.simplest(),
_ => unreachable!("filtered by is_float_or_integer; ChoiceNode invariant otherwise"),
}
}
fn redistribute_pair(shrinker: &mut Shrinker<'_>, i: usize, j: usize) {
let (v_i, kind_i) = match (
&shrinker.current_nodes[i].kind,
&shrinker.current_nodes[i].value,
) {
(ChoiceKind::Integer(ic), ChoiceValue::Integer(n)) => {
(NumericValue::Integer(*n), NumericKind::Integer(ic.clone()))
}
(ChoiceKind::Float(fc), ChoiceValue::Float(f)) => {
(NumericValue::Float(*f), NumericKind::Float(fc.clone()))
}
_ => unreachable!("redistribute_pair gated on is_float_or_integer + is_trivial"),
};
let (v_j, kind_j) = match (
&shrinker.current_nodes[j].kind,
&shrinker.current_nodes[j].value,
) {
(ChoiceKind::Integer(ic), ChoiceValue::Integer(n)) => {
(NumericValue::Integer(*n), NumericKind::Integer(ic.clone()))
}
(ChoiceKind::Float(fc), ChoiceValue::Float(f)) => {
(NumericValue::Float(*f), NumericKind::Float(fc.clone()))
}
_ => unreachable!("redistribute_pair gated on is_float_or_integer + is_trivial"),
};
let target_i = shrink_target(&kind_i);
let dir = if v_i.as_f64() >= target_i {
Direction::LowerLeftRaiseRight
} else {
Direction::RaiseLeftLowerRight
};
find_integer(|k| {
let (cand_i, cand_j) = apply_delta(&v_i, &v_j, k as i128, dir);
let Some(val_i) = build_value(&kind_i, cand_i) else {
return false;
};
let Some(val_j) = build_value(&kind_j, cand_j) else {
return false;
};
shrinker.replace(&HashMap::from([(i, val_i), (j, val_j)]))
});
}
#[derive(Clone, Copy)]
enum Direction {
LowerLeftRaiseRight,
RaiseLeftLowerRight,
}
#[derive(Clone, Copy)]
enum NumericValue {
Integer(i128),
Float(f64),
}
impl NumericValue {
fn as_f64(self) -> f64 {
match self {
NumericValue::Integer(n) => n as f64,
NumericValue::Float(f) => f,
}
}
}
#[derive(Clone)]
enum NumericKind {
Integer(IntegerChoice),
Float(FloatChoice),
}
fn shrink_target(kind: &NumericKind) -> f64 {
match kind {
NumericKind::Integer(ic) => ic.simplest() as f64,
NumericKind::Float(_) => 0.0,
}
}
fn apply_delta(
v_i: &NumericValue,
v_j: &NumericValue,
k: i128,
dir: Direction,
) -> (NumericValue, NumericValue) {
let signed_k_i = match dir {
Direction::LowerLeftRaiseRight => -k,
Direction::RaiseLeftLowerRight => k,
};
let signed_k_j = -signed_k_i;
(add_int(v_i, signed_k_i), add_int(v_j, signed_k_j))
}
fn add_int(v: &NumericValue, k: i128) -> NumericValue {
match v {
NumericValue::Integer(n) => NumericValue::Integer(n.saturating_add(k)),
NumericValue::Float(f) => NumericValue::Float(*f + k as f64),
}
}
fn build_value(kind: &NumericKind, candidate: NumericValue) -> Option<ChoiceValue> {
match (kind, candidate) {
(NumericKind::Integer(ic), NumericValue::Integer(n)) => {
ic.validate(n).then_some(ChoiceValue::Integer(n))
}
(NumericKind::Float(fc), NumericValue::Float(f)) => {
fc.validate(f).then_some(ChoiceValue::Float(f))
}
_ => unreachable!("apply_delta preserves variant; kind and value cannot disagree"),
}
}
#[cfg(test)]
#[path = "../../../tests/embedded/native/shrinker_floats_tests.rs"]
mod tests;