Skip to content

jq: isinfinite answers false for a genuine infinity, unlike isnan which has a sentinel check #2932

Description

@newhoggy

Severity: Low (one builtin's predicate; the value itself is correct, and every other observation of it agrees with jq)

Repro

Confirmed live against /usr/bin/jq 1.7.1 and succinctly at 1fc023b0c:

$ jq -nc 'infinite|isinfinite'
true
$ succinctly jq -nc 'infinite|isinfinite'
false

$ jq -nc '(-(infinite))|isinfinite'
true
$ succinctly jq -nc '(-(infinite))|isinfinite'
false

Why it is only this one predicate

The stored value is a genuine infinity, and everything else about it already matches jq:

filter jq 1.7.1 succinctly
infinite > 1.7976931348623157e+308 true true
infinite == 1.7976931348623157e+308 false false
(infinite - infinite)|isnan true true
infinite 1.7976931348623157e+308 1.7976931348623157e+308
infinite|tostring "1.7976931348623157e+308" "1.7976931348623157e+308"
infinite|isnormal false false
infinite|isinfinite true false

> and == both prove the value is above f64::MAX, so it is not stored clamped.

Root cause

builtin_isinfinite (src/jq/eval.rs) reads the bridged StandardJson::Number's text via
n.as_f64(). Bridging renders an infinity clamped — the same 1.7976931348623157e+308 jq
itself prints — so as_f64() hands back f64::MAX, whose is_infinite() is false.

builtin_isnan does not have this problem because it checks a sentinel first
(is_nan_sentinel(n.raw_bytes()), #472) before falling back to as_f64(). There is no
equivalent for infinity, so isinfinite reads the clamped text and answers on that.

Note test_nan_normals_isinfinite_isnormal_already_correct_613 pins nan | isinfinite as
false, which is correct and unaffected — that test is about NaN, not about a real infinity.

Fix direction

Give infinity the same treatment NaN already has: either a sentinel isinfinite checks before
as_f64(), or preserve the unclamped value across the bridge so as_f64() answers for itself.
The second is the cleaner shape if the bridge can carry it, since it would fix any future
predicate rather than one more special case — but it changes what the bridge emits, so check
tostring/rendering rows stay put (they already match jq and must keep matching).

Worth checking isfinite and isnormal for the same blind spot while there, and the yq-mode
spellings alongside.

Found while gathering the oracle mapping for
#2877 (Infinity as an input literal),
which is a separate surface — this one is about a value the evaluator itself produces.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    SonnetSuitable for a Sonnet-class model to implement

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions