twofloat 0.6.1

Double-double arithmetic functionality.
Documentation
#![allow(clippy::float_cmp)]

use core::convert::TryFrom;
use twofloat::{no_overlap, TwoFloat};

#[macro_use]
pub mod common;

use common::*;

// fract() tests

#[test]
fn fract_hi_fract_test() {
    repeated_test(|| {
        let source = get_valid_twofloat(|x, _| x.fract() != 0.0);
        let expected = source.hi().fract() + source.lo().fract();
        let result = source.fract();
        assert!(
            result.is_valid(),
            "fract({:?}) produced invalid value",
            source
        );
        assert!(
            result.hi().trunc() == 0.0
                || (result.hi().trunc().abs() == 1.0
                    && ((result.hi() >= 0.0) != (result.lo() >= 0.0))),
            "Fractional part of {:?} is greater than one",
            source
        );
        assert!(
            result.lo().trunc() == 0.0,
            "Non-zero integer part of low word of fract({:?}",
            source
        );
        assert_eq_ulp!(
            result.hi(),
            expected,
            1,
            "Mismatch in fractional part of {:?}",
            source
        );
    });
}

#[test]
fn fract_lo_fract_test() {
    repeated_test(|| {
        let (a_fract, b) = get_valid_pair(|x, y| y.fract() != 0.0 && no_overlap(x.trunc(), y));
        let a = a_fract.trunc();
        let source = TwoFloat::try_from((a, b)).unwrap();
        let expected = match (a >= 0.0, b >= 0.0) {
            (true, false) => 1.0 + b.fract(),
            (false, true) => -1.0 + b.fract(),
            _ => b.fract(),
        };
        let result = source.fract();
        assert!(
            result.is_valid(),
            "fract({:?}) produced invalid value",
            source
        );
        assert!(
            result.hi().trunc() == 0.0
                || (result.hi().trunc().abs() == 1.0
                    && ((result.hi() >= 0.0) != (result.lo() >= 0.0))),
            "Fractional part of {:?} is greater than one",
            source
        );
        assert!(
            result.lo().trunc() == 0.0,
            "Non-zero integer part of low word of fract({:?}",
            source
        );
        assert_eq_ulp!(
            result.hi(),
            expected,
            1,
            "Mismatch in fractional part of {:?}",
            source
        );
    });
}

#[test]
fn fract_no_lo_word_test() {
    repeated_test(|| {
        let a = random_float();
        let source = TwoFloat::from(a);
        let expected = a.fract();
        let result = source.fract();

        assert!(
            result.is_valid(),
            "fract({:?}) produced invalid value",
            source
        );
        assert_eq!(
            result, expected,
            "fract({:?}) produced incorrect value",
            source
        );
    })
}

#[test]
fn fract_no_fract_test() {
    repeated_test(|| {
        let (a_fract, b_fract) = get_valid_pair(|x, y| no_overlap(x.trunc(), y.trunc()));
        let source = TwoFloat::try_from((a_fract.trunc(), b_fract.trunc())).unwrap();
        let expected = TwoFloat::from(0.0);
        let result = source.fract();
        assert_eq!(
            result, expected,
            "Non-zero fractional part of integer {:?}",
            source
        );
    });
}

// trunc() tests

#[test]
fn trunc_hi_fract_test() {
    repeated_test(|| {
        let source = get_valid_twofloat(|x, _| x.fract() != 0.0);
        let expected = TwoFloat::from(source.hi().trunc());
        let result = source.trunc();

        assert!(
            result.is_valid(),
            "trunc({:?}) produced invalid value",
            source
        );
        assert!(
            result.hi().fract() == 0.0,
            "Fractional part remains in high word after truncating {:?}",
            source
        );
        assert!(
            result.lo().fract() == 0.0,
            "Fractional part remains in low word after truncating {:?}",
            source
        );
        assert_eq!(result, expected, "Incorrect value of trunc({:?})", source);
    })
}

