---
title: "Numbers, units and precision"
description: "The canonical rules for rendering a health number, covering digits, units, zero and rounding. Rounding is a truth claim here, not a formatting preference."
url: "https://opsinjs.pensievelabs.org/health/numbers-units-precision"
source: "https://opsinjs.pensievelabs.org/health/numbers-units-precision.md"
section: "Health"
kind: "health"
evidence: "mixed"
reviewed: "2026-09-12"
reviewer: "design"
aliases: ["rounding", "significant figures", "decimals", "decimal places", "number formatting"]
implements: ["value", "result-card", "metric-tile", "range-bar", "reading-input", "score-dial", "log-sheet", "relative-time", "trend-sparkline", "goal-ring", "scale-input"]
---

> Elements written as `<PascalCase … />` below are opsinjs documentation
> components. Their attributes are the content: the values they render are
> generated from `tokens/*.json` and `registry/catalogue.ts` and are
> published separately at https://opsinjs.pensievelabs.org/r/index.json and under the Reference
> section.
> Nothing is missing from this page. The data simply does not live in
> the prose.

<PageTemplate kind="health" />

## What this means [#what-this-means]

This is the canonical page for how a number is rendered in opsinjs. Where any
other page in this documentation discusses number formatting, it defers here.

Each number a health interface displays asserts an accuracy, and the asserted
accuracy is set by how many digits are printed. `71 kg` asserts a weight known to
about a kilogram, `71.4 kg` asserts one known to a hundred grams, and
`71.4396 kg` asserts one known to a tenth of a gram, which is a laboratory
measurement rather than a domestic one. At most one of the three is true of any
given scale, and which one is a property of the instrument rather than of the
rendering code. The third is what an unformatted float produces, because printing
a float without a rule emits every digit the stored value happens to carry, and
that is an accuracy claim the measurement chain behind the value does not support.

The unit carries the other half of the claim. A single measurement takes
different values in different unit systems, and in health the plausible ranges of
those values overlap, so a missing or wrong unit renders as a different and
believable reading rather than as an error. The reader is given no signal that a
substitution has happened, and the component that rendered the number had none
either unless the unit travelled with the value.

## The rule [#the-rule]

**Every displayed health value is formatted by an explicit rule that states its
precision, its unit and its locale. There is no default float rendering
anywhere.**

Seventeen requirements follow, grouped below by what they govern. They are
numbered continuously across the four groups because the evidence section cites
them by number.

### Precision [#precision]

| # | Requirement                                                                                                                                                                         | Where it applies                                                                                                                                                      | Failure it prevents                                                                                        |
| - | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
| 1 | Never display more precision than the source carries. Precision travels with the measurement from its source and is not chosen at render time.                                      | Every rendering of a measured value, including a column whose figures would align more neatly at a deeper precision.                                                  | An accuracy claim the instrument that produced the value cannot support.                                   |
| 2 | Precision is a property of the metric rather than of the value. The same metric is shown to the same number of decimal places every time.                                           | Every surface in the product on which a given metric appears.                                                                                                         | A reader re-parsing the format before they can compare two readings of the same metric.                    |
| 3 | Round half away from zero, once, at the point of display. An already-rounded value is never rounded a second time, and no value is rounded before a comparison against a threshold. | The display boundary, after every calculation and every comparison the product performs.                                                                              | Compounded rounding drift, and a comparison decided by the display rule rather than by the recorded value. |
| 4 | Derived and estimated values are rounded harder than measured ones, and are labelled as estimates.                                                                                  | Any value the product computed or inferred rather than read from an instrument. See [Data provenance and device accuracy](./data-provenance-and-device-accuracy.mdx). | An estimate carrying the precision, and with it the authority, of a measurement.                           |
| 5 | Never pad with false zeros: a source value of `5` is shown as `5` or as the metric's fixed `5.0`, and never as `5.00`.                                                              | Tables and columns in which a neighbouring metric carries two decimal places.                                                                                         | Precision borrowed from the metric rendered next to it.                                                    |

### Units [#units]

