cmpute / dashu

A library set of arbitrary precision numbers implemented in Rust.
Apache License 2.0
74 stars 9 forks source link

Perfect "roundtrip" for f64 and RBig #46

Closed lskyum closed 7 months ago

lskyum commented 7 months ago

Hi, I'm new to dashu (and Rust) but thanks for a very nice looking library!

I'm testing to see if Rust and dashu can be used to optimize a C# application that relies on big rationals. But a very important feature is that any f64 can be converted to RBig and back to that exact f64 number.

My initial investigation showed that this didn't work with simplest_from_f64:

use dashu;
use rand::prelude::*;

fn main() {    
    let mut rng = rand::thread_rng();
    for _i in 0..10000000 {
        let a: f64 = rng.gen::<f64>() * 1000000.0;
        let b = dashu::Rational::simplest_from_f64(a).unwrap();
        let c : f64 = b.to_f64().value();

        if a != c {
            // for example a = 4142.7423687749879 and c = 4142.742368774987 ends up here...
            println!("{} != {}", a, c);
        }
    }
}

Does dashu have functions to make this "perfect roundtrip"?

cmpute commented 7 months ago

Hi, thanks for your interest. The method simplest_from_fxx is not meant for exact conversion, it rather produces a readable output for a float number. For exact conversion, you can try RBig::TryFrom<fxx>, that conversion is intended to be exact (and basically the denominator will always be a power of two). Could you please try RBig::try_from and tell me if there are any failures in terms of roundtrip?

lskyum commented 7 months ago

@cmpute Thanks for the quick response.

RBig::try_from seems to work very well. I've testet some edge cases along with a lot of random cases.


fn test_roundtrip(a : f64) {
    let b = Rational::try_from(a).unwrap();
    let c = b.to_f64().value();
    if a != c {
        println!("{} != {}", a, c);
    }
}

fn main() {    
    let mut rng = rand::thread_rng();
    for _i in 0..10000000 {
        let random_bits = rng.gen::<u64>();
        let a = f64::from_bits(random_bits);
        if a.is_nan() || a.is_infinite() { continue; }
        test_roundtrip(a);
    }

    // Edge cases
    test_roundtrip(0.0);
    test_roundtrip(-0.0);
    test_roundtrip(f64::MAX);
    test_roundtrip(f64::MIN);
    test_roundtrip(f64::MIN_POSITIVE);
    test_roundtrip(f64::EPSILON);
    test_roundtrip(-f64::EPSILON);
    // test_roundtrip(f64::INFINITY);       // try_from doesn't work for infinity
    // test_roundtrip(f64::NEG_INFINITY);

    println!("Finished!");
}

It only fails on INFINITY, but I'm not sure it's suppose to work? The C# implementation I'm using uses 1/0 and -1/0 for +/- infinity, but that is not a critical feature.

So far Rust and dashu is about 3x faster, so this looks very promising :-)

cmpute commented 7 months ago

Glad that dashu brings some speed up :)

Regarding infinities, RBig does not support infinities (nor nans) by design, maybe you can support that by adding some checks? dashu_float::FBig does support infinities, and that's why infinities won't be supported by RBig

lskyum commented 7 months ago

@cmpute Yes, it's not a big issue with nan and infinity, because the code usually just needs checks for div-by-zero.

cmpute commented 7 months ago

Great. Then I will close the issue by now, feel free to reach out if you have other questions.