Compare commits

..
1 Commits
Author SHA1 Message Date
weaselbot b9dfb3becc schemagen: parse integer fallback exactly for decimal/exponent forms
CI / build (-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++, clang-arm64, ubuntu-latest-arm64, true) (pull_request) Successful in 1m0s
CI / build (-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++, gcc-arm64, ubuntu-latest-arm64, false) (pull_request) Successful in 58s
CI / pre-commit (pull_request) Failing after 52s
CI / build (-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++, clang-amd64, ubuntu-latest-amd64, true) (pull_request) Successful in 1m29s
CI / build (-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++, gcc-amd64, ubuntu-latest-amd64, false) (pull_request) Successful in 1m24s
Replace the double-based fallback in generated integer slots with a
string-to-int64 parser that handles decimal points and exponents
without losing precision near the int64 boundaries.

The old path used std::from_chars<double> and compared against
±9223372036854775808.0, which rounds the int64 max and min so that
valid values are rejected and out-of-range negatives are accepted.

The new helper:
- Parses sign, integer part, optional fraction, and optional exponent.
- Strips trailing zeros to cancel fractional places.
- Rejects non-integral values and overflow using exact uint64_t
  arithmetic.

Adds regression tests covering root integer and object-field integer
boundary values, including the cases from issue #19.
2026-06-23 18:36:37 -04:00
2 changed files with 5 additions and 17 deletions
+2 -8
View File
@@ -324,9 +324,7 @@ class SchemagenIntegerBoundaryTest(unittest.TestCase):
self.assertEqual(run.returncode, 0, msg=run.stdout + run.stderr) self.assertEqual(run.returncode, 0, msg=run.stdout + run.stderr)
def _build_cases_array(self, name, entries): def _build_cases_array(self, name, entries):
lines = [ lines = [f" struct {name}Case {{ const char *s; WeaselJsonStatus expected; long long v; }};"]
f" struct {name}Case {{ const char *s; WeaselJsonStatus expected; long long v; }};"
]
lines.append(f" {name}Case {name}_cases[] = {{") lines.append(f" {name}Case {name}_cases[] = {{")
for s, exp, v in entries: for s, exp, v in entries:
val = f"{v}LL" if isinstance(v, int) else v val = f"{v}LL" if isinstance(v, int) else v
@@ -395,11 +393,7 @@ class SchemagenIntegerBoundaryTest(unittest.TestCase):
('{"age":1e3}', "WeaselJson_OK", 1000), ('{"age":1e3}', "WeaselJson_OK", 1000),
('{"age":2.0}', "WeaselJson_OK", 2), ('{"age":2.0}', "WeaselJson_OK", 2),
('{"age":0.001}', "WeaselJson_REJECT", 0), ('{"age":0.001}', "WeaselJson_REJECT", 0),
( ('{"age":-9223372036854775808.0}', "WeaselJson_OK", "-9223372036854775807LL - 1"),
'{"age":-9223372036854775808.0}',
"WeaselJson_OK",
"-9223372036854775807LL - 1",
),
] ]
cases_src = self._build_cases_array("object", cases) cases_src = self._build_cases_array("object", cases)
harness = textwrap.dedent( harness = textwrap.dedent(
+3 -9
View File
@@ -1001,15 +1001,9 @@ private:
mag *= 10; mag *= 10;
}} }}
if (mag > limit) return false; if (mag > limit) return false;
if (neg) {{ __int128 signedMag = static_cast<__int128>(mag);
if (mag == kMaxNeg) {{ if (neg) signedMag = -signedMag;
out = INT64_MIN; out = static_cast<int64_t>(signedMag);
}} else {{
out = -static_cast<int64_t>(mag);
}}
}} else {{
out = static_cast<int64_t>(mag);
}}
return true; return true;
}} }}