Merge pull request #3675 from Feoramund/fix-partial-infinity

Fix partial parsing of `infinity`
This commit is contained in:
gingerBill
2024-06-05 12:48:44 +01:00
committed by GitHub
5 changed files with 163 additions and 68 deletions

View File

@@ -882,13 +882,16 @@ parse_f64_prefix :: proc(str: string) -> (value: f64, nr: int, ok: bool) {
s = s[1:]
fallthrough
case 'i', 'I':
n = common_prefix_len_ignore_case(s, "infinity")
if 3 < n && n < 8 { // "inf" or "infinity"
n = 3
}
if n == 3 || n == 8 {
m := common_prefix_len_ignore_case(s, "infinity")
if 3 <= m && m < 9 { // "inf" to "infinity"
f = 0h7ff00000_00000000 if sign == 1 else 0hfff00000_00000000
n = nsign + 3
if m == 8 {
// We only count the entire prefix if it is precisely "infinity".
n = nsign + m
} else {
// The string was either only "inf" or incomplete.
n = nsign + 3
}
ok = true
return
}

View File

@@ -25,6 +25,7 @@ all_bsd: download_test_assets \
reflect_test \
runtime_test \
slice_test \
strconv_test \
strings_test \
thread_test \
time_test
@@ -98,6 +99,9 @@ runtime_test:
slice_test:
$(ODIN) test slice $(COMMON) -out:test_core_slice
strconv_test:
$(ODIN) test strconv $(COMMON) -out:test_core_strconv
strings_test:
$(ODIN) test strings $(COMMON) -out:test_core_strings

View File

@@ -103,6 +103,11 @@ echo Running core:slice tests
echo ---
%PATH_TO_ODIN% test slice %COMMON% -out:test_core_slice.exe || exit /b
echo ---
echo Running core:strconv tests
echo ---
%PATH_TO_ODIN% test strconv %COMMON% -out:test_core_strconv.exe || exit /b
echo ---
echo Running core:strings tests
echo ---

View File

@@ -0,0 +1,145 @@
package test_core_strconv
import "core:math"
import "core:strconv"
import "core:testing"
@(test)
test_float :: proc(t: ^testing.T) {
n: int
f: f64
ok: bool
f, ok = strconv.parse_f64("1.2", &n)
testing.expect_value(t, f, 1.2)
testing.expect_value(t, n, 3)
testing.expect_value(t, ok, true)
f, ok = strconv.parse_f64("1.2a", &n)
testing.expect_value(t, f, 1.2)
testing.expect_value(t, n, 3)
testing.expect_value(t, ok, false)
f, ok = strconv.parse_f64("+", &n)
testing.expect_value(t, f, 0)
testing.expect_value(t, n, 0)
testing.expect_value(t, ok, false)
f, ok = strconv.parse_f64("-", &n)
testing.expect_value(t, f, 0)
testing.expect_value(t, n, 0)
testing.expect_value(t, ok, false)
}
@(test)
test_nan :: proc(t: ^testing.T) {
n: int
f: f64
ok: bool
f, ok = strconv.parse_f64("nan", &n)
testing.expect_value(t, math.classify(f), math.Float_Class.NaN)
testing.expect_value(t, n, 3)
testing.expect_value(t, ok, true)
f, ok = strconv.parse_f64("nAN", &n)
testing.expect_value(t, math.classify(f), math.Float_Class.NaN)
testing.expect_value(t, n, 3)
testing.expect_value(t, ok, true)
f, ok = strconv.parse_f64("Nani", &n)
testing.expect_value(t, math.classify(f), math.Float_Class.NaN)
testing.expect_value(t, n, 3)
testing.expect_value(t, ok, false)
}
@(test)
test_infinity :: proc(t: ^testing.T) {
pos_inf := math.inf_f64(+1)
neg_inf := math.inf_f64(-1)
n: int
s := "infinity"
for i in 0 ..< len(s) + 1 {
ss := s[:i]
f, ok := strconv.parse_f64(ss, &n)
if i >= 3 { // "inf" .. "infinity"
expected_n := 8 if i == 8 else 3
expected_ok := i == 3 || i == 8
testing.expect_value(t, f, pos_inf)
testing.expect_value(t, n, expected_n)
testing.expect_value(t, ok, expected_ok)
testing.expect_value(t, math.classify(f), math.Float_Class.Inf)
} else { // invalid substring
testing.expect_value(t, f, 0)
testing.expect_value(t, n, 0)
testing.expect_value(t, ok, false)
testing.expect_value(t, math.classify(f), math.Float_Class.Zero)
}
}
s = "+infinity"
for i in 0 ..< len(s) + 1 {
ss := s[:i]
f, ok := strconv.parse_f64(ss, &n)
if i >= 4 { // "+inf" .. "+infinity"
expected_n := 9 if i == 9 else 4
expected_ok := i == 4 || i == 9
testing.expect_value(t, f, pos_inf)
testing.expect_value(t, n, expected_n)
testing.expect_value(t, ok, expected_ok)
testing.expect_value(t, math.classify(f), math.Float_Class.Inf)
} else { // invalid substring
testing.expect_value(t, f, 0)
testing.expect_value(t, n, 0)
testing.expect_value(t, ok, false)
testing.expect_value(t, math.classify(f), math.Float_Class.Zero)
}
}
s = "-infinity"
for i in 0 ..< len(s) + 1 {
ss := s[:i]
f, ok := strconv.parse_f64(ss, &n)
if i >= 4 { // "-inf" .. "infinity"
expected_n := 9 if i == 9 else 4
expected_ok := i == 4 || i == 9
testing.expect_value(t, f, neg_inf)
testing.expect_value(t, n, expected_n)
testing.expect_value(t, ok, expected_ok)
testing.expect_value(t, math.classify(f), math.Float_Class.Neg_Inf)
} else { // invalid substring
testing.expect_value(t, f, 0)
testing.expect_value(t, n, 0)
testing.expect_value(t, ok, false)
testing.expect_value(t, math.classify(f), math.Float_Class.Zero)
}
}
// Make sure odd casing works.
batch := [?]string {"INFiniTY", "iNfInItY", "InFiNiTy"}
for ss in batch {
f, ok := strconv.parse_f64(ss, &n)
testing.expect_value(t, f, pos_inf)
testing.expect_value(t, n, 8)
testing.expect_value(t, ok, true)
testing.expect_value(t, math.classify(f), math.Float_Class.Inf)
}
// Explicitly check how trailing characters are handled.
s = "infinityyyy"
f, ok := strconv.parse_f64(s, &n)
testing.expect_value(t, f, pos_inf)
testing.expect_value(t, n, 8)
testing.expect_value(t, ok, false)
testing.expect_value(t, math.classify(f), math.Float_Class.Inf)
s = "inflippity"
f, ok = strconv.parse_f64(s, &n)
testing.expect_value(t, f, pos_inf)
testing.expect_value(t, n, 3)
testing.expect_value(t, ok, false)
testing.expect_value(t, math.classify(f), math.Float_Class.Inf)
}

View File

@@ -1,62 +0,0 @@
// Tests issue #2087 https://github.com/odin-lang/Odin/issues/2087
package test_issues
import "core:math"
import "core:strconv"
import "core:testing"
@(test)
test_parse_float :: proc(t: ^testing.T) {
{
f, ok := strconv.parse_f64("1.2")
testing.expect(t, ok && f == 1.2, "expected f64(1.2), fully consumed")
f, ok = strconv.parse_f64("1.2a")
testing.expect(t, !ok && f == 1.2, "expected f64(1.2), partially consumed")
f, ok = strconv.parse_f64("+")
testing.expect(t, !ok && f == 0.0, "expected f64(0.0), with ok=false")
f, ok = strconv.parse_f64("-")
testing.expect(t, !ok && f == 0.0, "expected f64(0.0), with ok=false")
f, ok = strconv.parse_f64("inf")
testing.expect(t, ok && math.classify(f) == math.Float_Class.Inf, "expected f64(+inf), fully consumed")
f, ok = strconv.parse_f64("+inf")
testing.expect(t, ok && math.classify(f) == math.Float_Class.Inf, "expected f64(+inf), fully consumed")
f, ok = strconv.parse_f64("-inf")
testing.expect(t, ok && math.classify(f) == math.Float_Class.Neg_Inf, "expected f64(-inf), fully consumed")
f, ok = strconv.parse_f64("inFinity")
testing.expect(t, !ok && math.classify(f) == math.Float_Class.Inf, "expected f64(+inf), partially consumed")
f, ok = strconv.parse_f64("+InFinity")
testing.expect(t, !ok && math.classify(f) == math.Float_Class.Inf, "expected f64(+inf), partially consumed")
f, ok = strconv.parse_f64("-InfiniTy")
testing.expect(t, !ok && math.classify(f) == math.Float_Class.Neg_Inf, "expected f64(-inf), partially consumed")
f, ok = strconv.parse_f64("nan")
testing.expect(t, ok && math.classify(f) == math.Float_Class.NaN, "expected f64(nan), fully consumed")
f, ok = strconv.parse_f64("nAN")
testing.expect(t, ok && math.classify(f) == math.Float_Class.NaN, "expected f64(nan), fully consumed")
}
{
f, ok := strconv.parse_f32("1.2")
testing.expect(t, ok && f == 1.2, "expected f32(1.2), fully consumed")
f, ok = strconv.parse_f32("1.2a")
testing.expect(t, !ok && f == 1.2, "expected f32(1.2), partially consumed")
f, ok = strconv.parse_f32("inf")
testing.expect(t, ok && math.classify(f) == math.Float_Class.Inf, "expected f32(+inf), fully consumed")
f, ok = strconv.parse_f32("+inf")
testing.expect(t, ok && math.classify(f) == math.Float_Class.Inf, "expected f32(+inf), fully consumed")
f, ok = strconv.parse_f32("-inf")
testing.expect(t, ok && math.classify(f) == math.Float_Class.Neg_Inf, "expected f32(-inf), fully consumed")
f, ok = strconv.parse_f32("inFinity")
testing.expect(t, !ok && math.classify(f) == math.Float_Class.Inf, "expected f32(+inf), partially consumed")
f, ok = strconv.parse_f32("+InFinity")
testing.expect(t, !ok && math.classify(f) == math.Float_Class.Inf, "expected f32(+inf), partially consumed")
f, ok = strconv.parse_f32("-InfiniTy")
testing.expect(t, !ok && math.classify(f) == math.Float_Class.Neg_Inf, "expected f32(-inf), partially consumed")
f, ok = strconv.parse_f32("nan")
testing.expect(t, ok && math.classify(f) == math.Float_Class.NaN, "expected f32(nan), fully consumed")
f, ok = strconv.parse_f32("nAN")
testing.expect(t, ok && math.classify(f) == math.Float_Class.NaN, "expected f32(nan), fully consumed")
}
}