| # | Requirement                                                                                                                                                      | Where it applies                                                                                          | Failure it prevents                                                                                                   |
| - | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
| 6 | A value is never displayed without its unit.                                                                                                                     | A tile, a chart tooltip, a table header whose number sits in the cell underneath, and an accessible name. | A number read against whichever unit system the reader assumed, with nothing on screen to correct the assumption.     |
| 7 | The unit is part of the value, visually and semantically. It does not wrap away from its number and it is not styled as a footnote.                              | Type treatment and line breaking wherever a value is laid out.                                            | A unit separated from its number by a line break, or demoted until a reader scanning the screen does not register it. |
| 8 | The unit is spoken as words rather than letters. `mmHg` is announced as "millimetres of mercury" and `mmol/L` as "millimoles per litre".                         | The accessible name of every value a screen reader can reach.                                             | A screen reader reading "one twenty over eighty em em aitch gee".                                                     |
| 9 | Never convert silently: a converted value states that it has been converted, and the unit system is the reader's choice. See [Unit systems](./unit-systems.mdx). | Any surface showing a value in a system other than the one it was recorded in.                            | A converted value compared against a recorded one without the reader knowing the two were produced differently.       |

### Composition and locale [#composition-and-locale]

| #  | Requirement                                                                                                                                                                                                                                                                                                    | Where it applies                                                                | Failure it prevents                                                                                                                                                                                   |
| -- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 10 | Use the reader's locale for the decimal separator and for grouping, and never hand-roll either.                                                                                                                                                                                                                | Every numeric string the product formats, at the point of formatting.           | A decimal separator read as a grouping separator, which shifts the value by a factor of ten or more; the worked case is in "Why (evidence)" below.                                                    |
| 11 | Non-decimal composite values keep their conventional form. Blood pressure is `118/76` rather than two fields, and a height in feet and inches is one value.                                                                                                                                                    | Any measurement whose conventional written form is not a single decimal number. | A reader reassembling into one reading what the interface has split into parts.                                                                                                                       |
| 12 | A two-ended span is joined by the word "to" and carries its unit once, after the second number: `3.9 to 5.6 mmol/L`. A one-sided span is written "up to 5.6 mmol/L" or "5.6 mmol/L and upwards". No dash of any kind joins two numbers.                                                                        | Any two-ended span a component renders, including one the product supplies.     | Two ends read as two separate values; a unit read as applying only to the number it sits beside; and a character between the two ends that speech synthesis either skips or announces as punctuation. |
| 13 | Zero, none and unknown are three different things. `0 steps` is a measurement, "No readings yet" is an absence, and "Not available" is a failure. An absence renders as words and nothing else: no glyph precedes the words, no glyph stands in for them, and the slot is never left blank or filled with `0`. | Every empty, missing and error state on a surface that displays a value.        | An absence read as a measurement of zero, a failure read as an absence, or a slot a reader cannot tell apart from a component that failed to render.                                                  |
| 14 | Negative and out-of-scale values are shown rather than clamped.                                                                                                                                                                                                                                                | Gauges, dials, bars and any scale with a fixed end.                             | A value far beyond the end of a scale rendered in the same position as one that merely reaches it.                                                                                                    |
| 15 | Numerals are always digits rather than words. The interface writes "You have 3 readings" and not "You have three readings".                                                                                                                                                                                    | Running interface prose as well as value slots.                                 | A count missed by a reader scanning the screen, and by a reader with low numeracy who is looking for the digit.                                                                                       |

### Typography [#typography]

| #  | Requirement                                                                      | Where it applies                                                                | Failure it prevents                                                                                             |
| -- | -------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
| 16 | Use tabular figures for any number that appears in a column or updates in place. | Lists, tables, and any value that re-renders while the reader is looking at it. | Digits changing width between renders, which shifts the value sideways and leaves two readings hard to align.   |
| 17 | The value is the largest thing in its container.                                 | Every tile, card and result surface that has a single subject.                  | A reader locating the value by searching rather than at a glance, and reading a neighbouring figure on the way. |

## Why (evidence) [#why-evidence]

