use std::time::SystemTime;
use epics_base_rs::server::record::Record;
use epics_base_rs::server::records::swait::SwaitRecord;
use epics_base_rs::server::snapshot::{DisplayInfo, PropertySupport, Snapshot};
use epics_base_rs::types::c_cast::f64_to_f32;
use epics_base_rs::types::{DbFieldType, EpicsValue, encode_dbr};
const DBR_GR_FLOAT: u16 = 23;
#[test]
fn overflow_clamps_to_flt_max_with_sign() {
assert_eq!(f64_to_f32(1e300), f32::MAX);
assert_eq!(f64_to_f32(-1e300), -f32::MAX);
assert_eq!(f64_to_f32(f64::MAX), f32::MAX);
assert_eq!(f64_to_f32(f64::MIN), -f32::MAX);
assert_eq!(f64_to_f32(f32::MAX as f64), f32::MAX);
assert_eq!(f64_to_f32(-(f32::MAX as f64)), -f32::MAX);
}
#[test]
fn underflow_clamps_to_flt_min_with_sign() {
assert_eq!(f64_to_f32(1e-300), f32::MIN_POSITIVE);
assert_eq!(f64_to_f32(-1e-300), -f32::MIN_POSITIVE);
assert_eq!(f64_to_f32(1e-40), f32::MIN_POSITIVE);
assert_eq!(f64_to_f32(-1e-40), -f32::MIN_POSITIVE);
assert_eq!(f64_to_f32(f32::MIN_POSITIVE as f64), f32::MIN_POSITIVE);
assert_eq!(f64_to_f32(-(f32::MIN_POSITIVE as f64)), -f32::MIN_POSITIVE);
}
#[test]
fn zero_passes_through_keeping_its_sign() {
assert_eq!(f64_to_f32(0.0), 0.0f32);
assert!(f64_to_f32(0.0).is_sign_positive());
assert_eq!(f64_to_f32(-0.0), 0.0f32);
assert!(f64_to_f32(-0.0).is_sign_negative());
}
#[test]
fn non_finite_passes_through() {
assert!(f64_to_f32(f64::NAN).is_nan());
assert_eq!(f64_to_f32(f64::INFINITY), f32::INFINITY);
assert_eq!(f64_to_f32(f64::NEG_INFINITY), f32::NEG_INFINITY);
}
#[test]
fn in_band_values_are_the_plain_cast() {
assert_eq!(f64_to_f32(1.5), 1.5f32);
assert_eq!(f64_to_f32(-1.5), -1.5f32);
assert_eq!(f64_to_f32(0.1), 0.1f64 as f32);
}
#[test]
fn dbr_float_scalar_convert_to_clamps() {
let cases: &[(f64, f32)] = &[
(1e300, f32::MAX),
(-1e300, -f32::MAX),
(1e-300, f32::MIN_POSITIVE),
(-1e-300, -f32::MIN_POSITIVE),
(0.0, 0.0),
(2.5, 2.5),
];
for &(src, want) in cases {
let got = EpicsValue::Double(src).convert_to(DbFieldType::Float);
assert_eq!(
got,
EpicsValue::Float(want),
"DBF_DOUBLE {src} -> DBF_FLOAT"
);
}
let nan = EpicsValue::Double(f64::NAN).convert_to(DbFieldType::Float);
match nan {
EpicsValue::Float(v) => assert!(v.is_nan(), "NaN must survive the narrowing"),
other => panic!("expected Float, got {other:?}"),
}
}
#[test]
fn dbr_float_array_convert_to_clamps() {
let src = EpicsValue::DoubleArray(vec![1e300, -1e300, 1e-300, -1e-300, 0.0, 2.5]);
assert_eq!(
src.convert_to(DbFieldType::Float),
EpicsValue::FloatArray(vec![
f32::MAX,
-f32::MAX,
f32::MIN_POSITIVE,
-f32::MIN_POSITIVE,
0.0,
2.5,
])
);
}
#[test]
fn gr_float_limits_clamp_on_the_wire() {
let mut snap = Snapshot::new(EpicsValue::Float(1.5), 0, 0, SystemTime::UNIX_EPOCH);
snap.display = Some(DisplayInfo {
units: "V".into(),
precision: 2,
upper_disp_limit: 1e300,
lower_disp_limit: -1e300,
upper_alarm_limit: 1e-300,
lower_alarm_limit: -1e-300,
..Default::default()
});
snap.properties = PropertySupport::NUMERIC.narrowed_to_field(snap.value.db_field_type(), false);
let data = encode_dbr(DBR_GR_FLOAT, &snap).unwrap();
let word = |i: usize| f32::from_be_bytes(data[i..i + 4].try_into().unwrap());
assert_eq!(word(16), f32::MAX, "upper_disp_limit");
assert_eq!(word(20), -f32::MAX, "lower_disp_limit");
assert_eq!(word(24), f32::MIN_POSITIVE, "upper_alarm_limit");
assert_eq!(word(36), -f32::MIN_POSITIVE, "lower_alarm_limit");
}
#[test]
fn swait_odly_double_put_clamps() {
let mut w = SwaitRecord::default();
w.put_field("ODLY", EpicsValue::Double(1e300)).unwrap();
assert_eq!(w.get_field("ODLY"), Some(EpicsValue::Float(f32::MAX)));
w.put_field("ODLY", EpicsValue::Double(-1e-300)).unwrap();
assert_eq!(
w.get_field("ODLY"),
Some(EpicsValue::Float(-f32::MIN_POSITIVE))
);
}