Skip to content

Commit e21f2b1

Browse files
jozanzaospencer
andauthored
feat(stdlib): Add parseFloat function to Number module (#1288)
Co-authored-by: Oscar Spencer <oscar@grain-lang.org>
1 parent 900e976 commit e21f2b1

File tree

19 files changed

+4557
-4
lines changed

19 files changed

+4557
-4
lines changed

compiler/test/stdlib/number.test.gr

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,183 @@ assert Number.isInfinite(1/2) == false
389389
assert Number.isInfinite(-1/2) == false
390390
assert Number.isInfinite(BI.toNumber(-1t)) == false
391391

392+
// parseFloat
393+
// tests taken from Go's /src/strconv/atof_test.go
394+
assert Number.parseFloat("") == Err("Invalid string")
395+
assert Number.parseFloat("1") == Ok(1.)
396+
assert Number.parseFloat("+1") == Ok(1.)
397+
assert Number.parseFloat("1x") == Err("Invalid float")
398+
assert Number.parseFloat("1.1.") == Err("Invalid float")
399+
assert Number.parseFloat("1e23") == Ok(1e+23)
400+
assert Number.parseFloat("1E23") == Ok(1e+23)
401+
assert Number.parseFloat("100000000000000000000000") == Ok(1e+23)
402+
assert Number.parseFloat("1e-100") == Ok(1e-100)
403+
assert Number.parseFloat("123456700") == Ok(1.234567e+08)
404+
assert Number.parseFloat("99999999999999974834176") == Ok(9.999999999999997e+22)
405+
assert Number.parseFloat("100000000000000000000001") ==
406+
Ok(1.0000000000000001e+23)
407+
assert Number.parseFloat("100000000000000008388608") ==
408+
Ok(1.0000000000000001e+23)
409+
assert Number.parseFloat("100000000000000016777215") ==
410+
Ok(1.0000000000000001e+23)
411+
assert Number.parseFloat("100000000000000016777216") ==
412+
Ok(1.0000000000000003e+23)
413+
assert Number.parseFloat("-1") == Ok(-1.)
414+
assert Number.parseFloat("-0.1") == Ok(-0.1)
415+
assert Number.parseFloat("-0") == Ok(-0.0)
416+
assert Number.parseFloat("1e-20") == Ok(1e-20)
417+
assert Number.parseFloat("625e-3") == Ok(0.625)
418+
// zeros
419+
assert Number.parseFloat("0") == Ok(0.)
420+
assert Number.parseFloat("0e0") == Ok(0.)
421+
assert Number.parseFloat("-0e0") == Ok(-0.)
422+
assert Number.parseFloat("+0e0") == Ok(0.)
423+
assert Number.parseFloat("0e-0") == Ok(0.)
424+
assert Number.parseFloat("-0e-0") == Ok(-0.)
425+
assert Number.parseFloat("+0e-0") == Ok(0.)
426+
assert Number.parseFloat("0e+0") == Ok(0.)
427+
assert Number.parseFloat("-0e+0") == Ok(-0.)
428+
assert Number.parseFloat("+0e+0") == Ok(0.)
429+
assert Number.parseFloat("0e+01234567890123456789") == Ok(0.)
430+
assert Number.parseFloat("0.00e-01234567890123456789") == Ok(0.)
431+
assert Number.parseFloat("-0e+01234567890123456789") == Ok(-0.)
432+
assert Number.parseFloat("-0.00e-01234567890123456789") == Ok(-0.)
433+
assert Number.parseFloat("0e291") == Ok(0.)
434+
assert Number.parseFloat("0e292") == Ok(0.)
435+
assert Number.parseFloat("0e347") == Ok(0.)
436+
assert Number.parseFloat("0e348") == Ok(0.)
437+
// NaNs
438+
assert Number.isNaN(
439+
Result.expect("float should parse", Number.parseFloat("nan"))
440+
)
441+
assert Number.isNaN(
442+
Result.expect("float should parse", Number.parseFloat("NaN"))
443+
)
444+
assert Number.isNaN(
445+
Result.expect("float should parse", Number.parseFloat("NAN"))
446+
)
447+
// Infs
448+
assert Number.parseFloat("inf") == Ok(Number.infinity)
449+
assert Number.parseFloat("-Inf") == Ok(Number.neg(Number.infinity))
450+
assert Number.parseFloat("+INF") == Ok(Number.infinity)
451+
assert Number.parseFloat("-Infinity") == Ok(Number.neg(Number.infinity))
452+
assert Number.parseFloat("+INFINITY") == Ok(Number.infinity)
453+
assert Number.parseFloat("Infinity") == Ok(Number.infinity)
454+
// largest float64
455+
assert Number.parseFloat("1.7976931348623157e308") ==
456+
Ok(1.7976931348623157e+308)
457+
assert Number.parseFloat("-1.7976931348623157e308") ==
458+
Ok(-1.7976931348623157e+308)
459+
// next float64 - too large
460+
assert Number.parseFloat("1.7976931348623159e308") == Ok(Number.infinity)
461+
assert Number.parseFloat("-1.7976931348623159e308") ==
462+
Ok(Number.neg(Number.infinity))
463+
// the border is ...158079
464+
// borderline - okay
465+
assert Number.parseFloat("1.7976931348623158079e308") ==
466+
Ok(1.7976931348623157e+308)
467+
assert Number.parseFloat("-1.7976931348623158079e308") ==
468+
Ok(-1.7976931348623157e+308)
469+
// borderline - too large
470+
assert Number.parseFloat("1.797693134862315808e308") == Ok(Number.infinity)
471+
assert Number.parseFloat("-1.797693134862315808e308") ==
472+
Ok(Number.neg(Number.infinity))
473+
// a little too large
474+
assert Number.parseFloat("1e308") == Ok(1e+308)
475+
assert Number.parseFloat("2e308") == Ok(Number.infinity)
476+
assert Number.parseFloat("1e309") == Ok(Number.infinity)
477+
// way too large
478+
assert Number.parseFloat("1e310") == Ok(Number.infinity)
479+
assert Number.parseFloat("-1e310") == Ok(Number.neg(Number.infinity))
480+
assert Number.parseFloat("1e400") == Ok(Number.infinity)
481+
assert Number.parseFloat("-1e400") == Ok(Number.neg(Number.infinity))
482+
assert Number.parseFloat("1e400000") == Ok(Number.infinity)
483+
assert Number.parseFloat("-1e400000") == Ok(Number.neg(Number.infinity))
484+
// denormalized
485+
assert Number.parseFloat("1e-305") == Ok(1e-305)
486+
assert Number.parseFloat("1e-306") == Ok(1e-306)
487+
assert Number.parseFloat("1e-307") == Ok(1e-307)
488+
assert Number.parseFloat("1e-308") == Ok(1e-308)
489+
assert Number.parseFloat("1e-309") == Ok(1e-309)
490+
assert Number.parseFloat("1e-310") == Ok(1e-310)
491+
assert Number.parseFloat("1e-322") == Ok(1e-322)
492+
// smallest denormal
493+
assert Number.parseFloat("5e-324") == Ok(5e-324)
494+
assert Number.parseFloat("4e-324") == Ok(5e-324)
495+
assert Number.parseFloat("3e-324") == Ok(5e-324)
496+
// too small
497+
assert Number.parseFloat("2e-324") == Ok(0.)
498+
// way too small
499+
assert Number.parseFloat("1e-350") == Ok(0.)
500+
assert Number.parseFloat("1e-400000") == Ok(0.)
501+
// try to overflow exponent
502+
assert Number.parseFloat("1e-4294967296") == Ok(0.)
503+
assert Number.parseFloat("1e+4294967296") == Ok(Number.infinity)
504+
assert Number.parseFloat("1e-18446744073709551616") == Ok(0.)
505+
assert Number.parseFloat("1e+18446744073709551616") == Ok(Number.infinity)
506+
// Parse errors
507+
assert Number.parseFloat("1e") == Err("Invalid exponent")
508+
assert Number.parseFloat("1e-") == Err("Invalid exponent")
509+
assert Number.parseFloat(".e-1") == Err("Invalid float")
510+
// https://www.exploringbinary.com/java-hangs-when-converting-2-2250738585072012e-308/
511+
assert Number.parseFloat("2.2250738585072012e-308") ==
512+
Ok(2.2250738585072014e-308)
513+
// https://www.exploringbinary.com/php-hangs-on-numeric-value-2-2250738585072011e-308/
514+
assert Number.parseFloat("2.2250738585072011e-308") ==
515+
Ok(2.225073858507201e-308)
516+
// A different kind of very large number.
517+
assert Number.parseFloat("22.222222222222222") == Ok(22.22222222222222)
518+
assert Number.parseFloat(
519+
"2.222222222222222222222222222222222222222222222222222222222222222222222222222e+1"
520+
) ==
521+
Ok(22.22222222222222)
522+
// Exactly halfway between 1 and math.Nextafter(1, 2).
523+
// Round to even (down).
524+
assert Number.parseFloat(
525+
"1.00000000000000011102230246251565404236316680908203125"
526+
) ==
527+
Ok(1.)
528+
// Slightly lower; still round down.
529+
assert Number.parseFloat(
530+
"1.00000000000000011102230246251565404236316680908203124"
531+
) ==
532+
Ok(1.)
533+
// Slightly higher; round up.
534+
assert Number.parseFloat(
535+
"1.00000000000000011102230246251565404236316680908203126"
536+
) ==
537+
Ok(1.0000000000000002)
538+
// Slightly higher, but you have to read all the way to the end.
539+
assert Number.parseFloat(
540+
"1.0000000000000001110223024625156540423631668090820312500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001"
541+
) ==
542+
Ok(1.0000000000000002)
543+
// Halfway between x := math.Nextafter(1, 2) and math.Nextafter(x, 2)
544+
// Round to even (up).
545+
assert Number.parseFloat(
546+
"1.00000000000000033306690738754696212708950042724609375"
547+
) ==
548+
Ok(1.0000000000000004)
549+
// Halfway between 1090544144181609278303144771584 and 1090544144181609419040633126912
550+
assert Number.parseFloat("1090544144181609348671888949248") ==
551+
Ok(1.0905441441816093e+30)
552+
// slightly above, rounds up
553+
assert Number.parseFloat("1090544144181609348835077142190") ==
554+
Ok(1.0905441441816094e+30)
555+
// underscores
556+
assert Number.parseFloat("1__") == Ok(1.)
557+
assert Number.parseFloat("1_e2_3_") == Ok(1e+23)
558+
assert Number.parseFloat("100_000_000_000_000_000_000_000") == Ok(1e+23)
559+
assert Number.parseFloat("1_2345_6700") == Ok(1.234567e+08)
560+
assert Number.parseFloat("625e-3__") == Ok(0.625)
561+
assert Number.parseFloat("0_0e+0_12__3___4____567890123456789") == Ok(0.)
562+
assert Number.parseFloat("1_e400_000") == Ok(Number.infinity)
563+
assert Number.parseFloat("-1_e400_000") == Ok(Number.neg(Number.infinity))
564+
assert Number.parseFloat(
565+
"1.000_000_000_000_000_111_022_302_462_515_654_042_363_166_809_082_031_250_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_1"
566+
) ==
567+
Ok(1.0000000000000002)
568+
392569
// parseInt
393570
assert Number.parseInt("42", 10) == Ok(42)
394571
assert Number.parseInt("042", 10) == Ok(42)

stdlib/number.gr

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ import {
1818
isBoxedNumber,
1919
scalbn,
2020
} from "runtime/numbers"
21-
import { parseInt } from "runtime/stringUtils"
21+
import Atoi from "runtime/atoi/parse"
22+
import Atof from "runtime/atof/parse"
2223
import { newFloat64, newInt64 } from "runtime/dataStructures"
2324
import Tags from "runtime/unsafe/tags"
2425
import Exception from "runtime/exception"
@@ -890,7 +891,18 @@ export let isInfinite = (x: Number) => {
890891
*
891892
* @since v0.4.5
892893
*/
893-
export parseInt
894+
export let parseInt = Atoi.parseInt
895+
896+
/**
897+
* Parses a string representation of a float into a `Number`. Underscores that appear
898+
* in numeric portions of the input are ignored.
899+
*
900+
* @param input: The string to parse
901+
* @returns `Ok(value)` containing the parsed number on a successful parse or `Err(msg)` containing an error message string otherwise
902+
*
903+
* @since v0.5.5
904+
*/
905+
export let parseFloat = Atof.parseFloat
894906

895907
/**
896908
* Computes how many times pi has to be subtracted to achieve the required bounds for sin.

stdlib/number.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -729,6 +729,32 @@ Returns:
729729
|----|-----------|
730730
|`Result<Number, String>`|`Ok(value)` containing the parsed number on a successful parse or `Err(msg)` containing an error message string otherwise|
731731

732+
### Number.**parseFloat**
733+
734+
<details disabled>
735+
<summary tabindex="-1">Added in <code>next</code></summary>
736+
No other changes yet.
737+
</details>
738+
739+
```grain
740+
parseFloat : String -> Result<Number, String>
741+
```
742+
743+
Parses a string representation of a float into a `Number`. Underscores that appear
744+
in numeric portions of the input are ignored.
745+
746+
Parameters:
747+
748+
|param|type|description|
749+
|-----|----|-----------|
750+
|`input`|`String`|The string to parse|
751+
752+
Returns:
753+
754+
|type|description|
755+
|----|-----------|
756+
|`Result<Number, String>`|`Ok(value)` containing the parsed number on a successful parse or `Err(msg)` containing an error message string otherwise|
757+
732758
### Number.**sin**
733759

734760
<details>

0 commit comments

Comments
 (0)