Skip to content

fix: round() and mod follow the XPath 1.0 numeric spec - #128

Merged
zhengchun merged 1 commit into
antchfx:masterfrom
gaoflow:fix-round-mod-numeric-spec
Jul 20, 2026
Merged

fix: round() and mod follow the XPath 1.0 numeric spec#128
zhengchun merged 1 commit into
antchfx:masterfrom
gaoflow:fix-round-mod-numeric-spec

Conversation

@gaoflow

@gaoflow gaoflow commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

round() and the mod operator both diverge from XPath 1.0 (and from libxml2/lxml).

round() — REC §4.4

int(math.Round(f)) rounds halves away from zero and drops non-finite values:

  • round(-0.5) → -1 (want 0), round(-1.5) → -2 (want -1), round(-2.5) → -3 (want -2); §4.4 rounds halves toward +Infinity.
  • round(1 div 0) → 9223372036854775807, round(-1 div 0) → the int64 minimum, round(0 div 0) → 0; want Infinity / -Infinity / NaN.
  • The int result isn't an XPath number, so round(2.5) + 0.5 evaluates to NaN instead of 3.5 (asNumber only understands float64).

round() now returns float64 like ceiling()/floor(): NaN/±Infinity pass through, and finite values use floor(f) with a half-up carry. It no longer depends on the go1.10-only math.Round, so it moves next to roundFunc. (§4.4 notes that floor(f + 0.5) is wrong for the signed-zero cases, e.g. round(-0.4) is -0.)

mod — REC §3.5

float64(int(a) % int(b)) truncates the operands and panics on a zero divisor: 5.5 mod 2 → 1 (want 1.5) and 5 mod 0 panics with "integer divide by zero" (want NaN). math.Mod is the truncating IEEE remainder the spec calls for — the sign follows the dividend, and a zero divisor yields NaN.

All results checked against lxml/libxml2. The existing round tests are updated to the number result, and table tests are added for both classes covering the half-to-+Infinity, non-finite, and zero-divisor edges.

go test ./..., go test -race ./..., go vet ./..., git diff --check.

round() rounded halves away from zero and dropped the non-finite cases via the
int cast (REC 4.4); it now returns a number like ceiling()/floor(), so halves
round toward +Infinity and NaN/+-Infinity pass through. Since it no longer needs
the go1.10-only math.Round it moves next to roundFunc.

mod truncated its operands to int and panicked on a zero divisor; math.Mod is
the truncating remainder the spec requires (REC 3.5), with NaN for mod by zero.
@coveralls

Copy link
Copy Markdown

Coverage Status

coverage: 80.477% (+0.06%) from 80.418% — gaoflow:fix-round-mod-numeric-spec into antchfx:master

@zhengchun
zhengchun merged commit 4d32f25 into antchfx:master Jul 20, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants