Skip to content

Implement IEEE 754 comparisons for floats (#1084) #1090

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

Closed
wants to merge 5 commits into from
Closed
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
123 changes: 49 additions & 74 deletions src/comp/middle/trans.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1492,26 +1492,26 @@ tag scalar_type { nil_type; signed_int; unsigned_int; floating_point; }


fn compare_scalar_types(cx: @block_ctxt, lhs: ValueRef, rhs: ValueRef,
t: ty::t, llop: ValueRef) -> result {
let f = bind compare_scalar_values(cx, lhs, rhs, _, llop);
t: ty::t, op: ast::binop) -> result {
let f = bind compare_scalar_values(cx, lhs, rhs, _, op);

alt ty::struct(bcx_tcx(cx), t) {
ty::ty_nil. { ret f(nil_type); }
ty::ty_nil. { ret rslt(cx, f(nil_type)); }
ty::ty_bool. | ty::ty_uint. | ty::ty_ptr(_) | ty::ty_char. {
ret f(unsigned_int);
ret rslt(cx, f(unsigned_int));
}
ty::ty_int. { ret f(signed_int); }
ty::ty_float. { ret f(floating_point); }
ty::ty_int. { ret rslt(cx, f(signed_int)); }
ty::ty_float. { ret rslt(cx, f(floating_point)); }
ty::ty_machine(_) {
if ty::type_is_fp(bcx_tcx(cx), t) {
// Floating point machine types
ret f(floating_point);
ret rslt(cx, f(floating_point));
} else if ty::type_is_signed(bcx_tcx(cx), t) {
// Signed, integral machine types
ret f(signed_int);
ret rslt(cx, f(signed_int));
} else {
// Unsigned, integral machine types
ret f(unsigned_int);
ret rslt(cx, f(unsigned_int));
}
}
ty::ty_type. {
Expand All @@ -1535,70 +1535,50 @@ fn compare_scalar_types(cx: @block_ctxt, lhs: ValueRef, rhs: ValueRef,

// A helper function to do the actual comparison of scalar values.
fn compare_scalar_values(cx: @block_ctxt, lhs: ValueRef, rhs: ValueRef,
nt: scalar_type, llop: ValueRef) -> result {
let eq_cmp;
let lt_cmp;
let le_cmp;
nt: scalar_type, op: ast::binop) -> ValueRef {
alt nt {
nil_type. {
// We don't need to do actual comparisons for nil.
// () == () holds but () < () does not.
eq_cmp = 1u;
lt_cmp = 0u;
le_cmp = 1u;
alt op {
ast::eq. | ast::le. | ast::ge. { ret C_bool(true); }
ast::ne. | ast::lt. | ast::gt. { ret C_bool(false); }
}
}
floating_point. {
eq_cmp = lib::llvm::LLVMRealUEQ;
lt_cmp = lib::llvm::LLVMRealULT;
le_cmp = lib::llvm::LLVMRealULE;
let cmp = alt op {
ast::eq. { lib::llvm::LLVMRealOEQ }
ast::ne. { lib::llvm::LLVMRealUNE }
ast::lt. { lib::llvm::LLVMRealOLT }
ast::le. { lib::llvm::LLVMRealOLE }
ast::gt. { lib::llvm::LLVMRealOGT }
ast::ge. { lib::llvm::LLVMRealOGE }
};
ret FCmp(cx, cmp, lhs, rhs);
}
signed_int. {
eq_cmp = lib::llvm::LLVMIntEQ;
lt_cmp = lib::llvm::LLVMIntSLT;
le_cmp = lib::llvm::LLVMIntSLE;
let cmp = alt op {
ast::eq. { lib::llvm::LLVMIntEQ }
ast::ne. { lib::llvm::LLVMIntNE }
ast::lt. { lib::llvm::LLVMIntSLT }
ast::le. { lib::llvm::LLVMIntSLE }
ast::gt. { lib::llvm::LLVMIntSGT }
ast::ge. { lib::llvm::LLVMIntSGE }
};
ret ICmp(cx, cmp, lhs, rhs);
}
unsigned_int. {
eq_cmp = lib::llvm::LLVMIntEQ;
lt_cmp = lib::llvm::LLVMIntULT;
le_cmp = lib::llvm::LLVMIntULE;
}
}
// FIXME: This wouldn't be necessary if we could bind methods off of
// objects and therefore abstract over FCmp and ICmp (issue #435). Then
// we could just write, e.g., "cmp_fn = bind FCmp(cx, _, _, _);" in
// the above, and "auto eq_result = cmp_fn(eq_cmp, lhs, rhs);" in the
// below.

fn generic_cmp(cx: @block_ctxt, nt: scalar_type, op: uint, lhs: ValueRef,
rhs: ValueRef) -> ValueRef {
let r: ValueRef;
if nt == nil_type {
r = C_bool(op != 0u);
} else if nt == floating_point {
r = FCmp(cx, op, lhs, rhs);
} else { r = ICmp(cx, op, lhs, rhs); }
ret r;
}
let last_cx = new_sub_block_ctxt(cx, "last");
let eq_cx = new_sub_block_ctxt(cx, "eq");
let eq_result = generic_cmp(eq_cx, nt, eq_cmp, lhs, rhs);
Br(eq_cx, last_cx.llbb);
let lt_cx = new_sub_block_ctxt(cx, "lt");
let lt_result = generic_cmp(lt_cx, nt, lt_cmp, lhs, rhs);
Br(lt_cx, last_cx.llbb);
let le_cx = new_sub_block_ctxt(cx, "le");
let le_result = generic_cmp(le_cx, nt, le_cmp, lhs, rhs);
Br(le_cx, last_cx.llbb);
let unreach_cx = new_sub_block_ctxt(cx, "unreach");
Unreachable(unreach_cx);
let llswitch = Switch(cx, llop, unreach_cx.llbb, 3u);
AddCase(llswitch, C_u8(abi::cmp_glue_op_eq), eq_cx.llbb);
AddCase(llswitch, C_u8(abi::cmp_glue_op_lt), lt_cx.llbb);
AddCase(llswitch, C_u8(abi::cmp_glue_op_le), le_cx.llbb);
let last_result =
Phi(last_cx, T_i1(), [eq_result, lt_result, le_result],
[eq_cx.llbb, lt_cx.llbb, le_cx.llbb]);
ret rslt(last_cx, last_result);
let cmp = alt op {
ast::eq. { lib::llvm::LLVMIntEQ }
ast::ne. { lib::llvm::LLVMIntNE }
ast::lt. { lib::llvm::LLVMIntULT }
ast::le. { lib::llvm::LLVMIntULE }
ast::gt. { lib::llvm::LLVMIntUGT }
ast::ge. { lib::llvm::LLVMIntUGE }
};
ret ICmp(cx, cmp, lhs, rhs);
}
}
}

type val_pair_fn = fn(@block_ctxt, ValueRef, ValueRef) -> @block_ctxt;
Expand Down Expand Up @@ -1912,16 +1892,6 @@ fn call_cmp_glue(cx: @block_ctxt, lhs: ValueRef, rhs: ValueRef, t: ty::t,
ret rslt(bcx, Load(bcx, llcmpresultptr));
}

// Compares two values. Performs the simple scalar comparison if the types are
// scalar and calls to comparison glue otherwise.
fn compare(cx: @block_ctxt, lhs: ValueRef, rhs: ValueRef, t: ty::t,
llop: ValueRef) -> result {
if ty::type_is_scalar(bcx_tcx(cx), t) {
ret compare_scalar_types(cx, lhs, rhs, t, llop);
}
ret call_cmp_glue(cx, lhs, rhs, t, llop);
}

fn take_ty(cx: @block_ctxt, v: ValueRef, t: ty::t) -> @block_ctxt {
if ty::type_has_pointers(bcx_tcx(cx), t) {
ret call_tydesc_glue(cx, v, t, abi::tydesc_field_take_glue);
Expand Down Expand Up @@ -2262,6 +2232,11 @@ fn trans_expr_fn(bcx: @block_ctxt, f: ast::_fn, sp: span,

fn trans_compare(cx: @block_ctxt, op: ast::binop, lhs: ValueRef,
_lhs_t: ty::t, rhs: ValueRef, rhs_t: ty::t) -> result {
if ty::type_is_scalar(bcx_tcx(cx), rhs_t) {
let rs = compare_scalar_types(cx, lhs, rhs, rhs_t, op);
ret rslt(rs.bcx, rs.val);
}

// Determine the operation we need.
let llop;
alt op {
Expand All @@ -2270,7 +2245,7 @@ fn trans_compare(cx: @block_ctxt, op: ast::binop, lhs: ValueRef,
ast::le. | ast::gt. { llop = C_u8(abi::cmp_glue_op_le); }
}

let rs = compare(cx, lhs, rhs, rhs_t, llop);
let rs = call_cmp_glue(cx, lhs, rhs, rhs_t, llop);

// Invert the result if necessary.
alt op {
Expand Down
45 changes: 35 additions & 10 deletions src/lib/float.rs
Original file line number Diff line number Diff line change
Expand Up @@ -213,13 +213,16 @@ fn NaN() -> float {
ret 0./0.;
}

/* Predicate: isNaN */
pure fn isNaN(f: float) -> bool { f != f }

/* Function: infinity */
fn infinity() -> float {
pure fn infinity() -> float {
ret 1./0.;
}

/* Function: neg_infinity */
fn neg_infinity() -> float {
pure fn neg_infinity() -> float {
ret -1./0.;
}

Expand Down Expand Up @@ -256,17 +259,39 @@ pure fn ge(x: float, y: float) -> bool { ret x >= y; }
/* Predicate: gt */
pure fn gt(x: float, y: float) -> bool { ret x > y; }

/* Predicate: positive */
pure fn positive(x: float) -> bool { ret x > 0.; }
/*
Predicate: positive

Returns true if `x` is a positive number, including +0.0 and +Infinity.
*/
pure fn positive(x: float) -> bool { ret x > 0. || (1./x) == infinity(); }

/*
Predicate: negative

/* Predicate: negative */
pure fn negative(x: float) -> bool { ret x < 0.; }
Returns true if `x` is a negative number, including -0.0 and -Infinity.
*/
pure fn negative(x: float) -> bool { ret x < 0. || (1./x) == neg_infinity(); }

/* Predicate: nonpositive */
pure fn nonpositive(x: float) -> bool { ret x <= 0.; }
/*
Predicate: nonpositive

/* Predicate: nonnegative */
pure fn nonnegative(x: float) -> bool { ret x >= 0.; }
Returns true if `x` is a negative number, including -0.0 and -Infinity.
(This is the same as `float::negative`.)
*/
pure fn nonpositive(x: float) -> bool {
ret x < 0. || (1./x) == neg_infinity();
}

/*
Predicate: nonnegative

Returns true if `x` is a positive number, including +0.0 and +Infinity.
(This is the same as `float::positive`.)
*/
pure fn nonnegative(x: float) -> bool {
ret x > 0. || (1./x) == infinity();
}

//
// Local Variables:
Expand Down
82 changes: 82 additions & 0 deletions src/test/run-pass/float-nan.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
use std;
import std::float;

fn main() {
let nan = float::NaN();
assert(float::isNaN(nan));

let inf = float::infinity();
assert(-inf == float::neg_infinity());

assert( nan != nan);
assert( nan != -nan);
assert(-nan != -nan);
assert(-nan != nan);

assert( nan != 1.);
assert( nan != 0.);
assert( nan != inf);
assert( nan != -inf);

assert( 1. != nan);
assert( 0. != nan);
assert( inf != nan);
assert(-inf != nan);

assert(!( nan == nan));
assert(!( nan == -nan));
assert(!( nan == 1.));
assert(!( nan == 0.));
assert(!( nan == inf));
assert(!( nan == -inf));
assert(!( 1. == nan));
assert(!( 0. == nan));
assert(!( inf == nan));
assert(!(-inf == nan));
assert(!(-nan == nan));
assert(!(-nan == -nan));

assert(!( nan > nan));
assert(!( nan > -nan));
assert(!( nan > 0.));
assert(!( nan > inf));
assert(!( nan > -inf));
assert(!( 0. > nan));
assert(!( inf > nan));
assert(!(-inf > nan));
assert(!(-nan > nan));

assert(!(nan < 0.));
assert(!(nan < 1.));
assert(!(nan < -1.));
assert(!(nan < inf));
assert(!(nan < -inf));
assert(!(nan < nan));
assert(!(nan < -nan));

assert(!( 0. < nan));
assert(!( 1. < nan));
assert(!( -1. < nan));
assert(!( inf < nan));
assert(!(-inf < nan));
assert(!(-nan < nan));

assert(float::isNaN(nan + inf));
assert(float::isNaN(nan + -inf));
assert(float::isNaN(nan + 0.));
assert(float::isNaN(nan + 1.));
assert(float::isNaN(nan * 1.));
assert(float::isNaN(nan / 1.));
assert(float::isNaN(nan / 0.));
assert(float::isNaN(0. / 0.));
assert(float::isNaN(-inf + inf));
assert(float::isNaN(inf - inf));

assert(!float::isNaN(-1.));
assert(!float::isNaN(0.));
assert(!float::isNaN(0.1));
assert(!float::isNaN(1.));
assert(!float::isNaN(inf));
assert(!float::isNaN(-inf));
assert(!float::isNaN(1./-inf));
}
45 changes: 44 additions & 1 deletion src/test/stdtest/float.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,53 @@ fn test_from_str() {
assert ( float::from_str("2.5e10") == 25000000000. );
assert ( float::from_str("25000000000.E-10") == 2.5 );
assert ( float::from_str("") == 0. );
assert ( float::from_str(" ") == 0. );
assert ( float::isNaN(float::from_str(" ")) );
assert ( float::from_str(".") == 0. );
assert ( float::from_str("5.") == 5. );
assert ( float::from_str(".5") == 0.5 );
assert ( float::from_str("0.5") == 0.5 );
}

#[test]
fn test_positive() {
assert(float::positive(float::infinity()));
assert(float::positive(1.));
assert(float::positive(0.));
assert(!float::positive(-1.));
assert(!float::positive(float::neg_infinity()));
assert(!float::positive(1./float::neg_infinity()));
assert(!float::positive(float::NaN()));
}

#[test]
fn test_negative() {
assert(!float::negative(float::infinity()));
assert(!float::negative(1.));
assert(!float::negative(0.));
assert(float::negative(-1.));
assert(float::negative(float::neg_infinity()));
assert(float::negative(1./float::neg_infinity()));
assert(!float::negative(float::NaN()));
}

#[test]
fn test_nonpositive() {
assert(!float::nonpositive(float::infinity()));
assert(!float::nonpositive(1.));
assert(!float::nonpositive(0.));
assert(float::nonpositive(-1.));
assert(float::nonpositive(float::neg_infinity()));
assert(float::nonpositive(1./float::neg_infinity()));
assert(!float::nonpositive(float::NaN()));
}

#[test]
fn test_nonnegative() {
assert(float::nonnegative(float::infinity()));
assert(float::nonnegative(1.));
assert(float::nonnegative(0.));
assert(!float::nonnegative(-1.));
assert(!float::nonnegative(float::neg_infinity()));
assert(!float::nonnegative(1./float::neg_infinity()));
assert(!float::nonnegative(float::NaN()));
}