@grain/stdlib 0.5.2 → 0.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -20,6 +20,7 @@ primitive (||): (Bool, Bool) -> Bool = "@or"
20
20
  primitive throw: Exception -> a = "@throw"
21
21
 
22
22
  exception UnknownNumberTag
23
+ exception InvariantViolation
23
24
 
24
25
  import {
25
26
  newRational,
@@ -84,6 +85,28 @@ export let isFloat = x => {
84
85
  }
85
86
  }
86
87
 
88
+ @unsafe
89
+ export let isInteger = x => {
90
+ if (isBoxedNumber(x)) {
91
+ let tag = WasmI32.load(x, 4n)
92
+ WasmI32.eq(tag, Tags._GRAIN_INT32_BOXED_NUM_TAG) ||
93
+ WasmI32.eq(tag, Tags._GRAIN_INT64_BOXED_NUM_TAG) ||
94
+ WasmI32.eq(tag, Tags._GRAIN_BIGINT_BOXED_NUM_TAG)
95
+ } else {
96
+ true
97
+ }
98
+ }
99
+
100
+ @unsafe
101
+ export let isRational = x => {
102
+ if (isBoxedNumber(x)) {
103
+ let tag = WasmI32.load(x, 4n)
104
+ WasmI32.eq(tag, Tags._GRAIN_RATIONAL_BOXED_NUM_TAG)
105
+ } else {
106
+ false
107
+ }
108
+ }
109
+
87
110
  @unsafe
