Skip to content

Commit

Permalink
Implement Float.== in Inko
Browse files Browse the repository at this point in the history
The implementation is the same as before, but instead of calling to a
runtime function written in Rust, we now implement this method in pure
Inko, though we still use an LLVM intrinsic for the base comparison
(exposed using the _INKO.float_eq intrinsic).

This fixes #584.

Changelog: changed
  • Loading branch information
yorickpeterse committed Jul 14, 2023
1 parent 3afdcd4 commit 449af85
Show file tree
Hide file tree
Showing 5 changed files with 61 additions and 61 deletions.
14 changes: 4 additions & 10 deletions compiler/src/llvm/passes.rs
Expand Up @@ -957,16 +957,10 @@ impl<'a, 'b, 'ctx> LowerMethod<'a, 'b, 'ctx> {
let reg_var = self.variables[&ins.register];
let lhs_var = self.variables[&ins.arguments[0]];
let rhs_var = self.variables[&ins.arguments[1]];
let state = self
.builder
.load_pointer(self.layouts.state, state_var)
.into();
let lhs = self.read_float(lhs_var).into();
let rhs = self.read_float(rhs_var).into();
let func = self
.module
.runtime_function(RuntimeFunction::FloatEq);
let res = self.builder.call(func, &[state, lhs, rhs]);
let lhs = self.read_float(lhs_var);
let rhs = self.read_float(rhs_var);
let raw = self.builder.float_eq(lhs, rhs);
let res = self.new_bool(state_var, raw);

self.builder.store(reg_var, res);
}
Expand Down
10 changes: 0 additions & 10 deletions compiler/src/llvm/runtime_function.rs
Expand Up @@ -10,7 +10,6 @@ pub(crate) enum RuntimeFunction {
FloatBoxed,
FloatBoxedPermanent,
FloatClone,
FloatEq,
Free,
IntBoxed,
IntBoxedPermanent,
Expand Down Expand Up @@ -42,7 +41,6 @@ impl RuntimeFunction {
"inko_float_boxed_permanent"
}
RuntimeFunction::FloatClone => "inko_float_clone",
RuntimeFunction::FloatEq => "inko_float_eq",
RuntimeFunction::Free => "inko_free",
RuntimeFunction::IntBoxed => "inko_int_boxed",
RuntimeFunction::IntBoxedPermanent => "inko_int_boxed_permanent",
Expand Down Expand Up @@ -154,14 +152,6 @@ impl RuntimeFunction {

ret.fn_type(&[state, val], false)
}
RuntimeFunction::FloatEq => {
let state = module.layouts.state.ptr_type(space).into();
let lhs = context.f64_type().into();
let rhs = context.f64_type().into();
let ret = context.pointer_type();

ret.fn_type(&[state, lhs, rhs], false)
}
RuntimeFunction::ProcessFinishMessage => {
let proc = context.pointer_type().into();
let terminate = context.bool_type().into();
Expand Down
41 changes: 1 addition & 40 deletions rt/src/runtime/float.rs
@@ -1,10 +1,6 @@
use crate::mem::{Bool, Float, String as InkoString};
use crate::mem::{Float, String as InkoString};
use crate::state::State;

/// The maximum difference between two floats for them to be considered equal,
/// as expressed in "Units in the Last Place" (ULP).
const ULP_DIFF: i64 = 1;

#[no_mangle]
pub unsafe extern "system" fn inko_float_boxed(
state: *const State,
Expand All @@ -21,41 +17,6 @@ pub unsafe extern "system" fn inko_float_boxed_permanent(
Float::alloc_permanent((*state).float_class, value)
}

#[no_mangle]
pub unsafe extern "system" fn inko_float_eq(
state: *const State,
left: f64,
right: f64,
) -> *const Bool {
// For float equality we use ULPs. See
// https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/
// for more details.
let state = &*state;

if left == right {
// Handle cases such as `-0.0 == 0.0`.
return state.true_singleton;
}

if left.is_sign_positive() != right.is_sign_positive() {
return state.false_singleton;
}

if left.is_nan() || right.is_nan() {
return state.false_singleton;
}

let left_bits = left.to_bits() as i64;
let right_bits = right.to_bits() as i64;
let diff = left_bits.wrapping_sub(right_bits);

if (-ULP_DIFF..=ULP_DIFF).contains(&diff) {
state.true_singleton
} else {
state.false_singleton
}
}

#[no_mangle]
pub unsafe extern "system" fn inko_float_clone(
state: *const State,
Expand Down
32 changes: 31 additions & 1 deletion std/src/std/float.inko
Expand Up @@ -218,6 +218,18 @@ class builtin Float {
fn pub to_bits -> Int {
_INKO.float_to_bits(self)
}

# Returns `true` if `self` has a negative sign, including `-0.0`, NaNs with a
# negative sign bit, and negative infinity.
fn pub negative_sign? -> Bool {
to_bits & MIN != 0
}

# Returns `true` if `self` has a positive sign, including `+0.0`, NaNs with a
# positive sign bit, and positive infinity.
fn pub positive_sign? -> Bool {
negative_sign?.false?
}
}

impl ToInt for Float {
Expand Down Expand Up @@ -297,8 +309,26 @@ impl Compare[Float] for Float {
}

impl Equal[Float] for Float {
# Returns `true` if `self` and `other` are equal to each other.
#
# This method uses "Units in the Last Place" or ULPs to perform an approximate
# comparison when two values aren't exactly identical. This means most common
# floats _can_ be compared for equality and give consistent results, but you
# still shouldn't rely on it _always_ being accurate. Or to put it
# differently, if you need 100% accuracy, you should use either `Int` or some
# other data type.
#
# See https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/
# for more details on how ULPs work.
fn pub ==(other: ref Float) -> Bool {
_INKO.float_eq(self, other)
# Handle simple comparisons such as `1.2 == 1.2` and `0.0 == -0.0`.
if _INKO.float_eq(self, other) { return true }
if positive_sign? != other.positive_sign? { return false }
if not_a_number? or other.not_a_number? { return false }

let diff = to_bits.wrapping_sub(other.to_bits)

diff >= -1 and diff <= 1
}
}

Expand Down
25 changes: 25 additions & 0 deletions std/test/std/test_float.inko
Expand Up @@ -189,8 +189,15 @@ fn pub tests(t: mut Tests) {

t.test('Float.==') fn (t) {
t.equal(10.2, 10.2)
t.equal(-10.2, -10.2)
t.equal(Float.infinity, Float.infinity)
t.equal(0.0, 0.0)
t.equal(0.0, -0.0)
t.equal(10.2999999999999999998, 10.3)
t.not_equal(Float.not_a_number, Float.not_a_number)
t.not_equal(10.299999999999998, 10.3)
t.not_equal(-10.2, 10.2)
t.not_equal(Float.infinity, Float.negative_infinity)
}

t.test('Float.to_string') fn (t) {
Expand All @@ -216,4 +223,22 @@ fn pub tests(t: mut Tests) {
t.equal(fmt(Float.infinity), 'Infinity')
t.equal(fmt(Float.negative_infinity), '-Infinity')
}

t.test('Float.negative_sign?') fn (t) {
t.true(-1.2.negative_sign?)
t.true(-0.0.negative_sign?)
t.true(Float.negative_infinity.negative_sign?)
t.false(1.2.negative_sign?)
t.false(0.0.negative_sign?)
t.false(Float.infinity.negative_sign?)
}

t.test('Float.positive_sign?') fn (t) {
t.true(1.2.positive_sign?)
t.true(0.0.positive_sign?)
t.true(Float.infinity.positive_sign?)
t.false(-1.2.positive_sign?)
t.false(-0.0.positive_sign?)
t.false(Float.negative_infinity.positive_sign?)
}
}

0 comments on commit 449af85

Please sign in to comment.