Skip to content

engine: Vensim :NA: was mishandled as IEEE NaN (should be the finite sentinel -2^109) #586

@bpowers

Description

@bpowers

Status

FIXED in commit 70f8f749 ("engine: represent Vensim :NA: as the finite sentinel -2^109, not NaN"). Filed for the record (an earlier filing attempt failed with an HTTP 529). One follow-on remains open (see "Remaining follow-on" below); leaving close/triage to the owner.

Problem

Vensim's :NA: keyword is not IEEE NaN. Per the Vensim documentation (vensim.com/documentation/na.html, dataequations.html) and the Ventana forum (t=4707), :NA: is an ordinary finite sentinel value, -2^109 (= -6.490371073168535e32). It is used to test for the existence of data via the idiom IF THEN ELSE(X = :NA:, ...) -- a plain equality-against-the-sentinel that only works because :NA: is finite (IEEE NaN never compares equal to anything, including itself, so the existence test would silently never fire under NaN).

Simlin previously mishandled :NA: in three inconsistent ways, none of which equalled -2^109:

  1. Expression :NA: -> the literal string "NAN" (in mdl/xmile_compat.rs) -> Const(f64::NAN) -> IEEE NaN.
  2. Data-list :NA: (inside a data equation's value list) -> NA_VALUE = -1e38 (in mdl/parser.rs).
  3. Neither path used the correct -2^109 sentinel.

Under IEEE NaN, any unguarded arithmetic touching a :NA: value is irreversibly NaN (NaN is absorbing). This was the root cause of C-LEARN's 434 entirely-NaN core series (tracked under element-cycle-resolution AC7.3): a single :NA: poisoned downstream expressions instead of producing a finite, testable sentinel.

Why it matters

  • Correctness / Vensim semantic fidelity: the X = :NA: existence-test idiom is common in Vensim data models and is structurally broken under a NaN representation -- the equality silently never matches.
  • Robustness: NaN propagation poisons whole series irreversibly; the finite sentinel keeps arithmetic finite and recoverable, matching Vensim runtime behavior.
  • Consistency: three different in-engine representations of the same concept (f64::NAN, -1e38, and the intended -2^109) is a latent correctness hazard.

The fix (commit 70f8f74)

A single canonical constant pub const NA: f64 = -6.490371073168535e32; was introduced in src/simlin-engine/src/float.rs, bit-pinned to -(2.0_f64).powi(109) by the test na_is_negative_two_pow_109 (-2^109 has a zero mantissa, so the spelled-out literal is bit-identical). Both :NA: paths (expression-literal and data-list) now route to this constant. Tests assert the existence-test semantics: approx_eq(NA, NA) is true (the test fires), approx_eq(NA, 0.0) and approx_eq(NA, -2^110) are false (a contaminated/doubled value does not spuriously match), and NA.is_finite() holds.

Structurally distinct NaN-producing paths were deliberately left untouched, because they are genuinely-NaN by Vensim semantics (not the :NA: sentinel):

  • out-of-bounds -> NaN for VECTOR ELM MAP and range subscripts;
  • empty-reducer -> NaN.

Remaining follow-on (do NOT conflate with this fix)

Vensim saves :NA: as a literal 0.0 to the VDF (verified by a raw-byte scan of test/xmutil_test_models/Ref.vdf). So the runtime sentinel (-2^109) vs. VDF-on-disk (0.0) reconciliation needed for the 1% numeric match in the test-comparison machinery is a separate follow-on (element-cycle-resolution Phase 7 / AC8.1). By design, the engine's runtime output keeps the -2^109 sentinel and does NOT map it to 0; only the test comparator must reconcile the two representations.

Components affected

  • src/simlin-engine/src/float.rs (canonical NA constant)
  • src/simlin-engine/src/mdl/xmile_compat.rs (expression :NA: path)
  • src/simlin-engine/src/mdl/parser.rs (data-list :NA: path)
  • src/simlin-engine/src/mdl/ast.rs
  • src/simlin-engine/src/mdl/reader.rs

Relationship to other issues

Distinct from #584 (INITIAL()-backed module/macro output omitted from initials runlist, spurious inf) and #585 (arrayed VECTOR SORT ORDER flat indices). Those are unrelated numeric/runlist issues; this is specifically the :NA: sentinel representation.

Discovery context

Identified during the C-LEARN hero-model work (branch clearn-hero-model) while diagnosing C-LEARN's all-NaN core series. The :NA: -> NaN mishandling was the root cause.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingengineIssues with the rust-based simulation engine

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions