Skip to content

Add float quickcheck #111

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
Nov 13, 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
5 changes: 3 additions & 2 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -424,8 +424,9 @@ fn main() {
}

// To filter away some flaky test (see src/float/add.rs for details)
if llvm_target.last() == Some(&"gnueabihf") {
println!("cargo:rustc-cfg=gnueabihf")
if llvm_target[0].starts_with("arm") &&
llvm_target.last().unwrap().contains("gnueabi") {
println!("cargo:rustc-cfg=arm_linux")
}

// To compile intrinsics.rs for thumb targets, where there is no libc
Expand Down
113 changes: 13 additions & 100 deletions src/float/add.rs
Original file line number Diff line number Diff line change
Expand Up @@ -184,114 +184,27 @@ macro_rules! add {
add!(__addsf3: f32);
add!(__adddf3: f64);

#[cfg(test)]
// NOTE(cfg) for some reason, on arm*-unknown-linux-gnueabi*, our implementation doesn't
// match the output of its gcc_s or compiler-rt counterpart. Until we investigate further, we'll
// just avoid testing against them on those targets. Do note that our implementation gives the
// correct answer; gcc_s and compiler-rt are incorrect in this case.
#[cfg(all(test, not(arm_linux)))]
mod tests {
use core::{f32, f64};
use qc::{F32, F64};

use float::{Float, FRepr};
use qc::{U32, U64};

// 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)
-> Option<FRepr<f32> > {
let (a, b) = (f32::from_repr(a.0), f32::from_repr(b.0));
Some(FRepr(f(a, b)))
a: F32,
b: F32)
-> Option<F32> {
Some(F32(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<F64> {
Some(F64(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);
}
}
78 changes: 48 additions & 30 deletions src/float/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
use core::mem;
#[cfg(test)]
use core::fmt;

pub mod add;
pub mod pow;
Expand All @@ -16,22 +14,41 @@ pub trait Float: Sized + Copy {
/// Returns the bitwidth of the significand
fn significand_bits() -> u32;

/// Returns the bitwidth of the exponent
fn exponent_bits() -> u32 {
Self::bits() - Self::significand_bits() - 1
}

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

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

/// Returns a mask for the exponent
fn exponent_mask() -> 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. Inputs are treated as bits and shifted into position.
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);
}

// FIXME: Some of this can be removed if RFC Issue #1424 is resolved
// https://github.com/rust-lang/rfcs/issues/1424
impl Float for f32 {
type Int = u32;
fn bits() -> u32 {
Expand All @@ -40,6 +57,15 @@ impl Float for f32 {
fn significand_bits() -> u32 {
23
}
fn sign_mask() -> Self::Int {
1 << (Self::bits() - 1)
}
fn significand_mask() -> Self::Int {
(1 << Self::significand_bits()) - 1
}
fn exponent_mask() -> Self::Int {
!(Self::sign_mask() | Self::significand_mask())
}
fn repr(self) -> Self::Int {
unsafe { mem::transmute(self) }
}
Expand All @@ -54,6 +80,11 @@ 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::significand_bits()) & Self::exponent_mask()) |
(significand & 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 @@ -68,6 +99,15 @@ impl Float for f64 {
fn significand_bits() -> u32 {
52
}
fn sign_mask() -> Self::Int {
1 << (Self::bits() - 1)
}
fn significand_mask() -> Self::Int {
(1 << Self::significand_bits()) - 1
}
fn exponent_mask() -> Self::Int {
!(Self::sign_mask() | Self::significand_mask())
}
fn repr(self) -> Self::Int {
unsafe { mem::transmute(self) }
}
Expand All @@ -82,36 +122,14 @@ 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::significand_bits()) & Self::exponent_mask()) |
(significand & Self::significand_mask()))
}
fn normalize(significand: Self::Int) -> (i32, Self::Int) {
let shift = significand.leading_zeros()
.wrapping_sub((1u64 << Self::significand_bits()).leading_zeros());
(1i32.wrapping_sub(shift as i32), significand << shift as Self::Int)
}
}

// TODO: Move this to F32/F64 in qc.rs
#[cfg(test)]
#[derive(Copy, Clone)]
pub struct FRepr<F>(F);

#[cfg(test)]
impl<F: Float> PartialEq for FRepr<F> {
fn eq(&self, other: &FRepr<F>) -> bool {
// NOTE(cfg) for some reason, on hard float targets, our implementation doesn't
// match the output of its gcc_s counterpart. Until we investigate further, we'll
// just avoid testing against gcc_s on those targets. Do note that our
// implementation matches the output of the FPU instruction on *hard* float targets
// and matches its gcc_s counterpart on *soft* float targets.
if cfg!(gnueabihf) {
return true
}
self.0.eq_repr(other.0)
}
}

#[cfg(test)]
impl<F: fmt::Debug> fmt::Debug for FRepr<F> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(f)
}
}
17 changes: 7 additions & 10 deletions src/float/pow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,22 +31,19 @@ pow!(__powidf2: f64, i32);

#[cfg(test)]
mod tests {
use float::{Float, FRepr};
use qc::{I32, U32, U64};
use qc::{I32, F32, F64};

check! {
fn __powisf2(f: extern fn(f32, i32) -> f32,
a: U32,
b: I32) -> Option<FRepr<f32> > {
let (a, b) = (f32::from_repr(a.0), b.0);
Some(FRepr(f(a, b)))
a: F32,
b: I32) -> Option<F32> {
Some(F32(f(a.0, b.0)))
}

fn __powidf2(f: extern fn(f64, i32) -> f64,
a: U64,
b: I32) -> Option<FRepr<f64> > {
let (a, b) = (f64::from_repr(a.0), b.0);
Some(FRepr(f(a, b)))
a: F64,
b: I32) -> Option<F64> {
Some(F64(f(a.0, b.0)))
}
}
}
63 changes: 60 additions & 3 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 @@ -143,6 +145,61 @@ 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))
}
}

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)
}
}

impl PartialEq for $TY {
fn eq(&self, other: &$TY) -> bool {
self.0.eq_repr(other.0)
}
}
}
}

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 @@ -224,9 +281,9 @@ macro_rules! check {
print!("{} - Args: ", stringify!($name));
$(print!("{:?} ", $arg);)*
print!("\n");
println!(" rustc-builtins: {:?}", my_answer);
println!(" compiler_rt: {:?}", compiler_rt_answer);
println!(" gcc_s: {:?}", gcc_s_answer);
println!(" compiler-builtins: {:?}", my_answer);
println!(" compiler_rt: {:?}", compiler_rt_answer);
println!(" gcc_s: {:?}", gcc_s_answer);
};

if my_answer != compiler_rt_answer {
Expand Down