agentlang-index · task easy
Parse two unsigned integers and write their sum, or error on parse failure or u32 overflow
017-checked-add-overflow. Read two lines from standard input.
Prompt
This is the natural-language brief given to every model, verbatim. The harness prefixes a language-specific calling-convention block and suffixes a "return only the source code" instruction. Nothing else.
# 017-checked-add-overflow
Read two lines from standard input. Each line contains an unsigned
decimal integer that fits in u32 (range 0 through 4294967295
inclusive). Parse both. Compute the sum `a + b` and write the result
as decimal followed by a single newline.
If any of the following hold, write the literal string `error\n`
instead and exit:
1. Either line fails to parse as a u32 (empty input, non-digit
characters in the body, leading `+` sign, value exceeds u32
maximum).
2. The sum `a + b` would exceed 4294967295 (u32 overflow).
Trailing whitespace on either digit line is tolerated and trimmed
before parsing. Leading zeros are accepted (`007` parses as 7).
Output exactly one line followed by `\n`. Do not write to standard
error. Exit with status 0 in every case.
## Examples
Input (stdin):
```
2
3
```
Output: `5\n`
Input (stdin):
```
0
4294967295
```
Output: `4294967295\n` (boundary, sum equals u32 max)
Input (stdin):
```
4294967295
1
```
Output: `error\n` (smallest u32 overflow)
Input (stdin):
```
2147483648
2147483648
```
Output: `error\n` (2^31 + 2^31 = 2^32, exact wrap)
Input (stdin):
```
abc
5
```
Output: `error\n` (parse fails on first line)
## Zero input convention
Zero 0.1.2 has no exposed stdin capability. The Zero reference
reads `a` from `argv[1]` and `b` from `argv[2]`; values are
interpreted exactly as the two stdin lines for other languages.
Acceptance
A task counts as passed only when every public and hidden test case agrees on these fields. No fuzzy matching, no "off by one trailing newline is fine."
| stdout (byte-exact, per case) | true |
|---|---|
| stderr (exact bytes) | "" |
| exit code | 0 |
| wall time max (ms) | 5000 |
| tags | error-handling, parsing, arithmetic, overflow |
Results
Each cell is one attempt. Pass means stdout matched byte-exact on every test case, stderr empty, exit zero. Hover a failure to see the captured first line of the diagnostic.
| Model | Zero | TypeScript | Rust | Go | Python |
|---|---|---|---|---|---|
| gpt-4o | compile | ✓ | ✓ | ✓ | ✓ |
| gpt-4o-mini | compile | ✓ | ✓ | ✓ | ✓ |
| gpt-5 | wrong output | ✓ | ✓ | ✓ | ✓ |
Failure excerpts
3 of 15 attempts failed. Each card is one attempt, with the captured first line of the diagnostic.
-
ref.zero:1:1 IMP001: unknown package-local import 'std' -
ref.zero:3:13 PAR100: expected '{' before block -
(no diagnostic captured)
Reference implementations
The hand-written reference each language ships with. Every reference passes the same public and hidden test suite under the pinned toolchain before any model touches the task.
Click a language to expand
Zero 141 lines
// Checked u32 add-with-overflow reference for AgentLang Index.
//
// Zero 0.1.2 has no exposed stdin, so a and b come from argv[1..2].
// std.parse.parseU32 cannot accept runtime String values on the
// direct ELF64 backend (CGEN004: requires literal text), so the
// parse + overflow check is implemented inline: u64 accumulator,
// digit-by-digit, reject any non-digit byte, reject if final value
// exceeds u32::MAX.
//
// Three failure modes collapse to "error\n":
// - argv[1] or argv[2] missing
// - either argv has non-digit bytes, is empty, or exceeds u32::MAX
// - the sum a + b exceeds u32::MAX (computed in u64 to dodge wrap)
//
// main ends with an explicit `return` to dodge the trailing-write
// byte-count-as-exit-code codegen quirk surfaced on task 012.
pub fun main(world: World) -> Void raises {
let maybe_a = std.args.get(1)
let maybe_b = std.args.get(2)
if maybe_a.has == false {
check world.out.write("error\n")
return
}
if maybe_b.has == false {
check world.out.write("error\n")
return
}
let a_in = std.mem.span(maybe_a.value)
let b_in = std.mem.span(maybe_b.value)
// ---- Parse a as u32, reject non-digit / empty / > u32::MAX ----
let a_len = std.mem.len(a_in)
let mut a_acc: u64 = 0_u64
let mut a_have: Bool = false
let mut a_ok: Bool = true
let mut a_i: usize = 0
while a_i < a_len {
let c = a_in[a_i]
if c >= 48_u8 {
if c <= 57_u8 {
let d: u64 = (c - 48_u8) as u64
a_acc = a_acc * 10_u64 + d
a_have = true
a_i = a_i + 1
} else {
a_ok = false
a_i = a_len
}
} else {
a_ok = false
a_i = a_len
}
}
if a_ok == false {
check world.out.write("error\n")
return
}
if a_have == false {
check world.out.write("error\n")
return
}
if a_acc > 4294967295_u64 {
check world.out.write("error\n")
return
}
// ---- Parse b as u32 ----
let b_len = std.mem.len(b_in)
let mut b_acc: u64 = 0_u64
let mut b_have: Bool = false
let mut b_ok: Bool = true
let mut b_i: usize = 0
while b_i < b_len {
let c = b_in[b_i]
if c >= 48_u8 {
if c <= 57_u8 {
let d: u64 = (c - 48_u8) as u64
b_acc = b_acc * 10_u64 + d
b_have = true
b_i = b_i + 1
} else {
b_ok = false
b_i = b_len
}
} else {
b_ok = false
b_i = b_len
}
}
if b_ok == false {
check world.out.write("error\n")
return
}
if b_have == false {
check world.out.write("error\n")
return
}
if b_acc > 4294967295_u64 {
check world.out.write("error\n")
return
}
// ---- Sum in u64, bounds-check against u32::MAX ----
let sum: u64 = a_acc + b_acc
if sum > 4294967295_u64 {
check world.out.write("error\n")
return
}
let s_val: u32 = sum as u32
// ---- Render s_val as decimal + newline ----
let mut out: [16]u8 = [0_u8; 16]
let mut out_n: usize = 0
if s_val == 0_u32 {
out[out_n] = 48_u8
out_n = out_n + 1
} else {
let mut tmp: [16]u8 = [0_u8; 16]
let mut tn: usize = 0
let mut v: u32 = s_val
while v > 0_u32 {
let digit_u32: u32 = 48_u32 + (v % 10_u32)
tmp[tn] = digit_u32 as u8
tn = tn + 1
v = v / 10_u32
}
let mut wj: usize = 0
while wj < tn {
out[out_n] = tmp[tn - 1 - wj]
out_n = out_n + 1
wj = wj + 1
}
}
out[out_n] = 10_u8
out_n = out_n + 1
check world.out.write(out[0..out_n])
return
}
TypeScript 49 lines
const U32_MAX = 4294967295n;
function parseU32(s: string): bigint | null {
const t = s.trim();
if (t.length === 0) return null;
for (let i = 0; i < t.length; i++) {
const c = t.charCodeAt(i);
if (c < 48 || c > 57) return null;
}
try {
const v = BigInt(t);
if (v > U32_MAX) return null;
return v;
} catch {
return null;
}
}
async function readAll(): Promise<string> {
const chunks: Buffer[] = [];
for await (const chunk of process.stdin) {
chunks.push(chunk as Buffer);
}
return Buffer.concat(chunks).toString("utf8");
}
async function main(): Promise<void> {
const data = await readAll();
const lines = data.split("\n");
if (lines.length < 2) {
process.stdout.write("error\n");
return;
}
const a = parseU32(lines[0]);
const b = parseU32(lines[1]);
if (a === null || b === null) {
process.stdout.write("error\n");
return;
}
const sum = a + b;
if (sum > U32_MAX) {
process.stdout.write("error\n");
return;
}
process.stdout.write(`${sum}\n`);
}
main();
Rust 45 lines
use std::io::{self, Read, Write};
fn parse_u32(s: &str) -> Option<u32> {
let t = s.trim();
if t.is_empty() {
return None;
}
for b in t.bytes() {
if !(b'0'..=b'9').contains(&b) {
return None;
}
}
t.parse::<u32>().ok()
}
fn main() {
let mut input = String::new();
if io::stdin().read_to_string(&mut input).is_err() {
let _ = io::stdout().write_all(b"error\n");
return;
}
let mut lines = input.split('\n');
let line_a = lines.next();
let line_b = lines.next();
let (Some(la), Some(lb)) = (line_a, line_b) else {
let _ = io::stdout().write_all(b"error\n");
return;
};
let a = parse_u32(la);
let b = parse_u32(lb);
match (a, b) {
(Some(av), Some(bv)) => match av.checked_add(bv) {
Some(sum) => {
let _ = writeln!(io::stdout(), "{}", sum);
}
None => {
let _ = io::stdout().write_all(b"error\n");
}
},
_ => {
let _ = io::stdout().write_all(b"error\n");
}
}
}
Go 67 lines
package main
import (
"bufio"
"fmt"
"math"
"os"
"strconv"
"strings"
)
func parseU32(s string) (uint32, bool) {
t := strings.TrimSpace(s)
if t == "" {
return 0, false
}
for _, c := range t {
if c < '0' || c > '9' {
return 0, false
}
}
v, err := strconv.ParseUint(t, 10, 32)
if err != nil {
return 0, false
}
return uint32(v), true
}
func main() {
w := bufio.NewWriter(os.Stdout)
defer w.Flush()
r := bufio.NewReader(os.Stdin)
data, _ := readAll(r)
parts := strings.SplitN(data, "\n", 3)
if len(parts) < 2 {
fmt.Fprint(w, "error\n")
return
}
a, okA := parseU32(parts[0])
b, okB := parseU32(parts[1])
if !okA || !okB {
fmt.Fprint(w, "error\n")
return
}
sum := uint64(a) + uint64(b)
if sum > math.MaxUint32 {
fmt.Fprint(w, "error\n")
return
}
fmt.Fprintf(w, "%d\n", uint32(sum))
}
func readAll(r *bufio.Reader) (string, error) {
var sb strings.Builder
buf := make([]byte, 4096)
for {
n, err := r.Read(buf)
if n > 0 {
sb.Write(buf[:n])
}
if err != nil {
break
}
}
return sb.String(), nil
}
Python 43 lines
#!/usr/bin/env python3
import sys
U32_MAX = 4294967295
def parse_u32(s: str):
s = s.strip()
if not s:
return None
for ch in s:
if not ('0' <= ch <= '9'):
return None
try:
v = int(s)
except ValueError:
return None
if v > U32_MAX:
return None
return v
def main() -> None:
data = sys.stdin.read()
lines = data.split("\n")
if len(lines) < 2:
sys.stdout.write("error\n")
return
a = parse_u32(lines[0])
b = parse_u32(lines[1])
if a is None or b is None:
sys.stdout.write("error\n")
return
s = a + b
if s > U32_MAX:
sys.stdout.write("error\n")
return
sys.stdout.write(f"{s}\n")
if __name__ == "__main__":
main()
Design notes
Algorithm, failure modes, cross-language parity, and where Zero needed a workaround. From corpus/017-checked-add-overflow/notes.md.
Algorithm
Read two unsigned decimal integers (each in u32 range). Parse both,
reject leading sign and non-digit bodies. If either parse fails or
a + b would exceed u32::MAX = 4294967295, write error\n.
Otherwise write the sum as decimal followed by \n. Process exit is
0 in every case.
Failure modes
Three independent paths collapse to the same error\n:
- argv/stdin shape missing (under two lines or two argv values).
- Either integer fails to parse (empty body, non-digit byte, leading
+/-, or value exceeds u32::MAX = 4294967295). - The sum
a + bwould exceed u32::MAX (u32 add wrap).
Leading zeros are accepted (007 parses to 7). Trailing whitespace
on the stdin lines is tolerated and trimmed before parsing; for the
Zero argv path the values are taken exactly as the OS passes them
(no embedded whitespace).
Overflow-check shape per language
- Python / TypeScript: arithmetic is unbounded (BigInt in TS, native
int in Python). Sum then compare
> U32_MAX. No wraparound trap. - Rust:
u32::checked_addreturnsOption<u32>—Noneon overflow. The match arm pattern threads it cleanly with the parse failures (Some(av).checked_add(bv)then match). - Go: cast both addends to
uint64, sum, compare againstmath.MaxUint32. (Could also usebits.Add32to read the carry bit; the u64 widen is simpler and equivalent.) - Zero: accumulate both parsed values in
u64directly, sum in u64, compare> 4294967295_u64. Identical pattern to the per-value bounds check on the parse path — u64 is already the working type.
Arc closure (third structural distinct shape)
This task closes the error-handling arc at 3/3:
- 015 (
checked-divide-u32): three flat failure paths that short-circuit at gate checks BEFORE the work (missing argv, parse failure, divisor zero). The work is a single divide; no arithmetic-side failure surface. - 016 (
parse-list-sum): a counted loop where ANY iteration can fail (parse failure OR running-sum overflow). Failure is mid-work and must terminate the loop cleanly. - 017 (
checked-add-overflow): the work itself is the failure surface. Both inputs parse cleanly yet the operation can still fail — overflow is detected by computing in a wider type and bounds-checking, OR by using a checked-arithmetic primitive that signals overflow as a sum-time return value.
Across the three: pre-work gate, in-loop failure with state,
post-parse arithmetic failure. The error-output collapse is uniform
(error\n + exit 0), but the where-it-fails dimension is structurally
distinct each time.
Cross-implementation parity
All five share the same dispatch:
- parse a (u32)
- parse b (u32)
- check
a + b <= u32::MAX - emit sum +
\n
Byte-exact agreement on every case.
Zero-specific notes
- argv[1..2] carry a and b (no exposed stdin in Zero 0.1.2).
std.args.get(i)returnsMaybe<String>. The.valueis a String, whichstd.mem.span(.)converts to aSpan<u8>for the manual digit scan.std.parse.parseU32is still unusable for runtime data per the eighth quirk surfaced on task 015 (CGEN004 on direct ELF64 for non-literal input).- Parse helpers are inlined twice (once for a, once for b) per the
seventh quirk forbidding
Span<u8>-taking user functions on direct ELF64. The two parse blocks are byte-identical apart from the local-variable prefix. - main ends with an explicit
returnto dodge the trailing-write byte-count-as-exit-code codegen quirk surfaced on task 012. - The overflow check is
if sum > 4294967295_u64 { error }wheresumisa_acc + b_accboth already validated <= 4294967295_u64. Worst-case sum is 2 * (2^32 - 1) = 2^33 - 2, comfortably within u64 range (no second-order wrap). - The render loop uses a small-divisor (10) decimal split, dodging the 2^32-divisor SIGFPE quirk noted for direct ELF64 in the AgentLang Index notes.
No new codegen quirks surfaced during 017 — the eight quirks from tasks 012-015 were already in scope, and this task follows the patterns established in 015/016.
Cost
| Model | Prompt tokens | Completion tokens | API ms |
|---|---|---|---|
| gpt-4o | 3,265 | 1,198 | 11,668 |
| gpt-4o-mini | 3,265 | 1,326 | 22,780 |
| gpt-5 | 3,260 | 23,903 | 203,552 |
Tokens and API ms are summed across the five languages this model attempted for this task.
Compare
Model deep-dives: gpt-4o · gpt-4o-mini · gpt-5 . Back to the leaderboard and methodology.