Provisional Numbers
numeric is Typical's provisional numeric annotation. It allows the compiler to select and revise a concrete numeric primitive as information becomes available from literals, operations, assignments, arguments, and return values.
numeric is not a runtime numeric type. Every provisional number has a compiler-known concrete representation before it is lowered.
number has a different meaning: it is an alias for the concrete f64 type.
provisional is numeric = 1.25
floatingPoint is number = 1.25
The first value begins as a fixed decimal. The second value is an f64.
Literal Inference
An unsuffixed whole-number literal begins with the narrowest integer type that can represent its value. Positive values prefer unsigned integers; negative values require signed integers.
count = 0
console.log(getPrimitiveTypeName(count)) // u8
An unsuffixed literal with a decimal point begins as the narrowest fixed decimal that can represent the written value.
amount = 1.25
console.log(getPrimitiveTypeName(amount)) // u8.2
A lowercase f suffix pins a literal to f32. A lowercase ff suffix pins it to f64.
single = 1.25f
double = 1.25ff
Progressive Resolution
The compiler revises a provisional number when later operations require another concrete representation.
x is var numeric = 0
x += getInt16()
console.log(getPrimitiveTypeName(x)) // i16
x is var numeric = 0
x += getInt16()
x += getFloat32()
console.log(getPrimitiveTypeName(x)) // f32
This is a compile-time process. The runtime value does not carry a tag saying that it is provisional, and its primitive type does not change while the program executes.
When control-flow paths require different representations, the compiler first selects a common type that accepts every path without loss. If no such concrete type exists, the joined value uses a sum type rather than silently discarding information.
Pinning
Context can pin a provisional number to a concrete type.
small is u8 = 10
ratio is f32 = 1 / 3
price is decimal = 19.99
Stored fields, serialized layouts, and foreign interfaces must have a concrete representation by the time they are lowered. A numeric parameter or return is specialized to the concrete representation required by its call site.
If the surrounding program does not otherwise constrain a provisional literal, it keeps the narrowest concrete type that represents that literal.
Automatic Conversion
Typical automatically converts compatible numeric representations:
- A provably lossless conversion is inserted silently.
- A potentially lossy conversion is inserted and receives the saw decoration (🪚).
declare noAutomaticNumberConversionreports potentially lossy automatic conversions within its scope. An explicit conversion satisfies the declaration.
The saw is an editor decoration. It communicates that the generated program contains a conversion that can discard information.
source is u16 = 257
target is u8 = 🪚source
console.log(target) // 1
Typical still produces compiled output when an automatic conversion violates declare noAutomaticNumberConversion. The compiler reports a notice and uses the ordinary conversion as its recovery behavior.
Fixed-Decimal Conversion
Converting a fixed decimal to a smaller scale truncates toward zero by default.
source is i32.4 = 1.2399
target is i32.2 = 🪚source
console.log(target) // 1.23
Potentially discarding fractional digits receives a saw. Rounding and round-and-clamp behavior are requested through explicit conversion operations rather than a scoped rounding declaration.
Integer-To-Float Conversion
An integer type converts losslessly to a floating-point type only when every value of the source type is exactly representable in the target type.
| Source | Lossless targets |
|---|---|
i8, u8 |
f16, f32, f64, f128 |
i16, u16 |
f32, f64, f128 |
i32, u32 |
f64, f128 |
i64, u64 |
f128 |
i128, u128 |
None |
There is no type-wide provably lossless conversion from floats to integers, floats to fixed decimals, fixed decimals to floats, or arbitrary-precision numbers to fixed-width numbers.
Type Attestations
numeric can test whether an otherwise broad value is one of Typical's built-in numeric primitives. number tests specifically for f64 because it is an alias for that type.
write(value is any)
(
if (value is numeric) (
console.log(value)
)
)
A provisional value may also be tested against its currently selected concrete primitive. The result is determined statically for a given compilation.
x is var numeric = 0
if (x is u8) (
console.log("x currently resolves to u8")
)
Editing the surrounding program may cause the compiler to select another concrete type and therefore change the result of this attestation in the next compilation.