#[test]
fn trunc_lo_fract_test() {
    repeated_test(|| {
        let (a_fract, b) = get_valid_pair(|x, y| y.fract() != 0.0 && no_overlap(x.trunc(), y));
        let a = a_fract.trunc();
        let source = TwoFloat::try_from((a, b)).unwrap();
        let expected = if a.is_sign_positive() {
            TwoFloat::new_add(a, b.floor())
        } else {
            TwoFloat::new_add(a, b.ceil())
        };

        let result = source.trunc();
        assert!(
            result.is_valid(),
            "trunc({:?}) produced invalid value",
            source
        );
        assert!(
            result.hi().fract() == 0.0,
            "Fractional part remains in high word after truncating {:?}",
            source
        );
        assert!(
            result.lo().fract() == 0.0,
            "Fractional part remains in low word after truncating {:?}",
            source
        );
        assert_eq!(result, expected, "Incorrect value in trunc({:?})", source);
    });
}

#[test]
fn trunc_no_lo_word_test() {
    repeated_test(|| {
        let a = random_float();
        let source = TwoFloat::from(a);
        let result = source.trunc();

        assert!(
            result.is_valid(),
            "trunc({:?}) produced invalid value",
            source
        );
        assert_eq!(
            result,
            a.trunc(),
            "trunc({:?}) produced incorrect value",
            source
        );
    })
}

#[test]
fn trunc_no_lo_fract_test() {
    repeated_test(|| {
        let (a, b_fract) = get_valid_pair(|x, y| no_overlap(x, y.trunc()));
        let b = b_fract.trunc();
        let source = TwoFloat::try_from((a, b)).unwrap();
        let result = source.trunc();

        assert!(
            result.is_valid(),
            "trunc({:?} produced invalid value",
            source
        );
        assert_eq!(
            result.lo(),
            b,
            "trunc({:?}) changed integer low word",
            source
        );
        assert_eq!(
            result.hi(),
            a.trunc(),
            "trunc({:?}) returned incorrect high word",
            source
        );
    })
}

#[test]
fn trunc_no_fract_test() {
    repeated_test(|| {
        let (a_fract, b_fract) = get_valid_pair(|x, y| no_overlap(x.trunc(), y.trunc()));
        let source = TwoFloat::try_from((a_fract.trunc(), b_fract.trunc())).unwrap();
        let expected = source;
        let result = source.trunc();

        assert!(
            result.is_valid(),
            "trunc({:?}) produced invalid value",
            source
        );
        assert_eq!(
            result, expected,
            "trunc({:?}) returned different value",
            source
        );
    })
}

// ceil() tests

#[test]
fn ceil_hi_fract_test() {
    repeated_test(|| {
        let source = get_valid_twofloat(|x, _| x.fract() != 0.0);
        let expected = TwoFloat::from(source.hi().ceil());
        let result = source.ceil();

        assert!(
            result.is_valid(),
            "ceil({:?}) produced invalid value",
            source
        );
        assert_eq!(result, expected, "Incorrect value of ceil({:?})", source);
    })
}

#[test]
fn ceil_lo_fract_test() {
    repeated_test(|| {
        let (a_fract, b) = get_valid_pair(|x, y| y.fract() != 0.0 && no_overlap(x.trunc(), y));
        let a = a_fract.trunc();
        let source = TwoFloat::try_from((a, b)).unwrap();
        let expected = TwoFloat::new_add(a, b.ceil());
        let result = source.ceil();

        assert!(
            result.is_valid(),
            "ceil({:?}) produced invalid value",
            source
        );
        assert_eq!(result, expected, "Incorrect value of ceil({:?})", source);
    })
}

#[test]
fn ceil_no_lo_word_test() {
    repeated_test(|| {
        let a = random_float();
        let source = TwoFloat::from(a);
        let result = source.ceil();

        assert!(
            result.is_valid(),
            "ceil({:?}) produced invalid value",
            source
        );
        assert_eq!(
            result,
            a.ceil(),
            "ceil({:?}) produced incorrect value",
            source
        );
    })
}

