@@ -107,6 +107,29 @@ export let isRational = x => {
107107 }
108108}
109109
110+ @unsafe
111+ export let isNaN = x => {
112+ if (isBoxedNumber(x)) {
113+ // Boxed numbers can have multiple subtypes, of which float32 and float64 can be NaN.
114+ let tag = WasmI32.load(x, 4n)
115+ if (WasmI32.eq(tag, Tags._GRAIN_FLOAT64_BOXED_NUM_TAG)) {
116+ // uses the fact that NaN is the only number not equal to itself
117+ let wf64 = WasmF64.load(x, 8n)
118+ WasmF64.ne(wf64, wf64)
119+ } else if (WasmI32.eq(tag, Tags._GRAIN_FLOAT32_BOXED_NUM_TAG)) {
120+ let wf32 = WasmF32.load(x, 8n)
121+ WasmF32.ne(wf32, wf32)
122+ } else {
123+ // Neither rational numbers nor boxed integers can be infinite or NaN.
124+ // Grain doesn't allow creating a rational with denominator of zero either.
125+ false
126+ }
127+ } else {
128+ // Simple numbers are integers and cannot be NaN.
129+ false
130+ }
131+ }
132+
110133@unsafe
111134let isBigInt = x => {
112135 if (isBoxedNumber(x)) {
@@ -1777,8 +1800,11 @@ let cmpBigInt = (x: WasmI32, y: WasmI32) => {
17771800 }
17781801}
17791802
1803+ // cmpFloat applies a total ordering relation:
1804+ // unlike regular float logic, NaN is considered equal to itself and
1805+ // smaller than any other number
17801806@unsafe
1781- let cmpFloat = (x: WasmI32, y: WasmI32, is64: Bool, totalOrdering: Bool ) => {
1807+ let cmpFloat = (x: WasmI32, y: WasmI32, is64: Bool) => {
17821808 let xf = if (is64) {
17831809 boxedFloat64Number(x)
17841810 } else {
@@ -1787,13 +1813,13 @@ let cmpFloat = (x: WasmI32, y: WasmI32, is64: Bool, totalOrdering: Bool) => {
17871813 if (isSimpleNumber(y)) {
17881814 let yf = WasmF64.convertI32S(untagSimple(y))
17891815 // special NaN cases
1790- if (totalOrdering && WasmF64.ne(xf, xf)) {
1816+ if (WasmF64.ne(xf, xf)) {
17911817 if (WasmF64.ne(yf, yf)) {
17921818 0n
17931819 } else {
17941820 -1n
17951821 }
1796- } else if (totalOrdering && WasmF64.ne(yf, yf)) {
1822+ } else if (WasmF64.ne(yf, yf)) {
17971823 if (WasmF64.ne(xf, xf)) {
17981824 0n
17991825 } else {
@@ -1834,13 +1860,13 @@ let cmpFloat = (x: WasmI32, y: WasmI32, is64: Bool, totalOrdering: Bool) => {
18341860 },
18351861 }
18361862 // special NaN cases
1837- if (totalOrdering && WasmF64.ne(xf, xf)) {
1863+ if (WasmF64.ne(xf, xf)) {
18381864 if (WasmF64.ne(yf, yf)) {
18391865 0n
18401866 } else {
18411867 -1n
18421868 }
1843- } else if (totalOrdering && WasmF64.ne(yf, yf)) {
1869+ } else if (WasmF64.ne(yf, yf)) {
18441870 if (WasmF64.ne(xf, xf)) {
18451871 0n
18461872 } else {
@@ -1854,7 +1880,7 @@ let cmpFloat = (x: WasmI32, y: WasmI32, is64: Bool, totalOrdering: Bool) => {
18541880}
18551881
18561882@unsafe
1857- let cmpSmallInt = (x: WasmI32, y: WasmI32, is64: Bool, totalOrdering: Bool ) => {
1883+ let cmpSmallInt = (x: WasmI32, y: WasmI32, is64: Bool) => {
18581884 let xi = if (is64) {
18591885 boxedInt64Number(x)
18601886 } else {
@@ -1890,10 +1916,10 @@ let cmpSmallInt = (x: WasmI32, y: WasmI32, is64: Bool, totalOrdering: Bool) => {
18901916 ) -1n else 1n
18911917 },
18921918 t when WasmI32.eq(t, Tags._GRAIN_FLOAT32_BOXED_NUM_TAG) => {
1893- WasmI32.sub(0n, cmpFloat(y, x, false, totalOrdering ))
1919+ WasmI32.sub(0n, cmpFloat(y, x, false))
18941920 },
18951921 t when WasmI32.eq(t, Tags._GRAIN_FLOAT64_BOXED_NUM_TAG) => {
1896- WasmI32.sub(0n, cmpFloat(y, x, true, totalOrdering ))
1922+ WasmI32.sub(0n, cmpFloat(y, x, true))
18971923 },
18981924 _ => {
18991925 throw UnknownNumberTag
@@ -1903,7 +1929,7 @@ let cmpSmallInt = (x: WasmI32, y: WasmI32, is64: Bool, totalOrdering: Bool) => {
19031929}
19041930
19051931@unsafe
1906- let cmpRational = (x: WasmI32, y: WasmI32, totalOrdering: Bool ) => {
1932+ let cmpRational = (x: WasmI32, y: WasmI32) => {
19071933 if (isSimpleNumber(y)) {
19081934 let xf = WasmF64.div(
19091935 BI.toFloat64(boxedRationalNumerator(x)),
@@ -1915,10 +1941,10 @@ let cmpRational = (x: WasmI32, y: WasmI32, totalOrdering: Bool) => {
19151941 let yBoxedNumberTag = boxedNumberTag(y)
19161942 match (yBoxedNumberTag) {
19171943 t when WasmI32.eq(t, Tags._GRAIN_INT32_BOXED_NUM_TAG) => {
1918- WasmI32.sub(0n, cmpSmallInt(y, x, false, totalOrdering ))
1944+ WasmI32.sub(0n, cmpSmallInt(y, x, false))
19191945 },
19201946 t when WasmI32.eq(t, Tags._GRAIN_INT64_BOXED_NUM_TAG) => {
1921- WasmI32.sub(0n, cmpSmallInt(y, x, true, totalOrdering ))
1947+ WasmI32.sub(0n, cmpSmallInt(y, x, true))
19221948 },
19231949 t when WasmI32.eq(t, Tags._GRAIN_BIGINT_BOXED_NUM_TAG) => {
19241950 WasmI32.sub(0n, cmpBigInt(y, x))
@@ -1951,10 +1977,10 @@ let cmpRational = (x: WasmI32, y: WasmI32, totalOrdering: Bool) => {
19511977 }
19521978 },
19531979 t when WasmI32.eq(t, Tags._GRAIN_FLOAT32_BOXED_NUM_TAG) => {
1954- WasmI32.sub(0n, cmpFloat(y, x, false, totalOrdering ))
1980+ WasmI32.sub(0n, cmpFloat(y, x, false))
19551981 },
19561982 t when WasmI32.eq(t, Tags._GRAIN_FLOAT64_BOXED_NUM_TAG) => {
1957- WasmI32.sub(0n, cmpFloat(y, x, true, totalOrdering ))
1983+ WasmI32.sub(0n, cmpFloat(y, x, true))
19581984 },
19591985 _ => {
19601986 throw UnknownNumberTag
@@ -1964,30 +1990,31 @@ let cmpRational = (x: WasmI32, y: WasmI32, totalOrdering: Bool) => {
19641990}
19651991
19661992@unsafe
1967- export let cmp = (x: WasmI32, y: WasmI32, totalOrdering: Bool ) => {
1993+ export let cmp = (x: WasmI32, y: WasmI32) => {
19681994 if (isSimpleNumber(x)) {
19691995 if (isSimpleNumber(y)) {
1970- if (WasmI32.ltS(x, y)) -1n else if (WasmI32.gtS(x, y)) 1n else 0n
1996+ // fast comparison path for simple numbers
1997+ WasmI32.sub(x, y)
19711998 } else {
19721999 let yBoxedNumberTag = boxedNumberTag(y)
19732000 match (yBoxedNumberTag) {
19742001 t when WasmI32.eq(t, Tags._GRAIN_INT32_BOXED_NUM_TAG) => {
1975- WasmI32.sub(0n, cmpSmallInt(y, x, false, totalOrdering ))
2002+ WasmI32.sub(0n, cmpSmallInt(y, x, false))
19762003 },
19772004 t when WasmI32.eq(t, Tags._GRAIN_INT64_BOXED_NUM_TAG) => {
1978- WasmI32.sub(0n, cmpSmallInt(y, x, true, totalOrdering ))
2005+ WasmI32.sub(0n, cmpSmallInt(y, x, true))
19792006 },
19802007 t when WasmI32.eq(t, Tags._GRAIN_BIGINT_BOXED_NUM_TAG) => {
19812008 WasmI32.sub(0n, cmpBigInt(y, x))
19822009 },
19832010 t when WasmI32.eq(t, Tags._GRAIN_RATIONAL_BOXED_NUM_TAG) => {
1984- WasmI32.sub(0n, cmpRational(y, x, totalOrdering ))
2011+ WasmI32.sub(0n, cmpRational(y, x))
19852012 },
19862013 t when WasmI32.eq(t, Tags._GRAIN_FLOAT32_BOXED_NUM_TAG) => {
1987- WasmI32.sub(0n, cmpFloat(y, x, false, totalOrdering ))
2014+ WasmI32.sub(0n, cmpFloat(y, x, false))
19882015 },
19892016 t when WasmI32.eq(t, Tags._GRAIN_FLOAT64_BOXED_NUM_TAG) => {
1990- WasmI32.sub(0n, cmpFloat(y, x, true, totalOrdering ))
2017+ WasmI32.sub(0n, cmpFloat(y, x, true))
19912018 },
19922019 _ => {
19932020 throw UnknownNumberTag
@@ -1998,22 +2025,22 @@ export let cmp = (x: WasmI32, y: WasmI32, totalOrdering: Bool) => {
19982025 let xBoxedNumberTag = boxedNumberTag(x)
19992026 match (xBoxedNumberTag) {
20002027 t when WasmI32.eq(t, Tags._GRAIN_INT32_BOXED_NUM_TAG) => {
2001- cmpSmallInt(x, y, false, totalOrdering )
2028+ cmpSmallInt(x, y, false)
20022029 },
20032030 t when WasmI32.eq(t, Tags._GRAIN_INT64_BOXED_NUM_TAG) => {
2004- cmpSmallInt(x, y, true, totalOrdering )
2031+ cmpSmallInt(x, y, true)
20052032 },
20062033 t when WasmI32.eq(t, Tags._GRAIN_BIGINT_BOXED_NUM_TAG) => {
20072034 cmpBigInt(x, y)
20082035 },
20092036 t when WasmI32.eq(t, Tags._GRAIN_RATIONAL_BOXED_NUM_TAG) => {
2010- cmpRational(x, y, totalOrdering )
2037+ cmpRational(x, y)
20112038 },
20122039 t when WasmI32.eq(t, Tags._GRAIN_FLOAT32_BOXED_NUM_TAG) => {
2013- cmpFloat(x, y, false, totalOrdering )
2040+ cmpFloat(x, y, false)
20142041 },
20152042 t when WasmI32.eq(t, Tags._GRAIN_FLOAT64_BOXED_NUM_TAG) => {
2016- cmpFloat(x, y, true, totalOrdering )
2043+ cmpFloat(x, y, true)
20172044 },
20182045 _ => {
20192046 throw UnknownNumberTag
@@ -2022,39 +2049,46 @@ export let cmp = (x: WasmI32, y: WasmI32, totalOrdering: Bool) => {
20222049 }
20232050}
20242051
2052+ // In the comparison functions below, NaN is neither greater than, less than,
2053+ // or equal to any other number (including NaN), so any comparison involving
2054+ // NaN is always false. The only exception to this rule is `compare`, which
2055+ // applies a total ordering relation to allow numbers to be sortable (with
2056+ // NaN being considered equal to itself and less than all other numbers in
2057+ // this case).
2058+
20252059@unsafe
20262060export let (<) = (x: Number, y: Number) => {
20272061 let x = WasmI32.fromGrain(x)
20282062 let y = WasmI32.fromGrain(y)
2029- WasmI32.ltS(cmp(x, y, false ), 0n)
2063+ !isNaN(x) && !isNaN(y) && WasmI32.ltS(cmp(x, y), 0n)
20302064}
20312065
20322066@unsafe
20332067export let (>) = (x: Number, y: Number) => {
20342068 let x = WasmI32.fromGrain(x)
20352069 let y = WasmI32.fromGrain(y)
2036- WasmI32.gtS(cmp(x, y, false ), 0n)
2070+ !isNaN(x) && !isNaN(y) && WasmI32.gtS(cmp(x, y), 0n)
20372071}
20382072
20392073@unsafe
20402074export let (<=) = (x: Number, y: Number) => {
20412075 let x = WasmI32.fromGrain(x)
20422076 let y = WasmI32.fromGrain(y)
2043- WasmI32.leS(cmp(x, y, false ), 0n)
2077+ !isNaN(x) && !isNaN(y) && WasmI32.leS(cmp(x, y), 0n)
20442078}
20452079
20462080@unsafe
20472081export let (>=) = (x: Number, y: Number) => {
20482082 let x = WasmI32.fromGrain(x)
20492083 let y = WasmI32.fromGrain(y)
2050- WasmI32.geS(cmp(x, y, false ), 0n)
2084+ !isNaN(x) && !isNaN(y) && WasmI32.geS(cmp(x, y), 0n)
20512085}
20522086
20532087@unsafe
20542088export let compare = (x: Number, y: Number) => {
20552089 let x = WasmI32.fromGrain(x)
20562090 let y = WasmI32.fromGrain(y)
2057- WasmI32.toGrain(tagSimple(cmp(x, y, true ))): Number
2091+ WasmI32.toGrain(tagSimple(cmp(x, y))): Number
20582092}
20592093
20602094/*
0 commit comments