Number precision
A computed number is emitted as the shortest decimal that is still the same
number — within a relative tolerance of 1e-10. One policy, every position.
This is noise removal, not a precision limit. Jess does its arithmetic in JavaScript
doubles, and 0.1 + 0.2 really is 0.30000000000000004 — a different double from
0.3. Printing the shortest string that reads back as the same double therefore
cannot help; what Jess does instead is emit the shortest decimal within 1e-10
(relative) of the computed value, which drops digits that are only float residue and
keeps digits that mean something.
Noise goes, earned digits stay
.a {
a: $(0.1 + 0.2); // float noise
b: $(100% / 3); // a real repeating decimal
c: pi();
}
.a {
a: 0.3;
b: 33.333333333%;
c: 3.1415926536;
}
There is no significant-figure cap and no per-unit rule. A cap would discard real
digits at larger magnitudes — 15.4px + 10cm is 393.3527559px, because the
cm-to-px factor 96/2.54 is a repeating decimal, and an 8-figure cap would print
393.35276px — to save a fraction of a percent of output size. CSS Values 4 §5
leaves numeric precision explicitly implementation-defined, so nothing requires
one.
Two consequences worth knowing:
- Small magnitudes survive. The tolerance is relative, not a fixed number of
decimal places, so a computed
0.00000000123456789stays itself rather than collapsing to0. - Exact integers are never touched. An integer carries no float noise, so there is nothing to remove — however many digits it has.
Output is never written in scientific notation, per
CSSOM §6.7.2 ("scientific
notation is not used"). That section also caps its own serialization at six
decimals; this policy deliberately does not, because CSSOM governs what
getComputedStyle hands back rather than what a compiler may write, and a
decimal-place cap is the axis argued against above.
Un-operated literals are emitted verbatim
Only a computed number goes through the policy. A literal you wrote is emitted exactly as written — see Verbatim values:
.a {
a: 1.50000px;
b: 2PX;
c: 0.00000000123456789;
}
.a {
a: 1.50000px;
b: 2PX;
c: 0.00000000123456789;
}
The one spelling adjustment is a leading decimal point: .3s is emitted as 0.3s.
Every position spells a value the same way
A number spliced into a selector, a property name, or a string prints the same bytes it would print in a declaration value:
$n: pi();
.a {
width: $n;
content: "${n}";
}
.a {
width: 3.1415926536;
content: "3.1415926536";
}
This is a language rule, not an implementation detail: position may change whether a value is emitted, never how it is spelled.
Interpolation is written ${name} in string, selector, property-name, and
at-rule-prelude position ($[…] is a lookup and $( … ) an expression, both
value-position; $[…] is rejected in a string).
A ${…} splice is grammar structure everywhere it is accepted, including
inside a custom-property name and value (--raw: ${n}, --${name}: 1), inside
a plain quoted string (content: "fonts/${family}.css"), and inside an escaped
~"…" string.
The one place it is not spliced is a quoted string nested inside a
custom-property value: --raw: "${n}" keeps the whole string as authored CSS
bytes, because a custom property's value is a verbatim token stream. Splice the
value directly (--raw: ${n}) instead of wrapping it in quotes.
If you want fewer digits than the policy gives you, round explicitly with
round() — that is a value operation, not an output
setting.