This site describes solve-engine 2.44.0, the version npm installs.
Value
Defined in: packages/engine/src/vm/Value.ts:544
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:791
Parameters
Section titled “Parameters”| Parameter | Type |
|---|---|
type | ValueType |
value | | string | number | bigint | boolean | SymbolicNode | MatrixData | RangeData | ColourData | SplitData | ChartData | IpCidrData |
unit? | string |
Returns
Section titled “Returns”Value
Properties
Section titled “Properties”calendarName?
Section titled “calendarName?”calendarName?: CalendarName;Defined in: packages/engine/src/vm/Value.ts:765
For a String that is a weekday or month name drawn from a date (as weekday, as month), which name it is.
The text stays the engine’s English ("Tuesday"), so a comparison with
"Tuesday", a join with other text and a lookup all read it as before;
the formatter reads this to write the name in the reader’s language
(Dienstag under de). Text built from the name ("on " + x) is new
text and carries nothing. A variable holding the name keeps it, through
clone. Cleared by recycle alongside the other sidecars.
datetimeSpan?
Section titled “datetimeSpan?”datetimeSpan?: boolean;Defined in: packages/engine/src/vm/Value.ts:710
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?”decimalPlaces?: number;Defined in: packages/engine/src/vm/Value.ts:622
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.
divisionByZero?
Section titled “divisionByZero?”divisionByZero?: boolean;Defined in: packages/engine/src/vm/Value.ts:789
Set on an infinity that a division by zero gave (1/0, 40 is what % of 0), so a refusal can name that cause; an infinity reached because a
number grew past the largest double (2^2000, 1e309) carries none.
The double alone cannot tell the two apart. Set by the VM’s / (see
vm/IndeterminateQuotient.ts’s zeroDivisorQuotient) and carried by +,
-, * and ^ when an operand had it and the result is still
infinite. Cleared by recycle.
exact?
Section titled “exact?”exact?: DecimalData;Defined in: packages/engine/src/vm/Value.ts:570
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. Plain
numbers carry it through arithmetic as money does (see vm/ExactDecimals.ts),
so “0.1 + 0.2” is exactly 0.3; the plain fast paths leave only for an
operand that carries it, so two whole numbers never pay for it. Cleared by
recycle so a reused arena Value never inherits a stale exact.
frozen?
Section titled “frozen?”frozen?: FrozenMark;Defined in: packages/engine/src/vm/Value.ts:738
That this value is a frozen answer: when it was frozen, and the key it is stored under.
Set only on what a frozen line answers with (see
engine/FrozenValues.ts), and deliberately not carried by arithmetic: a
value computed from a frozen one is new, and says so through
sources, whose records carry the freeze date. Cleared by
recycle.
grain?
Section titled “grain?”grain?: DatetimeGrain;Defined in: packages/engine/src/vm/Value.ts:646
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?”rational?: Rational;Defined in: packages/engine/src/vm/Value.ts:589
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. A fraction written with ”/” carries it, and so does a division of decimals that never ends (“0.1 / 3” is 1/30); a decimal that ends carries exact instead, and a transcendental result (“sqrt(2)”) carries neither. Cleared by recycle alongside exact.
sources?
Section titled “sources?”sources?: readonly ValueSource[];Defined in: packages/engine/src/vm/Value.ts:727
Where the live figures behind this value came from: the provider, how the engine came by each one, and when.
A provenance sidecar, the same shape as the others and kept off the same
paths: value is untouched, so every reader of the number is unchanged,
and a value computed only from what the reader typed carries nothing. It is
set where a live figure enters (a currency conversion reads the rate
table’s record, createQueryResolver stamps what it fetched) and merged by
arithmetic, so (10 USD in GBP) * 3 carries the rate’s record and a host
can mark the line as rate-dependent. The ADD/SUB/MUL/DIV fast paths decline
a value carrying it, exactly as they decline a rational or an uncertainty.
The list is shared, never mutated, so a value copied by clone
shares it safely. See vm/Provenance.ts. Cleared by recycle
alongside the other sidecars.
timeAnchor?
Section titled “timeAnchor?”timeAnchor?: number;Defined in: packages/engine/src/vm/Value.ts:674
For a time of day (grain "time", #708), an instant on the day it is
counted from, in epoch milliseconds: today’s midnight for a clock time, the
value’s own instant for one written as time. The formatter shows the
time alone and the days it has moved from this one beside it, read in the
value’s zone (1:00:00 AM (+1 day) for 11pm + 2 hours; 8:00 AM (+1 day) for 11pm London in Tokyo on 10 March 2026, since Tokyo’s clock is
on the day after the one the reader named), so the shift is fixed when the
time is written rather than counted from whatever day it is displayed on.
Carried through duration arithmetic with the grain. Cleared by
recycle alongside the other sidecars.
timedOut?
Section titled “timedOut?”timedOut?: boolean;Defined in: packages/engine/src/vm/Value.ts:556
Set by async resolvers when a fetch timed out, the result is a fallback (typically 0).
timePrecision?
Section titled “timePrecision?”timePrecision?: "minute";Defined in: packages/engine/src/vm/Value.ts:680
For a time of day, that it is written to the minute (7:00 PM) rather
than to the second, as every time-zone answer is (#757). Carried through
duration arithmetic with the grain. Cleared by recycle.
type: ValueType;Defined in: packages/engine/src/vm/Value.ts:552
uncertainty?
Section titled “uncertainty?”uncertainty?: number;Defined in: packages/engine/src/vm/Value.ts:606
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.
unit?: string;Defined in: packages/engine/src/vm/Value.ts:554
unitLabel?
Section titled “unitLabel?”unitLabel?: UnitLabel;Defined in: packages/engine/src/vm/Value.ts:779
The name a quantity is shown under when the reader wrote a word for its
unit that is not the unit’s own: a package’s alias (Meile for the mile)
or a unit the document defined (sprints, two weeks each). The quantity
itself stays in unit, so it converts and adds as that unit does;
only the written answer changes, to value / per followed by name
(#762).
Set on the quantity a literal (2 Meile) or a conversion (5 km in Meile, 84 days in sprints) produces. Arithmetic does not carry it,
since a computed value is a new quantity; a variable holding a labelled
value does, through clone. Cleared by recycle.
unspelledUnit?
Section titled “unspelledUnit?”unspelledUnit?: string;Defined in: packages/engine/src/vm/Value.ts:753
The unit this plain number is measured in when the engine cannot spell
it yet: J·s for planck, C (the coulomb) for elementary charge.
Set only by the constants package (#648), on a physical constant whose unit
needs a dimension the unit table does not have (charge, the mole). The
value stays a Number, so planck * 2 and avogadro / 1000 are plain
numbers as they always were; what the mark changes is a quantity meeting
it. planck * 5e14 Hz read the constant as a bare number and answered in
hertz, the other operand’s unit, and is refused by name instead. Arithmetic
does not carry it, since a computed value is not the constant any more; a
variable holding the constant does, through clone. Cleared by
recycle alongside the other sidecars.
value: | string | number | bigint | boolean | SymbolicNode | MatrixData | RangeData | ColourData | SplitData | ChartData | IpCidrData;Defined in: packages/engine/src/vm/Value.ts:553
zone?: string;Defined in: packages/engine/src/vm/Value.ts:661
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.
zoneDifference?
Section titled “zoneDifference?”zoneDifference?: ZoneDifference;Defined in: packages/engine/src/vm/Value.ts:691
For a duration that is the gap between two places’ clocks (time difference between London and Tokyo, #757), the two places, so the
formatter writes the gap as a direction: Tokyo is 8 hours ahead of London. The quantity is a plain signed duration in hours, positive when
the second place is ahead, so it converts (in hours), adds and compares
as any duration does; a conversion or arithmetic gives a plain duration,
since the answer is then a new quantity. A variable holding it keeps it,
through clone. Cleared by recycle.
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:1008
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:1016
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:872
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:926
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:953
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:949
Returns
Section titled “Returns”this is Value & { value: ColourData }
isError()
Section titled “isError()”isError(): boolean;Defined in: packages/engine/src/vm/Value.ts:976
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:996
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:922
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:957
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:934
Returns
Section titled “Returns”this is Value & { value: MatrixData }
isNaN()
Section titled “isNaN()”isNaN(): boolean;Defined in: packages/engine/src/vm/Value.ts:1081
Returns
Section titled “Returns”boolean
isNumber()
Section titled “isNumber()”isNumber(): this is Value & { value: number };Defined in: packages/engine/src/vm/Value.ts:918
Returns
Section titled “Returns”this is Value & { value: number }
isPending()
Section titled “isPending()”isPending(): boolean;Defined in: packages/engine/src/vm/Value.ts:985
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:945
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:930
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:961
Returns
Section titled “Returns”this is Value & { value: SymbolicNode }
isVectorShape()
Section titled “isVectorShape()”isVectorShape(): boolean;Defined in: packages/engine/src/vm/Value.ts:939
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:810
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 | SymbolicNode | MatrixData | RangeData | ColourData | SplitData | ChartData | IpCidrData |
unit? | string |
Returns
Section titled “Returns”void
toJSON()
Section titled “toJSON()”toJSON(): Record<string, unknown>;Defined in: packages/engine/src/vm/Value.ts:893
The value as plain JSON, for a host that logs, stores or posts a result.
JSON.stringify of a Value used to throw “Do not know how to serialize a
BigInt” as soon as the value carried an exact sidecar, which every decimal
literal did and, since exact decimals and exact large integers, most
computed answers do (#598). This writes the fields a host already reads
(type, value, unit), then each sidecar that is set, with every
bigint as its decimal string: exact as the decimal it stands for
("0.3") and rational as "n/d". The private cached number is left
out. It is for reading, not a snapshot format; engine.toJSON() is the
one that restores.
Returns
Section titled “Returns”Record<string, unknown>
A JSON-safe object describing this value.
toNumber()
Section titled “toNumber()”toNumber(): number;Defined in: packages/engine/src/vm/Value.ts:1020
Returns
Section titled “Returns”number