Browse Source

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

Fix partial parsing of `infinity`
gingerBill 1 year ago
parent
commit
a747e47582

+ 9 - 6
core/strconv/strconv.odin

@@ -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
 				}

+ 4 - 0
tests/core/Makefile

@@ -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
 

+ 5 - 0
tests/core/build.bat

@@ -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 ---

+ 145 - 0
tests/core/strconv/test_core_strconv.odin

@@ -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)
+}

+ 0 - 62
tests/issues/test_issue_2087.odin

@@ -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")
-	}
-}