Skip to content

Commit 0af0669

Browse files
authored
feat(stdlib): Implement isFloat, isInteger & isRational in Number module (#1393)
fix(compiler): Correctly handle underscores in bigint literals
1 parent b73d9bf commit 0af0669

File tree

7 files changed

+206
-17
lines changed

7 files changed

+206
-17
lines changed

compiler/src/utils/literals.re

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -138,13 +138,18 @@ let conv_bigint = s =>
138138
let accBits = ref(0);
139139
let results = ref([]);
140140
for (i in String.length(s) - 1 downto first) {
141-
let digit = Int64.of_int(digit_value(s.[i]));
142-
acc := Int64.logor(acc^, Int64.shift_left(digit, accBits^));
143-
accBits := accBits^ + bits;
144-
if (accBits^ >= 64) {
145-
results := [acc^, ...results^];
146-
accBits := accBits^ - 64;
147-
acc := Int64.shift_right_logical(digit, bits - accBits^);
141+
switch (s.[i]) {
142+
| ('0' .. '9' | 'a' .. 'f' | 'A' .. 'F') as digit =>
143+
let digit = Int64.of_int(digit_value(digit));
144+
acc := Int64.logor(acc^, Int64.shift_left(digit, accBits^));
145+
accBits := accBits^ + bits;
146+
if (accBits^ >= 64) {
147+
results := [acc^, ...results^];
148+
accBits := accBits^ - 64;
149+
acc := Int64.shift_right_logical(digit, bits - accBits^);
150+
};
151+
| '_' => ()
152+
| _ => failwith("Impossible: Bad char")
148153
};
149154
};
150155
if (Int64.unsigned_compare(acc^, Int64.zero) > 0) {
@@ -171,21 +176,26 @@ let conv_bigint = s =>
171176
};
172177
let get_chunk = ((start_idx, end_idx)) => {
173178
let result = ref(Int64.zero);
179+
let valid_digits = ref(0);
174180
for (i in start_idx to end_idx - 1) {
175-
result :=
176-
Int64.add(
177-
Int64.mul(result^, Int64.of_int(base)),
178-
Int64.of_int(digit_value(s.[i])),
179-
);
181+
let digit = s.[i];
182+
if (digit != '_') {
183+
incr(valid_digits);
184+
result :=
185+
Int64.add(
186+
Int64.mul(result^, Int64.of_int(base)),
187+
Int64.of_int(digit_value(s.[i])),
188+
);
189+
};
180190
};
181-
result^;
191+
(result^, valid_digits^);
182192
};
183193
// factor == 10l ** (places_at_a_time)
184-
let factor =
194+
let get_factor = num_digits =>
185195
List.fold_left(
186196
Int32.mul,
187197
Int32.one,
188-
List.init(places_at_a_time, n => Int32.of_int(base)),
198+
List.init(num_digits, n => Int32.of_int(base)),
189199
);
190200
let chunks =
191201
List.map(
@@ -194,10 +204,10 @@ let conv_bigint = s =>
194204
);
195205
let result =
196206
List.fold_left(
197-
(acc, chunk) => {
207+
(acc, (chunk, num_digits)) => {
198208
let ret =
199209
Mini_bigint.unsigned_add_i64(
200-
Mini_bigint.unsigned_mul_i32(acc, factor),
210+
Mini_bigint.unsigned_mul_i32(acc, get_factor(num_digits)),
201211
chunk,
202212
);
203213
ret;

compiler/test/stdlib/number.test.gr

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,30 @@ assert Number.neg(1/2) == -1/2
124124
assert Number.neg(BI.toNumber(1234t)) == BI.toNumber(-1234t)
125125
assert Number.neg(BI.toNumber(-1234t)) == BI.toNumber(1234t)
126126

127+
// isFloat
128+
assert Number.isFloat(0.0)
129+
assert Number.isFloat(1.5)
130+
assert Number.isFloat(42.)
131+
assert Number.isFloat(9e6)
132+
assert Number.isFloat(0) == false
133+
assert Number.isFloat(10) == false
134+
assert Number.isFloat(2/3) == false
135+
136+
// isInteger
137+
assert Number.isInteger(0)
138+
assert Number.isInteger(9)
139+
assert Number.isInteger(2_147_483_648)
140+
assert Number.isInteger(9_223_372_036_854_775_808)
141+
assert Number.isInteger(0.0) == false
142+
assert Number.isInteger(9e6) == false
143+
assert Number.isInteger(2/3) == false
144+
145+
// isRational
146+
assert Number.isRational(1/2)
147+
assert Number.isRational(6/5)
148+
assert Number.isRational(1) == false
149+
assert Number.isRational(1.5) == false
150+
127151
// isFinite
128152
assert Number.isFinite(0.0 / 0.0) == false // NaN
129153
assert Number.isFinite(1.0 / 0.0) == false // infinity

compiler/test/suites/numbers.re

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@ describe("numbers", ({test, testSkip}) => {
4141
assertRun("number_syntax8", "print(1.2e2)", "120.0\n");
4242
assertRun("number_syntax9", "print(1l)", "1\n");
4343
assertRun("number_syntax10", "print(1L)", "1\n");
44+
assertRun(
45+
"number_syntax11",
46+
"print(9_223_372_036_854_775_808)",
47+
"9223372036854775808\n",
48+
);
4449
assertRun(
4550
"number_shift_promote",
4651
"print(5 << 64)",

stdlib/number.gr

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import {
1313
coerceNumberToWasmF64,
1414
reducedInteger,
1515
isFloat,
16+
isInteger,
17+
isRational,
1618
isBoxedNumber,
1719
} from "runtime/numbers"
1820
import { parseInt } from "runtime/stringUtils"
@@ -232,6 +234,45 @@ export let abs = (x: Number) => if (0 > x) x * -1 else x
232234
*/
233235
export let neg = (x: Number) => x * -1
234236

237+
/**
238+
* Checks if a number is a floating point value.
239+
*
240+
* @param x: The number to check
241+
* @returns `true` if the value is a floating point number or `false` otherwise
242+
*
243+
* @since v0.5.3
244+
*/
245+
@unsafe
246+
export let isFloat = (x: Number) => {
247+
isFloat(WasmI32.fromGrain(x))
248+
}
249+
250+
/**
251+
* Checks if a number is an integer.
252+
*
253+
* @param x: The number to check
254+
* @returns `true` if the value is an integer or `false` otherwise
255+
*
256+
* @since v0.5.3
257+
*/
258+
@unsafe
259+
export let isInteger = (x: Number) => {
260+
isInteger(WasmI32.fromGrain(x))
261+
}
262+
263+
/**
264+
* Checks if a number is a non-integer rational value.
265+
*
266+
* @param x: The number to check
267+
* @returns `true` if the value is a non-integer rational number or `false` otherwise
268+
*
269+
* @since v0.5.3
270+
*/
271+
@unsafe
272+
export let isRational = (x: Number) => {
273+
isRational(WasmI32.fromGrain(x))
274+
}
275+
235276
/**
236277
* Checks if a number is finite.
237278
* All values are finite exept for floating point NaN, infinity or negative infinity.

stdlib/number.md

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,81 @@ Returns:
425425
|----|-----------|
426426
|`Number`|The negated operand|
427427

428+
### Number.**isFloat**
429+
430+
<details disabled>
431+
<summary tabindex="-1">Added in <code>next</code></summary>
432+
No other changes yet.
433+
</details>
434+
435+
```grain
436+
isFloat : Number -> Bool
437+
```
438+
439+
Checks if a number is a floating point value.
440+
441+
Parameters:
442+
443+
|param|type|description|
444+
|-----|----|-----------|
445+
|`x`|`Number`|The number to check|
446+
447+
Returns:
448+
449+
|type|description|
450+
|----|-----------|
451+
|`Bool`|`true` if the value is a floating point number or `false` otherwise|
452+
453+
### Number.**isInteger**
454+
455+
<details disabled>
456+
<summary tabindex="-1">Added in <code>next</code></summary>
457+
No other changes yet.
458+
</details>
459+
460+
```grain
461+
isInteger : Number -> Bool
462+
```
463+
464+
Checks if a number is an integer.
465+
466+
Parameters:
467+
468+
|param|type|description|
469+
|-----|----|-----------|
470+
|`x`|`Number`|The number to check|
471+
472+
Returns:
473+
474+
|type|description|
475+
|----|-----------|
476+
|`Bool`|`true` if the value is an integer or `false` otherwise|
477+
478+
### Number.**isRational**
479+
480+
<details disabled>
481+
<summary tabindex="-1">Added in <code>next</code></summary>
482+
No other changes yet.
483+
</details>
484+
485+
```grain
486+
isRational : Number -> Bool
487+
```
488+
489+
Checks if a number is a non-integer rational value.
490+
491+
Parameters:
492+
493+
|param|type|description|
494+
|-----|----|-----------|
495+
|`x`|`Number`|The number to check|
496+
497+
Returns:
498+
499+
|type|description|
500+
|----|-----------|
501+
|`Bool`|`true` if the value is a non-integer rational number or `false` otherwise|
502+
428503
### Number.**isFinite**
429504

430505
<details disabled>

stdlib/runtime/numbers.gr

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,28 @@ export let isFloat = x => {
8484
}
8585
}
8686

87+
@unsafe
88+
export let isInteger = x => {
89+
if (isBoxedNumber(x)) {
90+
let tag = WasmI32.load(x, 4n)
91+
WasmI32.eq(tag, Tags._GRAIN_INT32_BOXED_NUM_TAG) ||
92+
WasmI32.eq(tag, Tags._GRAIN_INT64_BOXED_NUM_TAG) ||
93+
WasmI32.eq(tag, Tags._GRAIN_BIGINT_BOXED_NUM_TAG)
94+
} else {
95+
true
96+
}
97+
}
98+
99+
@unsafe
100+
export let isRational = x => {
101+
if (isBoxedNumber(x)) {
102+
let tag = WasmI32.load(x, 4n)
103+
WasmI32.eq(tag, Tags._GRAIN_RATIONAL_BOXED_NUM_TAG)
104+
} else {
105+
false
106+
}
107+
}
108+
87109
@unsafe
88110
let isBigInt = x => {
89111
if (isBoxedNumber(x)) {

stdlib/runtime/numbers.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,18 @@ isBoxedNumber : WasmI32 -> Bool
1010
isFloat : WasmI32 -> Bool
1111
```
1212

13+
### Numbers.**isInteger**
14+
15+
```grain
16+
isInteger : WasmI32 -> Bool
17+
```
18+
19+
### Numbers.**isRational**
20+
21+
```grain
22+
isRational : WasmI32 -> Bool
23+
```
24+
1325
### Numbers.**isNumber**
1426

1527
```grain

0 commit comments

Comments
 (0)