Browse documentation
aner.units

Scalar quantities and units

Keep distance and temperature units in native scalar types, use concise unit literals, and check conversions and affine arithmetic.

aner.units gives a scalar Float64 an immutable registered unit and a checked type. Use Quantity<m> for linear quantities such as distance and Point<degC> for absolute temperature readings. A temperature difference is a quantity, such as Quantity<delta_degC>. This scalar module uses the same bounded native registry as SemanticTensor and EvidenceTensor.

See the language reference for syntax, concise research code for the first syntax increment, and scientific measurements for source aware uncertainty. A scalar unit identifies a physical magnitude; it does not claim measurement uncertainty or error source provenance.

These features require the updated native development runtime built from this source and matching editor support. Earlier packaged executables may reject the new types and literals. See installation. Packed unit bearing arrays are implemented in the updated development runtime. Automatic dataset normalization, arbitrary unit expressions and typed named simulation output schemas remain planned.

Native scalar API

Import aner.units before declaring scalar unit types or using their literals and operations. Unit codes are case sensitive and come from the registry below. Constructor and conversion unit arguments must be literal Strings: the compiler needs the target code to determine the result type.

OperationSignature and resultContract
Linear constructionunits.quantity(value: Float64, unit: String) → Quantity<UnitCode>Attaches a registered linear or temperature interval unit to a finite raw magnitude.
Point constructionunits.point(value: Float64, unit: String) → Point<UnitCode>Attaches a registered absolute temperature unit.
Conversionunits.convert(value, target_unit: String) or value.to(target_unit: String)Converts magnitude and returns the target unit in the checked type; requires compatible dimension and kind.
Raw magnitudeunits.value(value) or value.value() → Float64Explicitly drops the unit from this returned number. Save or transmit value.unit() alongside exported numbers.
Unit codeunits.unit(value) or value.unit() → StringReturns the exact declared code.
Displayprint(value), String(value)Retains magnitude and unit in literal form, such as 2.0@m.
Type inspectiontype(value) → StringReports Quantity<m>, Point<degC> or a quoted fixed code such as Quantity<"m/s">.
Storage inspectiondtype(value) → StringReports Float64; this does not erase the value's checked unit.

Operations use native scalar arithmetic. Each value stores a Float64 magnitude and a reference to immutable registry metadata; it does not allocate a Tensor, user object or owned unit String. Copies preserve magnitude and declared unit. Conversion creates another scalar value. This is a storage contract, not a new timing benchmark or a claim that interpreted programs have no overhead.

Concise literals and annotations

aner
import aner.units
let length: Quantity<m> = 2.0@m
let temperature: Point<degC> = -5.0@degC
let rise: Quantity<delta_degC> = 5.0@delta_degC
let speed: Quantity<"m/s"> = 3.0@"m/s"
let fraction: Quantity<"1"> = 0.5@"1"
print(length)
print(temperature)
print(rise)
print(speed)
print(fraction)

Storage is Float64. Write 2.0@m; 2@m is rejected rather than silently converting Int64. The @ marker and unit code must attach on the numeric literal's physical line. Unit codes are metadata, so an ordinary variable named m does not change what 2.0@m means. A quoted fixed code permits / or the dimensionless code 1; it is not a unit expression parser.

A signed lexical literal attaches its sign before point construction: -5.0@degC is a reading of negative five Celsius. Unary negation of an existing Point, including -(5.0@degC), is rejected. Lexical signed zero is retained.

Attach only to numeric literals. For a computed raw Float64 magnitude, use units.quantity(calculation, "m") or units.point(calculation, "degC"). Applying @ to a variable or parenthesized expression is rejected. Construction attaches a declared source unit; conversion is to, never relabeling an already unit bearing value.

Annotations, parameters, return values and class fields retain exact unit identity. A function accepting Quantity<m> requires meters; pass (150.0@cm).to("m") explicitly. Compatible units in arithmetic do not make assignments or function arguments implicitly convertible. The no value return type Unit retains its existing meaning.

Distance and time arithmetic

aner
import aner.units
let distance = 2.0@m + 50.0@cm
let seconds = 5.0@s
let speed: Quantity<"m/s"> = distance / seconds
print(distance)            // 2.5@m
print(distance.to("cm"))   // 250.0@cm
print(speed)               // 0.5@"m/s"
print(1.0@m == 100.0@cm)    // true

Addition and subtraction of quantities require compatible dimensions and retain the left operand's unit. Float64 scaling preserves the quantity's unit: distance * 2.0, 2.0 * distance, and distance / 2.0. Int64 scaling requires an explicit Float64 conversion. Unary negation is valid for a quantity.

Products and ratios of two quantities return a canonical registered linear unit. Examples include distance divided by time → m/s, force multiplied by distance → J, and a ratio of compatible quantities → Quantity<"1">. The result remains unit bearing even when dimensionless. An unregistered result dimension, such as m * m, is rejected during checking. This increment does not invent square meter or arbitrary composed unit descriptors. Raw Float64 divided by a quantity is also outside this initial API.