<ResearchNote evidence="mixed" date="2026-09-02">
  **Cited.** The systematic review of laboratory-result presentation formats in
  JMIR, 2024, [10.2196/53993](https://doi.org/10.2196/53993), supports the general
  claim that presentation format changes what a lay reader takes from a result. We
  cite it for that general claim and not for any specific rule on this page.

  **Opinion, but close to arithmetic.** Rules 1 to 5 restate significant-figure
  discipline, which is a convention of measurement science rather than a design
  opinion. The design decision is only *where* it is enforced: we insist on the
  component boundary, because a metric whose precision is settled by each caller
  is rendered to a different number of decimal places on each screen that displays
  it, and no single caller is in a position to notice.

  Rule 10 concerns the decimal separator and the grouping mark, and its failure is
  a numeric error rather than a presentational one. A decimal comma rendered into
  a locale that reads it as a thousands separator turns `1,5` into `15`. For a
  medication dose that is a tenfold error. We have not seen this quantified in a
  consumer app and we cite nothing; we regard the mechanism as sufficient.

  Rule 15 departs from the convention in general style guides that small numbers
  are spelled out in prose. We hold that a health interface is scanned rather than
  read, and that a reader with low numeracy is looking for the digit rather than
  for the word.

  What would change our mind on rule 15: a comparison of the digit and word forms
  in health-interface prose, showing that digits cost more in reading
  comprehension than they return in scanning speed, would revise it.
</ResearchNote>

## Applying it [#applying-it]

<DoDont>
  <DoDont.Do>
    Render `118/76 mmHg` as one composite value in its conventional form, with
    the unit given once and the whole announced as "118 over 76 millimetres of
    mercury".
  </DoDont.Do>

  <DoDont.Dont>
    Split the reading into `Systolic: 118.0 · Diastolic: 76.0`. Two fields carry
    a precision neither measurement has, and the reader has to reassemble a
    reading they already know how to read as one value.
  </DoDont.Dont>
</DoDont>

<DoDont>
  <DoDont.Do>
    Show `71.4 kg` from a scale that reports to 100 g, at that precision, every
    time, on every screen that displays the metric.
  </DoDont.Do>

  <DoDont.Dont>
    Show `71.4 kg` on the dashboard, `71 kg` in the summary and `71.44 kg` in the
    export. The reader cannot tell whether they lost weight or the app changed
    its mind.
  </DoDont.Dont>
</DoDont>

<DoDont>
  <DoDont.Do>
    Render an absence as an absence and give it a next step: "No readings yet.
    Log your first one to start."
  </DoDont.Do>

  <DoDont.Dont>
    Render a missing reading as `0 mmol/L`. A nullish default produces a
    physiologically impossible value and presents it as a measurement.
  </DoDont.Dont>
</DoDont>

<DoDont>
  <DoDont.Do>
    Show an out-of-scale value at its real magnitude with the scale annotated as
    exceeded: "218 mmHg. That is above the top of this scale."
  </DoDont.Do>

  <DoDont.Dont>
    Pin the dot to the end of the bar. The reader sees "at the top of the range"
    where the value is far beyond it.
  </DoDont.Dont>
</DoDont>

## Components that implement this [#components-that-implement-this]

{/* Generated from `implements`. Do not restate the list by hand. */}

`Value` is the enforcement point: it is specified to require a unit and a
precision rule rather than accepting a pre-formatted string, so a raw
`toFixed(2)` cannot reach the screen through it. Every other component that
displays a number is specified to render it through `Value` rather than
formatting inline.

## What this does not cover [#what-this-does-not-cover]

* **Which unit system to display in**, and conversion between systems, which is
  [Unit systems](./unit-systems.mdx).
* **Probabilities, risks and percentages**, which have their own failure modes:
  [Risk and statistics](./risk-and-statistics.mdx).
* **Dates and relative times in prose**, which are
  [Numbers, dates and time](../content/numbers-dates-and-time.mdx); that page
  defers to this one for the numbers themselves.
* **Capitalisation, punctuation and abbreviation** in UI strings, which are
  [Grammar and mechanics](../content/grammar-and-mechanics.mdx).
* **Chart axis scaling and tick labelling**, which are
  [Chart anatomy](../foundations/data-visualisation/chart-anatomy.mdx).
* **Whether the number is correct.** This page governs how a value is rendered;
  the accuracy of the value itself belongs to the source that produced it and to
  [Data provenance and device accuracy](./data-provenance-and-device-accuracy.mdx).

## Updates to this page [#updates-to-this-page]

<Reviewed />