88
111
  let isBigInt = x => {
89
112
  if (isBoxedNumber(x)) {
@@ -635,7 +658,7 @@ let numberEqualRationalHelp = (xptr, y) => {
635
658
  BI.toFloat32(xNumerator),
636
659
  BI.toFloat32(xDenominator)
637
660
  )
638
- // TODO: (#303) maybe we should have some sort of tolerance?
661
+ // TODO(#303): maybe we should have some sort of tolerance?
639
662
  WasmF32.eq(xAsFloat, yBoxedVal)
640
663
  },
641
664
  t when WasmI32.eq(t, Tags._GRAIN_FLOAT64_BOXED_NUM_TAG) => {
@@ -644,7 +667,7 @@ let numberEqualRationalHelp = (xptr, y) => {
644
667
  BI.toFloat64(xNumerator),
645
668
  BI.toFloat64(xDenominator)
646
669
  )
647
- // TODO: (#303) maybe we should have some sort of tolerance?
670
+ // TODO(#303): maybe we should have some sort of tolerance?
648
671
  WasmF64.eq(xAsFloat, yBoxedVal)
649
672
  },
650
673
  _ => {
@@ -686,12 +709,12 @@ let numberEqualFloat64Help = (x, y) => {
686
709
  },
687
710
  t when WasmI32.eq(t, Tags._GRAIN_FLOAT32_BOXED_NUM_TAG) => {
688
711
  let yBoxedVal = boxedFloat32Number(y)
689
- // TODO: (#303) maybe we should have some sort of tolerance?
712
+ // TODO(#303): maybe we should have some sort of tolerance?
690
713
  WasmF64.eq(x, WasmF64.promoteF32(yBoxedVal))
691
714
  },
692
715
  t when WasmI32.eq(t, Tags._GRAIN_FLOAT64_BOXED_NUM_TAG) => {
693
716
  let yBoxedVal = boxedFloat64Number(y)
694
- // TODO: (#303) maybe we should have some sort of tolerance?
717
+ // TODO(#303): maybe we should have some sort of tolerance?
695
718
  WasmF64.eq(x, yBoxedVal)
696
719
  },
697
720
  _ => {
@@ -873,7 +896,6 @@ let numberAddSubSimpleHelp = (x, y, isSub) => {
873
896
  let xval = WasmF32.convertI32S(xval)
874
897
  let result = if (isSub) WasmF32.sub(xval, yBoxedVal)
875
898
  else WasmF32.add(xval, yBoxedVal)
876
- // TODO: (#304) is this safe?
877
899
  newFloat32(result)
878
900
  },
879
901
  t when WasmI32.eq(t, Tags._GRAIN_FLOAT64_BOXED_NUM_TAG) => {
@@ -971,7 +993,6 @@ let numberAddSubInt64Help = (xval, y, isSub) => {
971
993
  let yBoxedVal = boxedFloat32Number(y)
972
994
  let result = if (isSub) WasmF32.sub(xval, yBoxedVal)
973
995
  else WasmF32.add(xval, yBoxedVal)
974
- // TODO(#304): this isn't safe enough
975
996
  newFloat32(result)
976
997
  },
977
998
  t when WasmI32.eq(t, Tags._GRAIN_FLOAT64_BOXED_NUM_TAG) => {
@@ -1083,7 +1104,6 @@ let numberAddSubBigIntHelp = (x, y, isSub) => {
1083
1104
  let yBoxedVal = boxedFloat32Number(y)
1084
1105
  let result = if (isSub) WasmF32.sub(xval, yBoxedVal)
1085
1106
  else WasmF32.add(xval, yBoxedVal)
1086
- // TODO: (#304) this isn't safe enough
1087
1107
  newFloat32(result)
1088
1108
  },
1089
1109
  t when WasmI32.eq(t, Tags._GRAIN_FLOAT64_BOXED_NUM_TAG) => {
@@ -1326,7 +1346,6 @@ let numberTimesDivideInt64Help = (xval, y, isDivide) => {
1326
1346
  t when WasmI32.eq(t, Tags._GRAIN_FLOAT32_BOXED_NUM_TAG) => {
1327
1347
  let xval = WasmF32.convertI64S(xval)
1328
1348
  let yBoxedVal = boxedFloat32Number(y)
1329
- // TODO: (#304) is this safe?
1330
1349
  if (isDivide) {
1331
1350
  newFloat32(WasmF32.div(xval, yBoxedVal))
1332
1351
  } else {
@@ -1407,7 +1426,6 @@ let numberTimesDivideBigIntHelp = (x, y, isDivide) => {
1407
1426
  // we can only fit rather small bigints (<4 limbs) into an F32
1408
1427
  let xval = BI.toFloat64(x)
1409
1428
  let yBoxedVal = WasmF64.promoteF32(boxedFloat32Number(y))
1410
- // TODO: (#304) is this safe?
1411
1429
  if (isDivide) {
1412
1430
  newFloat64(WasmF64.div(xval, yBoxedVal))
1413
1431
  } else {
@@ -1530,7 +1548,6 @@ let numberTimesDivideRationalHelp = (x, y, isDivide) => {
1530
1548
  let yDenominator = boxedRationalDenominator(y)
1531
1549
  // (a / b) * (c / d) == (a * c) / (b * d)
1532
1550
  // (a / b) / (c / d) == (a * d) / (b * c)
1533
- // TODO: (#304) this could maybe be written in a more overflow-proof way
1534
1551
  let numerator = if (isDivide) BI.mul(xNumerator, yDenominator)
1535
1552
  else BI.mul(xNumerator, yNumerator)
1536
1553
  let denominator = if (isDivide) BI.mul(xDenominator, yNumerator)
@@ -1681,9 +1698,18 @@ let numberMod = (x, y) => {
1681
1698
  }
1682
1699
 
1683
1700
  /*
1684
- * ===== LESS THAN / GREATER THAN / LESS EQUAL / GREATER EQUAL =====
1685
- * Coerce to float64 and then do comparisons
1686
- * TODO: (#305) Could probably be made more efficient
1701
+ * ===== COMPARISONS =====
1702
+ * Int/int and float/float comparisons are always accurate.
1703
+ * Rational/rational comparisons are approximations with the exception of
1704
+ * equality, which is always accurate.
1705
+ *
1706
+ * Values compared to floats or rationals are first converted to floats.
1707
+ *
1708
+ * All comparison operators consider NaN not equal to, less than, or greater
1709
+ * than NaN, with the exception of `compare`, which considers NaN equal to
1710
+ * itself and otherwise smaller than any other float value. This provides a
1711
+ * total order (https://en.wikipedia.org/wiki/Total_order) over all numerical
1712
+ * values, making `compare` suitable for sorting or ordering.
1687
1713
  */
1688
1714
 
1689
1715
  @unsafe
@@ -1721,81 +1747,286 @@ let cmpBigInt = (x: WasmI32, y: WasmI32) => {
1721
1747
  }
1722
1748
  }
1723
1749
 
1724
- // TODO: (#305) is this safe? I think it's safe?
1725
1750
  @unsafe
1726
- export let (<) = (x: Number, y: Number) => {
1727
- let xw32 = WasmI32.fromGrain(x)
1728
- let yw32 = WasmI32.fromGrain(y)
1729
- if (isBigInt(xw32)) {
1730
- WasmI32.ltS(cmpBigInt(xw32, yw32), 0n)
1731
- } else if (isBigInt(yw32)) {
1732
- WasmI32.geS(cmpBigInt(yw32, xw32), 0n)
1751
+ let cmpFloat = (x: WasmI32, y: WasmI32, is64: Bool, totalOrdering: Bool) => {
1752
+ let xf = if (is64) {
1753
+ boxedFloat64Number(x)
1733
1754
  } else {
1734
- let xval = coerceNumberToWasmF64(x)
1735
- let yval = coerceNumberToWasmF64(y)
1736
- WasmF64.lt(xval, yval)
1755
+ WasmF64.promoteF32(boxedFloat32Number(x))
1756
+ }
1757
+ if (isSimpleNumber(y)) {
1758
+ let yf = WasmF64.convertI32S(untagSimple(y))
1759
+ // special NaN cases
1760
+ if (totalOrdering && WasmF64.ne(xf, xf)) {
1761
+ if (WasmF64.ne(yf, yf)) {
1762
+ 0n
1763
+ } else {
1764
+ -1n
1765
+ }
1766
+ } else if (totalOrdering && WasmF64.ne(yf, yf)) {
1767
+ if (WasmF64.ne(xf, xf)) {
1768
+ 0n
1769
+ } else {
1770
+ 1n
1771
+ }
1772
+ } else {
1773
+ if (WasmF64.lt(xf, yf)) -1n else if (WasmF64.gt(xf, yf)) 1n else 0n
1774
+ }
1775
+ } else {
1776
+ let yBoxedNumberTag = boxedNumberTag(y)
1777
+ if (yBoxedNumberTag == Tags._GRAIN_BIGINT_BOXED_NUM_TAG) {
1778
+ WasmI32.sub(0n, cmpBigInt(y, x))
1779
+ } else {
1780
+ let yf = match (yBoxedNumberTag) {
1781
+ t when WasmI32.eq(t, Tags._GRAIN_INT32_BOXED_NUM_TAG) => {
1782
+ WasmF64.convertI32S(boxedInt32Number(y))
1783
+ },
1784
+ t when WasmI32.eq(t, Tags._GRAIN_INT64_BOXED_NUM_TAG) => {
1785
+ WasmF64.convertI64S(boxedInt64Number(y))
1786
+ },
1787
+ t when WasmI32.eq(t, Tags._GRAIN_BIGINT_BOXED_NUM_TAG) => {
1788
+ throw InvariantViolation
1789
+ },
1790
+ t when WasmI32.eq(t, Tags._GRAIN_RATIONAL_BOXED_NUM_TAG) => {
1791
+ WasmF64.div(
1792
+ BI.toFloat64(boxedRationalNumerator(y)),
1793
+ BI.toFloat64(boxedRationalDenominator(y))
1794
+ )
1795
+ },
1796
+ t when WasmI32.eq(t, Tags._GRAIN_FLOAT32_BOXED_NUM_TAG) => {
1797
+ WasmF64.promoteF32(boxedFloat32Number(y))
1798
+ },
1799
+ t when WasmI32.eq(t, Tags._GRAIN_FLOAT64_BOXED_NUM_TAG) => {
1800
+ boxedFloat64Number(y)
1801
+ },
1802
+ _ => {
1803
+ throw UnknownNumberTag
1804
+ },
1805
+ }
1806
+ // special NaN cases
1807
+ if (totalOrdering && WasmF64.ne(xf, xf)) {
1808
+ if (WasmF64.ne(yf, yf)) {
1809
+ 0n
1810
+ } else {
1811
+ -1n
1812
+ }
1813
+ } else if (totalOrdering && WasmF64.ne(yf, yf)) {
1814
+ if (WasmF64.ne(xf, xf)) {
1815
+ 0n
1816
+ } else {
1817
+ 1n
1818
+ }
1819
+ } else {
1820
+ if (WasmF64.lt(xf, yf)) -1n else if (WasmF64.gt(xf, yf)) 1n else 0n
1821
+ }
1822
+ }
1737
1823
  }
1738
1824
  }
1739
1825
 
1740
1826
  @unsafe
1741
- export let (>) = (x: Number, y: Number) => {
1742
- let xw32 = WasmI32.fromGrain(x)
1743
- let yw32 = WasmI32.fromGrain(y)
1744
- if (isBigInt(xw32)) {
1745
- WasmI32.gtS(cmpBigInt(xw32, yw32), 0n)
1746
- } else if (isBigInt(yw32)) {
1747
- WasmI32.leS(cmpBigInt(yw32, xw32), 0n)
1827
+ let cmpSmallInt = (x: WasmI32, y: WasmI32, is64: Bool, totalOrdering: Bool) => {
1828
+ let xi = if (is64) {
1829
+ boxedInt64Number(x)
1748
1830
  } else {
1749
- let xval = coerceNumberToWasmF64(x)
1750
- let yval = coerceNumberToWasmF64(y)
1751
- WasmF64.gt(xval, yval)
1831
+ WasmI64.extendI32S(boxedInt32Number(x))
1832
+ }
1833
+ if (isSimpleNumber(y)) {
1834
+ let yi = WasmI64.extendI32S(untagSimple(y))
1835
+ if (WasmI64.ltS(xi, yi)) -1n else if (WasmI64.gtS(xi, yi)) 1n else 0n
1836
+ } else {
1837
+ let yBoxedNumberTag = boxedNumberTag(y)
1838
+ match (yBoxedNumberTag) {
1839
+ t when WasmI32.eq(t, Tags._GRAIN_INT32_BOXED_NUM_TAG) => {
1840
+ let yi = WasmI64.extendI32S(boxedInt32Number(y))
1841
+ if (WasmI64.ltS(xi, yi)) -1n else if (WasmI64.gtS(xi, yi)) 1n else 0n
1842
+ },
1843
+ t when WasmI32.eq(t, Tags._GRAIN_INT64_BOXED_NUM_TAG) => {
1844
+ let yi = boxedInt64Number(y)
1845
+ if (WasmI64.ltS(xi, yi)) -1n else if (WasmI64.gtS(xi, yi)) 1n else 0n
1846
+ },
1847
+ t when WasmI32.eq(t, Tags._GRAIN_BIGINT_BOXED_NUM_TAG) => {
1848
+ WasmI32.sub(0n, cmpBigInt(y, x))
1849
+ },
1850
+ t when WasmI32.eq(t, Tags._GRAIN_RATIONAL_BOXED_NUM_TAG) => {
1851
+ // Rationals and ints are never considered equal
1852
+ if (
1853
+ WasmF64.lt(
1854
+ WasmF64.convertI64S(xi),
1855
+ WasmF64.div(
1856
+ BI.toFloat64(boxedRationalNumerator(y)),
1857
+ BI.toFloat64(boxedRationalDenominator(y))
1858
+ )
1859
+ )
1860
+ ) -1n else 1n
1861
+ },
1862
+ t when WasmI32.eq(t, Tags._GRAIN_FLOAT32_BOXED_NUM_TAG) => {
1863
+ WasmI32.sub(0n, cmpFloat(y, x, false, totalOrdering))
1864
+ },
1865
+ t when WasmI32.eq(t, Tags._GRAIN_FLOAT64_BOXED_NUM_TAG) => {
1866
+ WasmI32.sub(0n, cmpFloat(y, x, true, totalOrdering))
1867
+ },
1868
+ _ => {
1869
+ throw UnknownNumberTag
1870
+ },
1871
+ }
1752
1872
  }
1753
1873
  }
1754
1874
 
1755
1875
  @unsafe
1756
- export let (<=) = (x: Number, y: Number) => {
1757
- let xw32 = WasmI32.fromGrain(x)
1758
- let yw32 = WasmI32.fromGrain(y)
1759
- if (isBigInt(xw32)) {
1760
- WasmI32.leS(cmpBigInt(xw32, yw32), 0n)
1761
- } else if (isBigInt(yw32)) {
1762
- WasmI32.geS(cmpBigInt(yw32, xw32), 0n)
1876
+ let cmpRational = (x: WasmI32, y: WasmI32, totalOrdering: Bool) => {
1877
+ if (isSimpleNumber(y)) {
1878
+ let xf = WasmF64.div(
1879
+ BI.toFloat64(boxedRationalNumerator(x)),
1880
+ BI.toFloat64(boxedRationalDenominator(x))
1881
+ )
1882
+ // Rationals and ints are never considered equal
1883
+ if (WasmF64.lt(xf, WasmF64.convertI32S(untagSimple(y)))) -1n else 1n
1763
1884
  } else {
1764
- // Equality is finicky, so delegate
1765
- let xval = coerceNumberToWasmF64(x)
1766
- let yval = coerceNumberToWasmF64(y)
1767
- if (WasmF64.lt(xval, yval)) {
1768
- true
1769
- } else {
1770
- let x = WasmI32.fromGrain(x)
1771
- let y = WasmI32.fromGrain(y)
1772
- numberEqual(x, y)
1885
+ let yBoxedNumberTag = boxedNumberTag(y)
1886
+ match (yBoxedNumberTag) {
1887
+ t when WasmI32.eq(t, Tags._GRAIN_INT32_BOXED_NUM_TAG) => {
1888
+ WasmI32.sub(0n, cmpSmallInt(y, x, false, totalOrdering))
1889
+ },
1890
+ t when WasmI32.eq(t, Tags._GRAIN_INT64_BOXED_NUM_TAG) => {
1891
+ WasmI32.sub(0n, cmpSmallInt(y, x, true, totalOrdering))
1892
+ },
1893
+ t when WasmI32.eq(t, Tags._GRAIN_BIGINT_BOXED_NUM_TAG) => {
1894
+ WasmI32.sub(0n, cmpBigInt(y, x))
1895
+ },
1896
+ t when WasmI32.eq(t, Tags._GRAIN_RATIONAL_BOXED_NUM_TAG) => {
1897
+ // Comparing rationals efficiently is an open problem
1898
+ // Producing a definitive answer is quite expensive, so if the two
1899
+ // values are not strictly equal we approximate an answer
1900
+
1901
+ let xNumerator = boxedRationalNumerator(x)
1902
+ let xDenominator = boxedRationalDenominator(x)
1903
+ let yNumerator = boxedRationalNumerator(y)
1904
+ let yDenominator = boxedRationalDenominator(y)
1905
+
1906
+ if (
1907
+ BI.cmp(xNumerator, yNumerator) == 0n &&
1908
+ BI.cmp(xDenominator, yDenominator) == 0n
1909
+ ) {
1910
+ 0n
1911
+ } else {
1912
+ let xf = WasmF64.div(
1913
+ BI.toFloat64(xNumerator),
1914
+ BI.toFloat64(xDenominator)
1915
+ )
1916
+ let yf = WasmF64.div(
1917
+ BI.toFloat64(yNumerator),
1918
+ BI.toFloat64(yDenominator)
1919
+ )
1920
+ if (WasmF64.lt(xf, yf)) -1n else 1n
1921
+ }
1922
+ },
1923
+ t when WasmI32.eq(t, Tags._GRAIN_FLOAT32_BOXED_NUM_TAG) => {
1924
+ WasmI32.sub(0n, cmpFloat(y, x, false, totalOrdering))
1925
+ },
1926
+ t when WasmI32.eq(t, Tags._GRAIN_FLOAT64_BOXED_NUM_TAG) => {
1927
+ WasmI32.sub(0n, cmpFloat(y, x, true, totalOrdering))
1928
+ },
1929
+ _ => {
1930
+ throw UnknownNumberTag
1931
+ },
1773
1932
  }
1774
1933
  }
1775
1934
  }
1776
1935
 
1777
1936
  @unsafe
1778
- export let (>=) = (x: Number, y: Number) => {
1779
- let xw32 = WasmI32.fromGrain(x)
1780
- let yw32 = WasmI32.fromGrain(y)
1781
- if (isBigInt(xw32)) {
1782
- WasmI32.leS(cmpBigInt(xw32, yw32), 0n)
1783
- } else if (isBigInt(yw32)) {
1784
- WasmI32.geS(cmpBigInt(yw32, xw32), 0n)
1785
- } else {
1786
- // Equality is finicky, so delegate
1787
- let xval = coerceNumberToWasmF64(x)
1788
- let yval = coerceNumberToWasmF64(y)
1789
- if (WasmF64.gt(xval, yval)) {
1790
- true
1937
+ export let cmp = (x: WasmI32, y: WasmI32, totalOrdering: Bool) => {
1938
+ if (isSimpleNumber(x)) {
1939
+ if (isSimpleNumber(y)) {
1940
+ if (WasmI32.ltS(x, y)) -1n else if (WasmI32.gtS(x, y)) 1n else 0n
1791
1941
  } else {
1792
- let x = WasmI32.fromGrain(x)
1793
- let y = WasmI32.fromGrain(y)
1794
- numberEqual(x, y)
1942
+ let yBoxedNumberTag = boxedNumberTag(y)
1943
+ match (yBoxedNumberTag) {
1944
+ t when WasmI32.eq(t, Tags._GRAIN_INT32_BOXED_NUM_TAG) => {
1945
+ WasmI32.sub(0n, cmpSmallInt(y, x, false, totalOrdering))
1946
+ },
1947
+ t when WasmI32.eq(t, Tags._GRAIN_INT64_BOXED_NUM_TAG) => {
1948
+ WasmI32.sub(0n, cmpSmallInt(y, x, true, totalOrdering))
1949
+ },
1950
+ t when WasmI32.eq(t, Tags._GRAIN_BIGINT_BOXED_NUM_TAG) => {
1951
+ WasmI32.sub(0n, cmpBigInt(y, x))
1952
+ },
1953
+ t when WasmI32.eq(t, Tags._GRAIN_RATIONAL_BOXED_NUM_TAG) => {
1954
+ WasmI32.sub(0n, cmpRational(y, x, totalOrdering))
1955
+ },
1956
+ t when WasmI32.eq(t, Tags._GRAIN_FLOAT32_BOXED_NUM_TAG) => {
1957
+ WasmI32.sub(0n, cmpFloat(y, x, false, totalOrdering))
1958
+ },
1959
+ t when WasmI32.eq(t, Tags._GRAIN_FLOAT64_BOXED_NUM_TAG) => {
1960
+ WasmI32.sub(0n, cmpFloat(y, x, true, totalOrdering))
1961
+ },
1962
+ _ => {
1963
+ throw UnknownNumberTag
1964
+ },
1965
+ }
1966
+ }
1967
+ } else {
1968
+ let xBoxedNumberTag = boxedNumberTag(x)
1969
+ match (xBoxedNumberTag) {
1970
+ t when WasmI32.eq(t, Tags._GRAIN_INT32_BOXED_NUM_TAG) => {
1971
+ cmpSmallInt(x, y, false, totalOrdering)
1972
+ },
1973
+ t when WasmI32.eq(t, Tags._GRAIN_INT64_BOXED_NUM_TAG) => {
1974
+ cmpSmallInt(x, y, true, totalOrdering)
1975
+ },
1976
+ t when WasmI32.eq(t, Tags._GRAIN_BIGINT_BOXED_NUM_TAG) => {
1977
+ cmpBigInt(x, y)
1978
+ },
1979
+ t when WasmI32.eq(t, Tags._GRAIN_RATIONAL_BOXED_NUM_TAG) => {
1980
+ cmpRational(x, y, totalOrdering)
1981
+ },
1982
+ t when WasmI32.eq(t, Tags._GRAIN_FLOAT32_BOXED_NUM_TAG) => {
1983
+ cmpFloat(x, y, false, totalOrdering)
1984
+ },
1985
+ t when WasmI32.eq(t, Tags._GRAIN_FLOAT64_BOXED_NUM_TAG) => {
1986
+ cmpFloat(x, y, true, totalOrdering)
1987
+ },
1988
+ _ => {
1989
+ throw UnknownNumberTag
1990
+ },
1795
1991
  }
1796
1992
  }
1797
1993
  }
1798
1994
 
1995
+ @unsafe
1996
+ export let (<) = (x: Number, y: Number) => {
1997
+ let x = WasmI32.fromGrain(x)
1998
+ let y = WasmI32.fromGrain(y)
1999
+ WasmI32.ltS(cmp(x, y, false), 0n)
2000
+ }
2001
+
2002
+ @unsafe
2003
+ export let (>) = (x: Number, y: Number) => {
2004
+ let x = WasmI32.fromGrain(x)
2005
+ let y = WasmI32.fromGrain(y)
2006
+ WasmI32.gtS(cmp(x, y, false), 0n)
2007
+ }
2008
+
2009
+ @unsafe
2010
+ export let (<=) = (x: Number, y: Number) => {
2011
+ let x = WasmI32.fromGrain(x)
2012
+ let y = WasmI32.fromGrain(y)
2013
+ WasmI32.leS(cmp(x, y, false), 0n)
2014
+ }
2015
+
2016
+ @unsafe
2017
+ export let (>=) = (x: Number, y: Number) => {
2018
+ let x = WasmI32.fromGrain(x)
2019
+ let y = WasmI32.fromGrain(y)
2020
+ WasmI32.geS(cmp(x, y, false), 0n)
2021
+ }
2022
+
2023
+ @unsafe
2024
+ export let compare = (x: Number, y: Number) => {
2025
+ let x = WasmI32.fromGrain(x)
2026
+ let y = WasmI32.fromGrain(y)
2027
+ WasmI32.toGrain(tagSimple(cmp(x, y, true))): Number
2028
+ }
2029
+
1799
2030
  /*
1800
2031
  * ===== EQUAL =====
1801
2032
  */
@@ -1811,7 +2042,7 @@ export let numberEq = (x: Number, y: Number) => {
1811
2042
  * ===== LOGICAL OPERATIONS =====
1812
2043
  * Only valid for int-like numbers. Coerce to i64/bigInt and do operations
1813
2044
  */
1814
- // TODO: (#306) Semantics around when things should stay i32/i64
2045
+ // TODO(#306): Semantics around when things should stay i32/i64
1815
2046
 
1816
2047
  @unsafe
1817
2048
  export let lnot = (x: Number) => {
@@ -1833,7 +2064,15 @@ export let (<<) = (x: Number, y: Number) => {
1833
2064
  } else {
1834
2065
  let xval = coerceNumberToWasmI64(x)
1835
2066
  let yval = coerceNumberToWasmI64(y)
1836
- WasmI32.toGrain(reducedInteger(WasmI64.shl(xval, yval))): Number
2067
+ // if the number will be shifted beyond the end of the i64 range, promote to BigInt
2068
+ // (note that we subtract one leading zero, since the leading bit is the sign bit)
2069
+ if (WasmI64.leU(WasmI64.sub(WasmI64.clz(i64abs(xval)), 1N), yval)) {
2070
+ let xbi = coerceNumberToBigInt(x)
2071
+ let yval = coerceNumberToWasmI32(y)
2072
+ WasmI32.toGrain(reducedBigInteger(BI.shl(xbi, yval))): Number
2073
+ } else {
2074
+ WasmI32.toGrain(reducedInteger(WasmI64.shl(xval, yval))): Number
2075
+ }
1837
2076
  }
1838
2077
  }
1839
2078
 
@@ -10,6 +10,18 @@ isBoxedNumber : WasmI32 -> Bool
10
10
  isFloat : WasmI32 -> Bool
11
11
  ```
12
12
 
13
+ ### Numbers.**isInteger**
14
+
15
+ ```grain
16
+ isInteger : WasmI32 -> Bool
17
+ ```
18
+
19
+ ### Numbers.**isRational**
20
+
21
+ ```grain
22
+ isRational : WasmI32 -> Bool
23
+ ```
24
+
13
25
  ### Numbers.**isNumber**
14
26
 
15
27
  ```grain
@@ -94,6 +106,12 @@ coerceNumberToWasmI32 : Number -> WasmI32
94
106
  numberEqual : (WasmI32, WasmI32) -> Bool
95
107
  ```
96
108
 
109
+ ### Numbers.**cmp**
110
+
111
+ ```grain
112
+ cmp : (WasmI32, WasmI32, Bool) -> WasmI32
113
+ ```
114
+
97
115
  ### Numbers.**(<)**
98
116
 
99
117
  ```grain
@@ -118,6 +136,12 @@ numberEqual : (WasmI32, WasmI32) -> Bool
118
136
  (>=) : (Number, Number) -> Bool
119
137
  ```
120
138
 
139
+ ### Numbers.**compare**
140
+
141
+ ```grain
142
+ compare : (Number, Number) -> Number
143
+ ```
144
+
121
145
  ### Numbers.**numberEq**
122
146
 
123
147
  ```grain
package/set.gr CHANGED
@@ -454,8 +454,7 @@ export let intersect = (set1, set2) => {
454
454
  set
455
455
  }
456
456
 
457
- // TODO: Should return a Record type instead of a Tuple
458
- // Waiting on https://github.com/grain-lang/grain/issues/190
457
+ // TODO(#190): Should return a Record type instead of a Tuple
459
458
  /**
460
459
  * Provides data representing the internal state state of the set.
461
460
  *