#[test]
fn ceil_no_lo_fract_test() {
    repeated_test(|| {
        let (a, b_fract) = get_valid_pair(|x, y| no_overlap(x, y.trunc()));
        let b = b_fract.trunc();
        let source = TwoFloat::try_from((a, b)).unwrap();
        let result = source.ceil();

        assert!(
            result.is_valid(),
            "ceil({:?} produced invalid value",
            source
        );
        assert_eq!(
            result.lo(),
            b,
            "ceil({:?}) changed integer low word",
            source
        );
        assert_eq!(
            result.hi(),
            a.ceil(),
            "ceil({:?}) returned incorrect high word",
            source
        );
    })
}

#[test]
fn ceil_no_fract_test() {
    repeated_test(|| {
        let (a_fract, b_fract) = get_valid_pair(|x, y| no_overlap(x.trunc(), y.trunc()));
        let source = TwoFloat::try_from((a_fract.trunc(), b_fract.trunc())).unwrap();
        let expected = source;
        let result = source.ceil();

        assert!(
            result.is_valid(),
            "ceil({:?}) produced invalid value",
            source
        );
        assert_eq!(
            result, expected,
            "Ceil of integer {:?} returned different value",
            source
        );
    })
}

// floor() tests

#[test]
fn floor_hi_fract_test() {
    repeated_test(|| {
        let source = get_valid_twofloat(|x, _| x.fract() != 0.0);
        let expected = TwoFloat::from(source.hi().floor());
        let result = source.floor();

        assert!(
            result.is_valid(),
            "floor({:?}) produced invalid value",
            source
        );
        assert_eq!(result, expected, "Incorrect value of floor({:?})", source);
    })
}

#[test]
fn floor_lo_fract_test() {
    repeated_test(|| {
        let (a_fract, b) = get_valid_pair(|x, y| y.fract() != 0.0 && no_overlap(x.trunc(), y));
        let a = a_fract.trunc();
        let source = TwoFloat::try_from((a, b)).unwrap();
        let expected = TwoFloat::new_add(a, b.floor());
        let result = source.floor();
        assert!(
            result.is_valid(),
            "floor({:?}) produced invalid value",
            source
        );
        assert_eq!(result, expected, "Incorrect value of floor({:?})", source);
    });
}

#[test]
fn floor_no_lo_word_test() {
    repeated_test(|| {
        let a = random_float();
        let source = TwoFloat::from(a);
        let result = source.floor();

        assert!(
            result.is_valid(),
            "ceil({:?}) produced invalid value",
            source
        );
        assert_eq!(
            result,
            a.floor(),
            "ceil({:?}) produced incorrect value",
            source
        );
    });
}

#[test]
fn floor_no_lo_fract_test() {
    repeated_test(|| {
        let (a, b_fract) = get_valid_pair(|x, y| no_overlap(x, y.trunc()));
        let b = b_fract.trunc();
        let source = TwoFloat::try_from((a, b)).unwrap();
        let result = source.floor();

        assert!(
            result.is_valid(),
            "floor({:?} produced invalid value",
            source
        );
        assert_eq!(
            result.lo(),
            b,
            "floor({:?}) changed integer low word",
            source
        );
        assert_eq!(
            result.hi(),
            a.floor(),
            "floor({:?}) returned incorrect high word",
            source
        );
    })
}

#[test]
fn floor_no_fract_test() {
    repeated_test(|| {
        let (a_fract, b_fract) = get_valid_pair(|x, y| no_overlap(x.trunc(), y.trunc()));
        let source = TwoFloat::try_from((a_fract.trunc(), b_fract.trunc())).unwrap();
        let expected = source;
        let result = source.floor();
        assert!(
            result.is_valid(),
            "floor({:?}) produced invalid value",
            source
        );
        assert_eq!(
            result, expected,
            "floor({:?}) returned different value",
            source
        );
    });
}

// round() tests

