Skip to content
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

feat(stdlib): isNaN and isFinite utility functions for Number #729

Merged
merged 8 commits into from
Jun 15, 2021
30 changes: 30 additions & 0 deletions compiler/test/stdlib/number.test.gr
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,33 @@ assert Number.abs(-1/2) == 1/2
assert Number.neg(-25.5) == -25.5
assert Number.neg(25.5) == -25.5
assert Number.neg(1/2) == -1/2

// isFinite
assert Number.isFinite(0.0/0.0) == false // NaN
assert Number.isFinite(1.0/0.0) == false // infinity
assert Number.isFinite(-1.0/0.0) == false // -infinity
assert Number.isFinite(1)
assert Number.isFinite(1.0)
assert Number.isFinite(0)
assert Number.isFinite(0.0)
assert Number.isFinite(-1)
assert Number.isFinite(-1.0)
assert Number.isFinite(25.76)
assert Number.isFinite(-25.00)
assert Number.isFinite(1/2)
assert Number.isFinite(-1/2)

// isNaN
assert Number.isNaN(0.0/0.0)
assert Number.isNaN(1) == false
assert Number.isNaN(1.0) == false
assert Number.isNaN(0) == false
assert Number.isNaN(0.0) == false
assert Number.isNaN(-1) == false
assert Number.isNaN(-1.0) == false
assert Number.isNaN(25.76) == false
assert Number.isNaN(-25.00) == false
assert Number.isNaN(1/2) == false
assert Number.isNaN(-1/2) == false
assert Number.isNaN(1.0/0.0) == false // infinity
assert Number.isNaN(-1.0/0.0) == false // -infinity
64 changes: 62 additions & 2 deletions stdlib/number.gr
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import WasmI32 from "runtime/unsafe/wasmi32"
import WasmI64 from "runtime/unsafe/wasmi64"
import WasmF32 from "runtime/unsafe/wasmf32"
import WasmF64 from "runtime/unsafe/wasmf64"
import {
coerceNumberToWasmF64,
reducedInteger,
isFloat
isFloat,
isBoxedNumber
} from "runtime/numbers"
import { newFloat64, newInt64 } from "runtime/dataStructures"

import Tags from "runtime/unsafe/tags"

/**
* Computes the sum of its operands.
Expand Down Expand Up @@ -135,3 +137,61 @@ export let abs = (x: Number) => if (0 > x) x * -1 else x
* @returns Number
*/
export let neg = (x: Number) => if (x > 0) x * -1 else x

/**
* Checks if a number is finite.
* All values are finite exept for floating point NaN, infinity or negative infinity.
* @param x: Number - The number to check
* @returns Bool
*/
@disableGC
export let isFinite = (x: Number) => {
let asPtr = WasmI32.fromGrain(x)
if (isBoxedNumber(asPtr)) {
// Boxed numbers can have multiple subtypes, Of which float32 and float64 can be infinite,
let tag = WasmI32.load(asPtr, 4n)
if (WasmI32.eq(tag, Tags._GRAIN_FLOAT64_BOXED_NUM_TAG)) {
let wf64 = WasmF64.load(asPtr, 8n)
WasmF64.eq(WasmF64.sub(wf64, wf64), 0.W)
} else if (WasmI32.eq(tag, Tags._GRAIN_FLOAT32_BOXED_NUM_TAG)) {
let wf32 = WasmF32.load(asPtr, 8n)
WasmF32.eq(WasmF32.sub(wf32, wf32), 0.w)
} else {
// Neither rational numbers nor boxed integers can be infinite or NaN.
// Grain doesn't allow creating a rational with denominator of zero either.
true
}
} else {
// Simple numbers are integers and cannot be infinite.
true
}
}

/**
* Checks if a number contains the NaN value (Not A Number).
* Only boxed floating point numbers can contain NaN.
* @param x: Number - The number to check
* @returns Bool
*/
@disableGC
export let isNaN = (n: Number) => {
let x = WasmI32.fromGrain(n)
if (isBoxedNumber(x)) {
// Boxed numbers can have multiple subtypes. Of which float32 and float64 can be NaN,
let tag = WasmI32.load(x, 4n)
if (WasmI32.eq(tag, Tags._GRAIN_FLOAT64_BOXED_NUM_TAG)) {
let wf64 = WasmF64.load(x, 8n)
WasmF64.ne(wf64, wf64)
} else if (WasmI32.eq(tag, Tags._GRAIN_FLOAT32_BOXED_NUM_TAG)) {
let wf32 = WasmF32.load(x, 8n)
WasmF32.ne(wf32, wf32)
} else {
// Neither rational numbers nor boxed integers can be infinite or NaN.
// Grain doesn't allow creating a rational with denominator of zero either.
false
}
} else {
// Simple numbers are integers and cannot be NaN.
false
}
}
2 changes: 1 addition & 1 deletion stdlib/runtime/numbers.gr
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ let isSimpleNumber = (x) => {
WasmI32.eq(WasmI32.and(x, Tags._GRAIN_NUMBER_TAG_MASK), Tags._GRAIN_NUMBER_TAG_TYPE)
}

let isBoxedNumber = (x) => {
export let isBoxedNumber = (x) => {
if (WasmI32.eq(WasmI32.and(x, Tags._GRAIN_GENERIC_TAG_MASK), Tags._GRAIN_GENERIC_HEAP_TAG_TYPE)) {
WasmI32.eq(WasmI32.load(x, 0n), Tags._GRAIN_BOXED_NUM_HEAP_TAG)
} else {
Expand Down