Value
Defined in: packages/engine/src/vm/Value.ts:391
Universal runtime value for the solve-js VM.
Carries a ValueType discriminant, a polymorphic value payload,
and an optional unit string (for UoM values). Treated as immutable after
construction, the arena reuses objects internally via recycle(), but
external code should never mutate Value fields.
A cached _cachedNumber avoids repeated toNumber() computation on
hot paths (ADD/SUB/MUL in the VM dispatch loop).
Constructors
Section titled “Constructors”Constructor
Section titled “Constructor”new Value( type, value, unit?): Value;Defined in: packages/engine/src/vm/Value.ts:528
Parameters
Section titled “Parameters”| Parameter | Type |
|---|---|
type | ValueType |
value | | string | number | bigint | boolean | SplitData | SymbolicNode | MatrixData | RangeData | ColourData | ChartData | IpCidrData |
unit? | string |
Returns
Section titled “Returns”Value
Properties
Section titled “Properties”datetimeSpan?
Section titled “datetimeSpan?”optional datetimeSpan?: boolean;Defined in: packages/engine/src/vm/Value.ts:526
That this quantity is the gap between two datetimes, rather than a duration someone wrote down.
Both are milliseconds and both are a Uom in "ms", so the unit cannot
tell them apart, and the formatter used to render every one of them as a
clock. That is right for 10:30 - 08:00, which reads 2:30, and wrong
for 40ms + 120ms, which read 0:00 instead of 190 ms: a latency budget
is a quantity and not a time of day. Set where two datetimes subtract, and
read only by the formatter.
It survives the arithmetic that keeps a span a span: adding two spans,
scaling one by a plain number, and totalling a column of them, so a
timesheet still reads as a clock and half a shift is 0:30. It does not
survive a conversion, because (10:30 - 08:00) in ms asked for
milliseconds and should be given them, nor combining with a quantity
somebody typed. Cleared by recycle alongside the other sidecars.
decimalPlaces?
Section titled “decimalPlaces?”optional decimalPlaces?: number;Defined in: packages/engine/src/vm/Value.ts:468
The number of decimal places this value should DISPLAY at, when it has been given an explicit precision.
A display sidecar, not a value one: value is unchanged, so every
.value/toNumber() reader and all arithmetic behave exactly as before,
and a value with no decimalPlaces formats the way it always did (the
global two-place default with trailing zeros trimmed). It is set only by an
explicit precision request, <x> to N dp and round(x, N), so that
3.14159 to 4 dp shows 3.1416 and 1.5 to 2 dp shows 1.50 rather than
the value being rounded but then displayed at the default two places. It is
NOT propagated through arithmetic (a later + 1 re-decides precision),
which is why nothing that did not ask for a precision is affected. Cleared
by recycle alongside the other sidecars.
exact?
Section titled “exact?”optional exact?: DecimalData;Defined in: packages/engine/src/vm/Value.ts:417
The exact base-ten value this Value stands for, when it has one.
A sidecar rather than a replacement for value: money and decimal-point
literals set it to a DecimalData so that same-currency arithmetic
and display can be exact (“$0.10 + $0.20” is “$0.30”, not
“$0.30000000000000004”), while value stays the nearest double so every
existing consumer that reads .value or toNumber() is unchanged. The
plain Number-times-Number fast paths deliberately ignore it, which is why
a bare “0.1 + 0.2” still answers the double it always did: exactness is
carried only where a unit-bearing operand asks for it. Cleared by
recycle so a reused arena Value never inherits a stale exact.
grain?
Section titled “grain?”optional grain?: DatetimeGrain;Defined in: packages/engine/src/vm/Value.ts:492
What this instant anchors: a calendar day, a wall-clock reading on one, or a fixed point on the timeline.
The fifth sidecar, the same shape as exact, rational,
uncertainty and decimalPlaces and for the same reason: a
Datetime’s payload is epoch milliseconds and has to stay a plain number,
because the arena union, the worker DTO, the snapshot’s Datetime variant
and the Datetime SUB path all read it as one. The number cannot say
which of three questions it is the answer to, and the difference is not
recoverable from it: formatDatetime decides whether to print a time by
testing whether the local hour, minute, second and millisecond are all
zero, which is a guess that 2026-04-03T09:00:00+09:00 under TZ=UTC
gets wrong, because the nine o’clock the reader typed IS UTC midnight.
Set only where the shape is known: a date literal is 'date', a
wall-clock literal (2026-04-03T09:30, 6pm) is 'datetime', a literal
carrying Z or an offset is 'instant', and so is now. Absent means
not recorded, which every reader treats as an instant rather than
inferring one. Nothing in 2.26.0 displays it: the formatter does not read
it, so every rendered date is byte-identical to 2.25.0. Cleared by
recycle alongside the other sidecars.
rational?
Section titled “rational?”optional rational?: Rational;Defined in: packages/engine/src/vm/Value.ts:435
The exact rational value this Value stands for, when it has one.
The second sidecar, the same shape as exact and for the same
reason: a fraction has no exact base-ten form (1/3 is not any decimal),
so exact fraction arithmetic needs a numerator/denominator pair rather
than a coefficient and a scale. Integer division seeds it (“1/3” carries
the Rational 1/3), and +, -, *, / between rational-bearing
numbers keep it reduced, so “1/49 * 49” is exactly 1 and “5/6 - 1/6 - 1/6
- 1/6 - 1/6 - 1/6” is exactly 0 rather than the 1.6e-16 the doubles drift
to.
valuestill holds the nearest double, recomputed from the exact rational so accumulation error never creeps in, which is why the default display and every.value/toNumber()reader are unchanged. Only a fraction written with ”/” carries it, so a decimal literal (“0.1”) and a transcendental result (“sqrt(2)”) stay the plain doubles they were. Cleared by recycle alongside exact.
timedOut?
Section titled “timedOut?”optional timedOut?: boolean;Defined in: packages/engine/src/vm/Value.ts:403
Set by async resolvers when a fetch timed out, the result is a fallback (typically 0).
type: ValueType;Defined in: packages/engine/src/vm/Value.ts:399
uncertainty?
Section titled “uncertainty?”optional uncertainty?: number;Defined in: packages/engine/src/vm/Value.ts:452
The one-sigma uncertainty (standard error) this value carries, when it has one.
The third sidecar, the same shape as exact and rational
and for the same reason: a measurement written 12.3 ± 0.5 is still the
number 12.3 everywhere it is read as one, so the type stays
ValueType.Number and value stays the center, while this non
negative field carries the tolerance. The ± (or ASCII +/-) operator
seeds it, and +, -, *, / propagate it in quadrature for
independent errors (see vm/VMConversion.ts’s uncertainOp). Everything
else (a comparison, a transcendental function, a unit conversion) reads
the center through toNumber() and drops the tolerance, which is why a
value with no uncertainty behaves exactly as a plain number always did.
Cleared by recycle alongside the other two sidecars.
optional unit?: string;Defined in: packages/engine/src/vm/Value.ts:401
value: | string | number | bigint | boolean | SplitData | SymbolicNode | MatrixData | RangeData | ColourData | ChartData | IpCidrData;Defined in: packages/engine/src/vm/Value.ts:400
optional zone?: string;Defined in: packages/engine/src/vm/Value.ts:507
The zone this instant should be read and displayed in, when the line named one.
A zone reference in the encoding calendar/IntlZone.ts documents: an IANA
name ("Asia/Tokyo") or a fixed offset ("UTCOFFSET:540"). Set by
<datetime> in <zone> and by an ISO literal carrying Z or an explicit
offset, which names an offset rather than a zone and so records one.
Absent means the value is read in the calendar backend’s own zone, which
is what every Datetime meant before this sidecar existed. Like
grain it is recorded and readable in 2.26.0 and read by the
formatter from 3.0. Cleared by recycle alongside the other
sidecars.
Accessors
Section titled “Accessors”errorCode
Section titled “errorCode”Get Signature
Section titled “Get Signature”get errorCode(): string | undefined;Defined in: packages/engine/src/vm/Value.ts:690
The stable ErrorFactory code of an Error value, else undefined.
The code is the machine-branchable identifier (the message is the
human-facing text, errorMessage). It reads from the same slot
errorValue writes, so it is defined for exactly the values
isError accepts and undefined for every other type.
Returns
Section titled “Returns”string | undefined
errorMessage
Section titled “errorMessage”Get Signature
Section titled “Get Signature”get errorMessage(): string | undefined;Defined in: packages/engine/src/vm/Value.ts:698
The human-facing message of an Error value, else undefined. Paired with
errorCode, which carries the branchable identifier.
Returns
Section titled “Returns”string | undefined
Methods
Section titled “Methods”clone()
Section titled “clone()”clone(): Value;Defined in: packages/engine/src/vm/Value.ts:594
A fresh Value carrying everything this one does: the payload, the unit, the cached number and every sidecar, present or future.
The inverse of recycle, and written as a whole-object copy rather
than a list of fields on purpose. persistentValue() used to name the
sidecars it copied, and the two it did not name (decimalPlaces,
timedOut) were silently dropped by every viewport evaluation, so
3.14159 to 4 dp displayed as 3.14 there and as 3.1416 on a single line.
A sidecar added later is carried here without this method knowing its
name, which closes that class of bug rather than the one instance.
Shallow, as the old copy was: a matrix, a chart or a symbolic tree is
shared with the original, which is fine because a Value’s payload is
treated as immutable everywhere outside the arena’s own recycle().
Returns
Section titled “Returns”Value
A new Value equal to this one in every field.
isBigInt()
Section titled “isBigInt()”isBigInt(): this is Value & { value: bigint };Defined in: packages/engine/src/vm/Value.ts:608
Returns
Section titled “Returns”this is Value & { value: bigint }
isChart()
Section titled “isChart()”isChart(): this is Value & { value: ChartData };Defined in: packages/engine/src/vm/Value.ts:635
Returns
Section titled “Returns”this is Value & { value: ChartData }
isColour()
Section titled “isColour()”isColour(): this is Value & { value: ColourData };Defined in: packages/engine/src/vm/Value.ts:631
Returns
Section titled “Returns”this is Value & { value: ColourData }
isError()
Section titled “isError()”isError(): boolean;Defined in: packages/engine/src/vm/Value.ts:658
An Error value: a fault carrying a code and message, propagated through the DAG when a plugin or opcode cannot produce a quantity.
An Error reads as the number 0 through toNumber, so a numeric consumer that does not check this first cannot tell the fault apart from a real zero: exactly the silently-wrong result the engine guards against internally with faultedOperand. A host reading a live Value off a result should branch on this (or isFault) before trusting the number, and read errorCode/errorMessage for the detail.
Returns
Section titled “Returns”boolean
isFault()
Section titled “isFault()”isFault(): boolean;Defined in: packages/engine/src/vm/Value.ts:678
Either fault, an Error or a Pending: the single predicate to check before trusting toNumber. It mirrors the internal faultedOperand rule that every opcode applies to its operands, so a host reading a result makes the same distinction the engine does rather than computing with a zero it cannot tell from a real one.
Returns
Section titled “Returns”boolean
isHex()
Section titled “isHex()”isHex(): this is Value & { value: number };Defined in: packages/engine/src/vm/Value.ts:604
Returns
Section titled “Returns”this is Value & { value: number }
isIpCidr()
Section titled “isIpCidr()”isIpCidr(): this is Value & { value: IpCidrData };Defined in: packages/engine/src/vm/Value.ts:639
Returns
Section titled “Returns”this is Value & { value: IpCidrData }
isMatrix()
Section titled “isMatrix()”isMatrix(): this is Value & { value: MatrixData };Defined in: packages/engine/src/vm/Value.ts:616
Returns
Section titled “Returns”this is Value & { value: MatrixData }
isNaN()
Section titled “isNaN()”isNaN(): boolean;Defined in: packages/engine/src/vm/Value.ts:757
Returns
Section titled “Returns”boolean
isNumber()
Section titled “isNumber()”isNumber(): this is Value & { value: number };Defined in: packages/engine/src/vm/Value.ts:600
Returns
Section titled “Returns”this is Value & { value: number }
isPending()
Section titled “isPending()”isPending(): boolean;Defined in: packages/engine/src/vm/Value.ts:667
A Pending value: an async result that has not yet resolved, holding the query key it is waiting on. Reads as 0 through toNumber, the same caveat as isError: a settled value arrives on a later evaluation.
Returns
Section titled “Returns”boolean
isRange()
Section titled “isRange()”isRange(): this is Value & { value: RangeData };Defined in: packages/engine/src/vm/Value.ts:627
Returns
Section titled “Returns”this is Value & { value: RangeData }
isString()
Section titled “isString()”isString(): this is Value & { value: string };Defined in: packages/engine/src/vm/Value.ts:612
Returns
Section titled “Returns”this is Value & { value: string }
isSymbolic()
Section titled “isSymbolic()”isSymbolic(): this is Value & { value: SymbolicNode };Defined in: packages/engine/src/vm/Value.ts:643
Returns
Section titled “Returns”this is Value & { value: SymbolicNode }
isVectorShape()
Section titled “isVectorShape()”isVectorShape(): boolean;Defined in: packages/engine/src/vm/Value.ts:621
A Matrix shaped like a vector, 1×N (row) or N×1 (column).
Returns
Section titled “Returns”boolean
recycle()
Section titled “recycle()”recycle( type, value, unit?): void;Defined in: packages/engine/src/vm/Value.ts:547
Phase 5.3: Reset all fields for arena reuse. Called by ValueArena.acquire(), zero allocation, just field assignment.
Parameters
Section titled “Parameters”| Parameter | Type |
|---|---|
type | ValueType |
value | | string | number | bigint | boolean | SplitData | SymbolicNode | MatrixData | RangeData | ColourData | ChartData | IpCidrData |
unit? | string |
Returns
Section titled “Returns”void
toNumber()
Section titled “toNumber()”toNumber(): number;Defined in: packages/engine/src/vm/Value.ts:702
Returns
Section titled “Returns”number