Comparisons require compatible dimensions and the same quantity/point kind. Values with the same declared unit compare their raw magnitudes directly. Cross unit typed comparisons normalize both operands to a common canonical representation; normalization that overflows or loses a nonzero linear value to zero is rejected. Equality is exact, without a hidden tolerance. Explicit conversion and arithmetic use Float64 rounding, so equivalent decimal temperatures may differ by a rounding residual. Evaluate a difference in a chosen interval unit when applying a model's explicit tolerance.

Absolute temperatures and differences

ExpressionResult
25.0@degC - 20.0@degC5.0@delta_degC
20.0@degC + 5.0@delta_K25.0@degC
5.0@delta_K + 20.0@degC25.0@degC
20.0@degC - 5.0@delta_K15.0@degC
Point plus PointRejected
Quantity minus PointRejected
Point scaling, products, ratios or negationRejected
Point converted to an interval unitRejected

degC, degF and K are point units. delta_degC, delta_degF and delta_K are interval units. Fahrenheit conversion applies both scale and offset to points, and scale alone to intervals. Unit compatibility does not impose a model specific physical temperature range; finite negative magnitudes remain permitted.

Mutable scalar bindings support compound assignment when the resulting type stays exactly the binding's type:

aner
import aner.units
var length = 2.0@m
length += 50.0@cm
length *= 2.0
var room = 20.0@degC
room += 5.0@delta_K
print(length)
print(room)

Subtracting a Point from that Point binding with -= would change its type to a Quantity, so checking rejects it. Use a separate result binding. Existing restrictions on class field compound assignment remain in effect.

Registered unit codes

The shared registry contains these 23 exact codes:

Kind or dimensionCodes
Dimensionless quantity1
Lengthm, cm, mm, km
Times, h
Speedm/s, km/h
Force and force per lengthN, N/m
Absolute temperature pointdegC, degF, K
Temperature intervaldelta_degC, delta_degF, delta_K
Masskg
EnergyJ, kWh
PowerW, kW
PressurePa

Quantity<m> and Point<degC> have one fixed unit parameter and always use Float64 storage. Missing, unknown or wrongly classified units fail checking. Quantity<Float64, m> is not this increment's syntax.

Complete runnable examples

Download scalar unit basics and the laboratory observation example. The laboratory example uses class fields with exact units, explicit ingestion conversions, a unit bearing rate, an explicit zero time error, Result propagation and a temperature interval threshold. Both files include complete local input data and require no credentials, services or external datasets.

sh
aner check examples/units_basic.aner
aner run examples/units_basic.aner
aner check examples/units_lab_monitoring.aner
aner run examples/units_lab_monitoring.aner

The scalar basics page and laboratory page display their exact downloadable source. The basic example prints:

text
20.0@degC
293.15@K
25.0@degC
5.0@delta_degC
2.5@m
250.0@cm
0.5@"m/s"
Quantity<"m/s">
Float64
0.5
m/s
2.5@m
true

The laboratory example prints:

text
Measured speed: 3.0@"m/s"
5.0@delta_degC
true
elapsed time must be positive

These outputs were checked by executing the complete sources with the updated native runtime.

Storage, wrappers and parallel boundaries

Quantities and points are immutable values. They can live in typed class fields, user generic classes, Optional/Result payloads and heterogeneous List/Dictionary values. cast<Quantity<m>> checks the exact retained unit; it does not convert centimeters into meters. Any/List equality retains the established strict concrete representation rule: equal quantities must have the same declared unit and magnitude there. Typed scalar operators instead compare compatible physical values. Quantity/Point Dictionary keys, Set elements and List sorting are not admitted in this initial implementation.

Native Array/DynamicArray/NDArray unit specializations now accept Quantity<UnitCode> and Point<UnitCode> elements alongside primitive Int64, Float64, Bool and String. A unit buffer stores native Float64 magnitudes and one immutable unit descriptor. Exact writes, typed reductions, literal target conversion and explicit .values() export preserve the unit boundary. These mutable containers do not add the coordinate or uncertainty contracts of SemanticTensor/EvidenceTensor. Automatic CSV unit normalization and unit bearing dataset columns require a separate ingestion design.

Immutable scalar quantities also cross an isolated parallel.repeat parameter snapshot and can be used by checked local helpers. Results still use the existing numeric output contract: exporting .value() intentionally returns Float64 and requires the application to retain the associated unit description. Typed named simulation result columns remain planned.

Diagnostics and numerical bounds

Unknown/mismatched static units, invalid point algebra, incompatible dimensions, dynamic target unit Strings, unsupported products and raw numeric substitutions fail before execution. Unit arithmetic never implicitly converts a quantity or point to Float64.

Runtime scalar unit errors are source located R3701 diagnostics carrying a native unit/numerical error code. Inputs and returned magnitudes must be finite. Division by zero, nonfinite input, overflowing results and a nonzero linear scale conversion rounded to zero are rejected. Legitimate point offsets and exact cancellation may yield zero. Representable subnormal results are admitted. The module follows native Float64 arithmetic and makes no cross platform bitwise or exact decimal conversion promise.

Guides and references for aner.units

Aner documentation · Reference and guides