Skip to content

Add float quickcheck implementations (fixes #45) #48

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Sep 30, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
145 changes: 28 additions & 117 deletions src/float/add.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,27 +9,27 @@ macro_rules! add {
#[allow(unused_parens)]
#[cfg_attr(not(test), no_mangle)]
pub extern fn $intrinsic(a: $ty, b: $ty) -> $ty {
let one = Wrapping(1 as <$ty as Float>::Int);
let zero = Wrapping(0 as <$ty as Float>::Int);

let bits = Wrapping(<$ty>::bits() as <$ty as Float>::Int);
let significand_bits = Wrapping(<$ty>::significand_bits() as <$ty as Float>::Int);
let exponent_bits = bits - significand_bits - one;
let max_exponent = (one << exponent_bits.0 as usize) - one;

let implicit_bit = one << significand_bits.0 as usize;
let significand_mask = implicit_bit - one;
let sign_bit = one << (significand_bits + exponent_bits).0 as usize;
let abs_mask = sign_bit - one;
let exponent_mask = abs_mask ^ significand_mask;
let inf_rep = exponent_mask;
let quiet_bit = implicit_bit >> 1;
let qnan_rep = exponent_mask | quiet_bit;

let mut a_rep = Wrapping(a.repr());
let mut b_rep = Wrapping(b.repr());
let a_abs = a_rep & abs_mask;
let b_abs = b_rep & abs_mask;
let one = Wrapping(1 as <$ty as Float>::Int);
let zero = Wrapping(0 as <$ty as Float>::Int);

let bits = Wrapping(<$ty>::bits() as <$ty as Float>::Int);
let significand_bits = Wrapping(<$ty>::significand_bits() as <$ty as Float>::Int);
let exponent_bits = Wrapping(<$ty>::exponent_bits() as <$ty as Float>::Int);
let max_exponent = (one << exponent_bits.0 as usize) - one;

let implicit_bit = one << significand_bits.0 as usize;
let significand_mask = implicit_bit - one;
let sign_bit = one << (significand_bits + exponent_bits).0 as usize;
let abs_mask = sign_bit - one;
let exponent_mask = abs_mask ^ significand_mask;
let inf_rep = exponent_mask;
let quiet_bit = implicit_bit >> 1;
let qnan_rep = exponent_mask | quiet_bit;

let mut a_rep = Wrapping(a.repr());
let mut b_rep = Wrapping(b.repr());
let a_abs = a_rep & abs_mask;
let b_abs = b_rep & abs_mask;

// Detect if a or b is zero, infinity, or NaN.
if a_abs - one >= inf_rep - one ||
Expand Down Expand Up @@ -188,7 +188,7 @@ mod tests {
use core::{f32, f64};

use float::Float;
use qc::{U32, U64};
use qc::{F32, F64};

// NOTE The tests below have special handing for NaN values.
// Because NaN != NaN, the floating-point representations must be used
Expand All @@ -212,107 +212,18 @@ mod tests {
}
}

// TODO: Add F32/F64 to qc so that they print the right values (at the very least)
check! {
fn __addsf3(f: extern fn(f32, f32) -> f32,
a: U32,
b: U32)
a: F32,
b: F32)
-> Option<FRepr<f32> > {
let (a, b) = (f32::from_repr(a.0), f32::from_repr(b.0));
Some(FRepr(f(a, b)))
Some(FRepr(f(a.0, b.0)))
}

fn __adddf3(f: extern fn(f64, f64) -> f64,
a: U64,
b: U64) -> Option<FRepr<f64> > {
let (a, b) = (f64::from_repr(a.0), f64::from_repr(b.0));
Some(FRepr(f(a, b)))
a: F64,
b: F64) -> Option<FRepr<f64> > {
Some(FRepr(f(a.0, b.0)))
}
}

// More tests for special float values

#[test]
fn test_float_tiny_plus_tiny() {
let tiny = f32::from_repr(1);
let r = super::__addsf3(tiny, tiny);
assert!(r.eq_repr(tiny + tiny));
}

#[test]
fn test_double_tiny_plus_tiny() {
let tiny = f64::from_repr(1);
let r = super::__adddf3(tiny, tiny);
assert!(r.eq_repr(tiny + tiny));
}

#[test]
fn test_float_small_plus_small() {
let a = f32::from_repr(327);
let b = f32::from_repr(256);
let r = super::__addsf3(a, b);
assert!(r.eq_repr(a + b));
}

#[test]
fn test_double_small_plus_small() {
let a = f64::from_repr(327);
let b = f64::from_repr(256);
let r = super::__adddf3(a, b);
assert!(r.eq_repr(a + b));
}

#[test]
fn test_float_one_plus_one() {
let r = super::__addsf3(1f32, 1f32);
assert!(r.eq_repr(1f32 + 1f32));
}

#[test]
fn test_double_one_plus_one() {
let r = super::__adddf3(1f64, 1f64);
assert!(r.eq_repr(1f64 + 1f64));
}

#[test]
fn test_float_different_nan() {
let a = f32::from_repr(1);
let b = f32::from_repr(0b11111111100100010001001010101010);
let x = super::__addsf3(a, b);
let y = a + b;
assert!(x.eq_repr(y));
}

#[test]
fn test_double_different_nan() {
let a = f64::from_repr(1);
let b = f64::from_repr(0b1111111111110010001000100101010101001000101010000110100011101011);
let x = super::__adddf3(a, b);
let y = a + b;
assert!(x.eq_repr(y));
}

#[test]
fn test_float_nan() {
let r = super::__addsf3(f32::NAN, 1.23);
assert_eq!(r.repr(), f32::NAN.repr());
}

#[test]
fn test_double_nan() {
let r = super::__adddf3(f64::NAN, 1.23);
assert_eq!(r.repr(), f64::NAN.repr());
}

#[test]
fn test_float_inf() {
let r = super::__addsf3(f32::INFINITY, -123.4);
assert_eq!(r, f32::INFINITY);
}

#[test]
fn test_double_inf() {
let r = super::__adddf3(f64::INFINITY, -123.4);
assert_eq!(r, f64::INFINITY);
}
}
79 changes: 78 additions & 1 deletion src/float/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,45 @@ pub trait Float: Sized + Copy {
/// Returns the bitwidth of the float type
fn bits() -> u32;

/// Returns the bitwidth of the exponent
fn exponent_bits() -> u32;

/// Returns the bitwidth of the significand
fn significand_bits() -> u32;

/// Returns a mask for the sign bit of `self`
fn sign_mask() -> Self::Int;

/// Returns a mask for the exponent portion of `self`
fn exponent_mask() -> Self::Int;

/// Returns a mask for the significand portion of `self`
fn significand_mask() -> Self::Int;

/// Returns the sign bit of `self`
fn sign(self) -> bool;

/// Returns the exponent portion of `self`, shifted to the right
fn exponent(self) -> Self::Int;

/// Returns the significand portion of `self`
fn significand(self) -> Self::Int;

/// Returns `self` transmuted to `Self::Int`
fn repr(self) -> Self::Int;

#[cfg(test)]
/// Checks if two floats have the same bit representation. *Except* for NaNs! NaN can be
/// represented in multiple different ways. This methods returns `true` if two NaNs are
/// represented in multiple different ways. This method returns `true` if two NaNs are
/// compared.
fn eq_repr(self, rhs: Self) -> bool;

/// Returns a `Self::Int` transmuted back to `Self`
fn from_repr(a: Self::Int) -> Self;

/// Constructs a `Self` from its parts
fn from_parts(sign: bool, exponent: Self::Int, significand: Self::Int) -> Self;

/// Returns (normalized exponent, normalized significand)
fn normalize(significand: Self::Int) -> (i32, Self::Int);
}
Expand All @@ -34,9 +58,21 @@ impl Float for f32 {
fn bits() -> u32 {
32
}
fn exponent_bits() -> u32 {
8
}
fn significand_bits() -> u32 {
23
}
fn sign_mask() -> Self::Int {
1 << (Self::bits() - 1)
}
fn exponent_mask() -> Self::Int {
((1 << Self::exponent_bits()) - 1) << Self::significand_bits()
}
fn significand_mask() -> Self::Int {
(1 << Self::significand_bits()) - 1
}
fn repr(self) -> Self::Int {
unsafe { mem::transmute(self) }
}
Expand All @@ -51,6 +87,21 @@ impl Float for f32 {
fn from_repr(a: Self::Int) -> Self {
unsafe { mem::transmute(a) }
}

fn from_parts(sign: bool, exponent: Self::Int, significand: Self::Int) -> Self {
Self::from_repr(((sign as Self::Int) << (Self::bits() - 1)) |
exponent & Self::exponent_mask() |
significand & Self::significand_mask())
}
fn sign(self) -> bool {
(self.repr() & Self::sign_mask()) != 0
}
fn exponent(self) -> Self::Int {
self.repr() >> Self::significand_bits() & Self::exponent_mask()
}
fn significand(self) -> Self::Int {
self.repr() & Self::significand_mask()
}
fn normalize(significand: Self::Int) -> (i32, Self::Int) {
let shift = significand.leading_zeros()
.wrapping_sub((1u32 << Self::significand_bits()).leading_zeros());
Expand All @@ -62,9 +113,21 @@ impl Float for f64 {
fn bits() -> u32 {
64
}
fn exponent_bits() -> u32 {
11
}
fn significand_bits() -> u32 {
52
}
fn sign_mask() -> Self::Int {
1 << (Self::bits() - 1)
}
fn exponent_mask() -> Self::Int {
((1 << Self::exponent_bits()) - 1) << Self::significand_bits()
}
fn significand_mask() -> Self::Int {
(1 << Self::significand_bits()) - 1
}
fn repr(self) -> Self::Int {
unsafe { mem::transmute(self) }
}
Expand All @@ -79,6 +142,20 @@ impl Float for f64 {
fn from_repr(a: Self::Int) -> Self {
unsafe { mem::transmute(a) }
}
fn from_parts(sign: bool, exponent: Self::Int, significand: Self::Int) -> Self {
Self::from_repr(((sign as Self::Int) << (Self::bits() - 1)) |
exponent & Self::exponent_mask() |
significand & Self::significand_mask())
}
fn sign(self) -> bool {
(self.repr() & Self::sign_mask()) != 0
}
fn exponent(self) -> Self::Int {
self.repr() >> Self::significand_bits() & Self::exponent_mask()
}
fn significand(self) -> Self::Int {
self.repr() & Self::significand_mask()
}
fn normalize(significand: Self::Int) -> (i32, Self::Int) {
let shift = significand.leading_zeros()
.wrapping_sub((1u64 << Self::significand_bits()).leading_zeros());
Expand Down
55 changes: 55 additions & 0 deletions src/qc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@

use std::boxed::Box;
use std::fmt;
use core::{f32, f64};

use quickcheck::{Arbitrary, Gen};

use int::LargeInt;
use float::Float;

// Generates values in the full range of the integer type
macro_rules! arbitrary {
Expand Down Expand Up @@ -71,6 +73,7 @@ macro_rules! arbitrary {
arbitrary!(I32: i32);
arbitrary!(U32: u32);


// These integers are "too large". If we generate e.g. `u64` values in the full range then there's
// only `1 / 2^32` chance of seeing a value smaller than `2^32` (i.e. whose higher "word" (32-bits)
// is `0`)! But this is an important group of values to tests because we have special code paths for
Expand Down Expand Up @@ -143,6 +146,57 @@ macro_rules! arbitrary_large {
arbitrary_large!(I64: i64);
arbitrary_large!(U64: u64);


macro_rules! arbitrary_float {
($TY:ident : $ty:ident) => {
#[derive(Clone, Copy)]
pub struct $TY(pub $ty);

impl Arbitrary for $TY {
fn arbitrary<G>(g: &mut G) -> $TY
where G: Gen
{
let special = [
-0.0, 0.0, $ty::NAN, $ty::INFINITY, -$ty::INFINITY
];

if g.gen_weighted_bool(10) { // Random special case
$TY(*g.choose(&special).unwrap())
} else if g.gen_weighted_bool(10) { // NaN variants
let sign: bool = g.gen();
let exponent: <$ty as Float>::Int = g.gen();
let significand: <$ty as Float>::Int = 0;
$TY($ty::from_parts(sign, exponent, significand))
} else if g.gen() { // Denormalized
let sign: bool = g.gen();
let exponent: <$ty as Float>::Int = 0;
let significand: <$ty as Float>::Int = g.gen();
$TY($ty::from_parts(sign, exponent, significand))
} else { // Random anything
let sign: bool = g.gen();
let exponent: <$ty as Float>::Int = g.gen();
let significand: <$ty as Float>::Int = g.gen();
$TY($ty::from_parts(sign, exponent, significand))
}
Copy link
Contributor Author

@mattico mattico Aug 21, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not entirely convinced by the above method, but the tests get run often enough that I believe that the different combinations will be run many times, and not having any state keeps the method simple.

}

fn shrink(&self) -> Box<Iterator<Item=$TY>> {
::quickcheck::empty_shrinker()
}
}

impl fmt::Debug for $TY {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Debug::fmt(&self.0, f)
}
}
}
}

arbitrary_float!(F32: f32);
arbitrary_float!(F64: f64);


// Convenience macro to test intrinsics against their reference implementations.
//
// Each intrinsic is tested against both the `gcc_s` library as well as
Expand Down Expand Up @@ -263,3 +317,4 @@ macro_rules! check {
}
)
}