#[test]
fn round_hi_fract_test() {
    repeated_test(|| {
        let source = get_valid_twofloat(|x, _| x.fract() != 0.0 && x.fract().abs() != 0.5);
        let expected = TwoFloat::from(source.hi().round());
        let result = source.round();

        assert!(
            result.is_valid(),
            "round({:?}) produced invalid value",
            source
        );
        assert_eq!(result, expected, "Incorrect value of round({:?})", source);
    });
}

#[test]
fn round_hi_half_test() {
    repeated_test(|| {
        let (a, b) = get_valid_pair(|x, y| {
            let x_half = x.trunc() + 0.5;
            x_half.fract().abs() == 0.5 && no_overlap(x_half, y)
        });
        let source = TwoFloat::try_from((a.trunc() + 0.5, b)).unwrap();
        let expected = if source.hi().is_sign_positive() == source.lo().is_sign_positive() {
            TwoFloat::from(source.hi().round())
        } else {
            TwoFloat::from(source.hi().trunc())
        };
        let result = source.round();

        assert!(
            result.is_valid(),
            "round({:?}) produced invalid value",
            source
        );
        assert_eq!(result, expected, "Incorrect value of round({:?})", source);
    });
}

#[test]
fn round_lo_fract_test() {
    repeated_test(|| {
        let (a_fract, b) = get_valid_pair(|x, y| {
            y.fract() != 0.0 && y.fract().abs() != 0.5 && no_overlap(x.trunc(), y)
        });
        let a = a_fract.trunc();
        let source = TwoFloat::try_from((a, b)).unwrap();
        let expected = TwoFloat::new_add(a, b.round());
        let result = source.round();
        assert!(
            result.is_valid(),
            "round({:?}) produced invalid value",
            source
        );
        assert_eq!(result, expected, "Incorrect value of round({:?})", source);
    });
}

#[test]
fn round_lo_half_test() {
    repeated_test(|| {
        let (a, b) = get_valid_pair(|x, y| {
            let y_half = y.trunc() + 0.5;
            y_half.fract().abs() == 0.5 && no_overlap(x, y_half)
        });
        let source = TwoFloat::try_from((a, b.trunc() + 0.5)).unwrap();
        let expected = if source.hi().is_sign_positive() == source.lo().is_sign_positive() {
            TwoFloat::new_add(source.hi(), source.lo().round())
        } else {
            TwoFloat::new_add(source.hi(), source.lo().trunc())
        };

        let result = source.round();

        assert!(
            result.is_valid(),
            "round({:?}) produced invalid value",
            source
        );
        assert_eq!(result, expected, "Incorrect value of round({:?})", source);
    });
}

#[test]
fn round_no_lo_word_test() {
    repeated_test(|| {
        let a = random_float();
        let source = TwoFloat::from(a);
        let result = source.round();

        assert!(
            result.is_valid(),
            "round({:?}) produced invalid value",
            source
        );
        assert_eq!(
            result,
            a.round(),
            "round({:?}) produced incorrect value",
            source
        );
    })
}

#[test]
fn round_no_lo_fract_test() {
    repeated_test(|| {
        let (a, b_fract) = get_valid_pair(|x, y| no_overlap(x, y.trunc()));
        let b = b_fract.trunc();
        let source = TwoFloat::try_from((a, b)).unwrap();
        let result = source.round();

        assert!(
            result.is_valid(),
            "round({:?} produced invalid value",
            source
        );
        assert_eq!(
            result.lo(),
            b,
            "round({:?}) changed integer low word",
            source
        );
        assert_eq!(
            result.hi(),
            a.round(),
            "round({:?}) returned incorrect high word",
            source
        );
    });
}

#[test]
fn round_no_fract_test() {
    repeated_test(|| {
        let (a_fract, b_fract) = get_valid_pair(|x, y| no_overlap(x.trunc(), y.trunc()));
        let source = TwoFloat::try_from((a_fract.trunc(), b_fract.trunc())).unwrap();
        let expected = source;
        let result = source.round();
        assert!(
            result.is_valid(),
            "round({:?}) produced invalid value",
            source
        );
        assert_eq!(
            result, expected,
            "round({:?}) returned different value",
            source
        );
    })
}