This commit is contained in:
2026-02-24 09:58:53 -08:00
parent 834aa4263f
commit 4528e6b0c2

View File

@@ -23,35 +23,88 @@ fn f64_into_parts(f: f64) -> (u64, i16, i8) {
pub fn ratio_from_float(value:f64)->Result<Ratio64,RatioFromFloatError>{
// Handle special values first
match value.classify() {
match value.classify(){
core::num::FpCategory::Nan=>return Err(RatioFromFloatError::Nan),
core::num::FpCategory::Zero=>return Ok(Ratio64::ZERO),
core::num::FpCategory::Subnormal
|core::num::FpCategory::Normal
|core::num::FpCategory::Infinite=>{
if value < i64::MIN as f64 {
if value<i64::MIN as f64{
return Err(RatioFromFloatError::Underflow);
}
if (i64::MAX as f64) < value {
if (i64::MAX as f64)<value{
return Err(RatioFromFloatError::Overflow);
}
}
}
// value = sign * mantissa * 2 ^ exponent
let (mantissa, exponent, sign) = f64_into_parts(value);
let (mantissa,exponent,sign)=f64_into_parts(value);
if 0<=exponent{
// we know value < i64::MIN, so it must just be an integer.
return Ok(Ratio64::new((sign as i64*mantissa as i64)<<exponent,1).unwrap());
}
if exponent< -62{
if exponent< -62-52{
// very small number is not representable
return Ok(Ratio64::ZERO);
}
// the idea: create exact float num/den ratio, and compute continued fraction if it doesn't fit
// oh actually we can just straight up represent all floats exactly. ok.
Ok(Ratio64::new(sign as i64*mantissa as i64, 1<<-exponent).unwrap())
if -63<exponent{
// exactly representable
return Ok(Ratio64::new(sign as i64*mantissa as i64,1<<-exponent).unwrap());
}
// the denominator is necessarily bigger than the numerator at this point.
// create exact float num/den ratio
let mut in_num=mantissa as u128;
let mut in_den=1u128<<-exponent;
let mut out_num=0i64;
let mut out_den=1i64;
// a+b*c
fn ma(a:i64,b:i64,c:i64)->Option<i64>{
a.checked_add(b.checked_mul(c)?)
}
// compute continued fraction
loop{
let whole=in_den/in_num;
if (i64::MAX as u128)<whole{
// cannot continue fraction
break;
}
let Some(new_den)=ma(out_num,out_den,whole as i64)else{
// too big for Ratio64
break;
};
(out_num,out_den)=(out_den,new_den);
(in_num,in_den)=(in_den-in_num*whole,in_num);
}
Ok(Ratio64::new(sign as i64*out_num,out_den as u64).unwrap())
}
#[cfg(test)]
fn req(r0:Ratio64,r1:Ratio64){
println!("r0={r0:?} r1={r1:?}");
assert_eq!(r0.num(),r1.num(),"Nums not eq");
assert_eq!(r0.den(),r1.den(),"Dens not eq");
}
#[test]
fn test(){
req(ratio_from_float(0.00000000001).unwrap(),Ratio64::new(1,10000000000).unwrap());
req(ratio_from_float(0.5).unwrap(),Ratio64::new(1,2).unwrap());
req(ratio_from_float(1.0).unwrap(),Ratio64::new(1,1).unwrap());
req(ratio_from_float(1.1).unwrap(),Ratio64::new(2476979795053773,2251799813685248).unwrap());
req(ratio_from_float(2.0).unwrap(),Ratio64::new(2,1).unwrap());
req(ratio_from_float(core::f64::consts::PI).unwrap(),Ratio64::new(884279719003555,281474976710656).unwrap());
req(ratio_from_float(2222222222222.0).unwrap(),Ratio64::new(2222222222222,1).unwrap());
}