Skip to content

Bug(expr): divergences from CPython/spec #290

Description

@crowecawcaw

Describe the bug

Six silent-wrong-value divergences in openjd-expr: each expression evaluates successfully and returns a wrong result. None panic, so fuzzing and the existing 3,424 tests pass over them.

Discovered by fuzzing expressions and using equivalent between Rust and mwiebe's Python implementation as the oracle.

https://github.com/crowecawcaw/openjd-rs/actions/runs/30466242555/job/90624590862

Expected Behaviour

Values per the Want column below. #6 additionally needs a spec decision: CPython raises TypeError for PurePosixPath < str, so it can't adjudicate the direction — only that a < b and b > a must agree.

Two related spec questions, not bugs: whether path / '' keeps a trailing separator (posixpath.join says yes and this crate matches; PurePosixPath says no), and the #6 direction above.

Current Behaviour

# Expression Got Want
1 9.2e18 % 0.1 0.0 0.09740867242801976
2 205.0 // 0.1 2050 2049
3 1e-7 (display) 1e-7 1e-07
4 center('ab', 5) " ab " " ab "
5 path('a\b') / '\x' (Windows) a\b\\x \x
6 path('/tmp/x') < 'ab' false, but 'ab' > path('/tmp/x') is true consistent

Root causes:

  1. arithmetic.rs computes l - r*floor(l/r); the product cancels the dividend at large quotients. CPython uses fmod + sign correction.
  2. (l/r).floor() rounds before flooring; 0.1 is slightly above one tenth, so the true quotient is just under 2050. Value only — spec line 1162 makes float // float -> int, so the int type is correct and CPython diverges.
  3. format_float's .replace("e-0", "e-") strips the zero-pad C-style exponents require. Positive exponents are already correct.
  4. let left = pad / 2; — CPython's marg & width & 1 makes the bias depend on the parity of both operands. Odd padding only. Not fixed by fix(expr)!: enforce memory and operation limits before building large string outputs #284, which rewrites center_fn's preflight but keeps this line; its test uses width 4, where both rules agree.
  5. Root-relative right operand should replace a left with no drive (ntpath.join\x). Drive and UNC branches are already correct.
  6. The mixed-type arm loses operand order, so both directions claim the string is larger. Antisymmetry is violated regardless of intended direction.

Reproduction Steps

See the CI log linked above. Harness and corpus:
https://github.com/crowecawcaw/openjd-rs/tree/differential-harness/differential

Environment

openjd-expr at 99871a0 (upstream/main). Reproduced on Linux/CPython 3.13 and macOS/CPython 3.14.3 — not platform- or interpreter-specific.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions