# Physical quantities

A quantity type holds a measured amount, such as a length, a mass, a
temperature or a file's size. Each one (`Length`, `Mass`, `Temperature`
... and `DataSize`, `Bitrate`) holds an exact `Decimal` in its kind's SI
base unit, at a fixed scale, a number of decimal places, for each kind.

You choose a unit only when you make a value or show one. A `Length` is
a `Length` whatever unit built it, so `==` and comparisons work across
units: `1km == 1000metre`. Inside a program a quantity has no unit, so
metres plus centimetres is just `Length + Length`.

A quantity is a type built into the language, as `Money` is. You build a
type on one with `min:` and `max:`, as in `type ShelfLength = Length {
... }`. Like `Int`, a quantity is a representation type: a signature
names a type built on it, while a `var` may be a plain `Length`
([Signatures use named types](signatures-use-named-types.md)).
Every kind is ordered, `Temperature` and `SoundLevel` included.

| Kind | Base unit | Scale (smallest step) |
|---|---|---|
| `Length` | metre | 6 (a micrometre) |
| `Area` | square metre | 6 |
| `Volume` | cubic metre | 9 (a cubic millimetre) |
| `Angle` | radian | 9 |
| `Mass` | kilogram | 9 (a microgram) |
| `Pressure` | pascal | 3 |
| `Energy` | joule | 3 |
| `Power` | watt | 3 |
| `Temperature`, `TemperatureDifference` | kelvin | 3 |
| `Speed` | metre per second | 6 |
| `Frequency` | hertz | 3 |
| `Voltage` | volt | 6 |
| `Current` | ampere | 6 |
| `Illuminance` | lux | 3 |
| `SoundLevel` | decibel | 2 |
| `Concentration` | kilogram per cubic metre | 9 |
| `DataSize` | byte | 0 |
| `Bitrate` | bit per second | 0 |

## Making a quantity

Making a quantity always names a unit. There are four ways:

- `units.of(value, unit)`, from an `Int` or a `Decimal` and a unit of
  the kind: `units.of(5, LengthUnit.kilometre)`.
- A literal with a unit suffix, such as `5km`, `250g` or `2.5kg`
  ([Suffixed literals](physical-quantities.md#suffixed-literals)).
- `text.parse("5 km", as: Length)`, through the contract. It fails
  `unknown_unit` for a unit that isn't the kind's, `malformed` for text
  with no number, or with a rule of the type.
- Another value of the same kind, by construction into a named type
  (`RoomWidth(length)`), which checks the type's rules. No conversion is
  needed, since both are already in metres.

A bare number where a quantity is expected is a compile error,
`type.unit_required`, whose fix asks for the unit (`5km`, or
`units.of(5, LengthUnit.kilometre)`). It can never be declined
([Declining a judgement call](declining-a-judgement-call.md)):
a number without its unit is the mistake the type exists to prevent.

A suffix that names no unit, or misses one only by case (`5mw`, between
`mW` and `MW`), is `type.unit_unknown`, which names each unit it was
near.

## Units

Each kind's units are an enum named for the kind, such as `LengthUnit`,
whose variants are the units' names. A `Length` has `millimetre`,
`centimetre`, `metre`, `kilometre`, `inch`, `foot`, `yard`, `mile` and
`nautical_mile`, and so on for each kind. There is one variant for each
suffix in the table under [Suffixed literals](physical-quantities.md#suffixed-literals), plus
the units that have no suffix: the cubic metre
(`VolumeUnit.cubic_metre`), and a `TemperatureDifference`'s `celsius`,
`fahrenheit` and `kelvin`.

A unit's code is its suffix, which text and JSON use too; the cubic
metre's code is `cubic_metre`. `text.format(value, locale:, unit:)`
shows a quantity in one of these units, or in its kind's base unit when
you leave `unit:` out.

## What the suffixes mean

- `floz` is the US fluid ounce (29.5735295625 ml), since UK labels give
  millilitres.
- A degree of angle is π / 180 radians, to 30 digits.
- `psi` is a pound-force on a square inch, `kcal` the thermochemical
  4,184 J, `t` the tonne and `knot` 1,852 m an hour.

A value is converted exactly where the unit is defined exactly, and
rounded half to even to its kind's scale. A `Mass` is held in kilograms
to 9 places, `numeric(38, 9)`, so `1mcg` is exact, as a medicine's dose
needs. No other kind has a suffix finer than its scale.

## A literal's suffix decides its kind

A duration's suffix (`ms`, `s`, `m`, `h`, `d`) makes a `Duration`, and
any other suffix makes a quantity, matched exactly. A quantity literal
may be negative (`-40degC`); a duration literal may not.

`-` on a quantity value is refused, as it is on any value but a number
([Expressions and operators](expressions-and-operators.md)).
Write the quantity first when you multiply: `bracket * 2`, never
`2 * bracket`, as for money.

## Quantities as text

A quantity's canonical text is the number in the base unit with no
trailing zeros, a space, and the base unit's code: `2.5 kg`,
`1000 metre`.

`text.parse` reads a number, an optional space, and any of the kind's
units by its code, matching case exactly. It fails `malformed` for text
with no number or no unit, and `unknown_unit` for another kind's unit or
a word that is no unit.

## Showing quantities to people

`text.format`'s `unit:` is a unit of the value's kind. The number is the
value in that unit, in the locale's digits and separators. It is exact
where the conversion ends within the places the kind's scale gives in
that unit, and otherwise rounded half to even to one place fewer: `1oz`,
held as 0.028349523 kg, is shown as `1 oz`.

For lengths, areas, volumes and masses, ICU4X has CLDR's unit names, and
the locale's short pattern places the unit (`252,62 cm`). For the other
kinds it writes the number, a space and the unit's SI symbol (`21 °C`,
but `90°`), until ICU4X has their names.

`Display` writes a quantity as code makes one, in its base unit:
`2.5262metre`, or `units.of(0.001, VolumeUnit.cubic_metre)` for a kind
whose base unit has no suffix.

## Suffixed literals

Durations already have suffixes, `500ms`, `30s`, `5m`, `2h` and `30d`
([Durations](durations.md)). `5m` is five
minutes, so you write metres as `5metre`, or with `units.of`. The type a
literal is expected to have never changes what its suffix means, because
a reviewer can't see that type at the literal.

Suffixes are case-sensitive, as SI writes them, and the longest suffix
wins (`dB` before `d`, `mmHg` before `mm`). A near-miss by case (`5mw`,
between milliwatts and megawatts) is an error naming both. No suffix
clashes with a duration's or with another unit's; the last column says
what each kind's suffixes avoid:

| Kind | Suffixes | Clash avoided |
|---|---|---|
| `Length` | `mm`, `cm`, `metre`, `km`, `inch`, `ft`, `yd`, `mi`, `nmi` | `m` is minutes; `in` is a keyword (`for x in`) |
| `Mass` | `mcg`, `mg`, `g`, `kg`, `t`, `oz`, `lb` | `µg` isn't typeable everywhere |
| `Volume` | `ml`, `litre`, `floz`, `ukgal`, `usgal` | `l` reads as `1`; `gal` alone is ambiguous |
| `Area` | `sqmm`, `sqm`, `sqkm`, `ha`, `sqft`, `acre` | `m2` puts a digit in a suffix |
| `Angle` | `deg`, `rad` | `d` is days; longest wins |
| `Temperature` | `degC`, `degF`, `kelvin` | `K` reads as thousands ("5K") |
| `Speed` | `mps`, `kmh`, `mph`, `knot` | `ms` is milliseconds |
| `Pressure` | `Pa`, `hPa`, `kPa`, `bar`, `mbar`, `psi`, `mmHg` | `h` is hours; longest wins (`mmHg` before `mm`) |
| `Energy` | `J`, `kJ`, `Wh`, `kWh`, `kcal` | `cal` refused: food labels mean `kcal` |
| `Power` | `mW`, `W`, `kW`, `MW` | `mW` and `MW` differ only by case |
| `Frequency` | `Hz`, `kHz`, `MHz`, `GHz`, `bpm` | `bps` is bits a second |
| `Voltage`, `Current` | `mV`, `V`, `kV`; `mA`, `A` | |
| `Illuminance`, `SoundLevel` | `lx`; `dB` | `d` is days; longest wins |
| `Concentration` | `mgL`, `gL` | |
| `DataSize` | `B`, `kB`, `MB`, `GB`, `TB`, `KiB`, `MiB`, `GiB`, `TiB` | `b` (bits) refused, so bytes and bits can't be confused |
| `Bitrate` | `bps`, `kbps`, `Mbps`, `Gbps` | |

`TemperatureDifference` has no suffix of its own: you get one by
subtracting two temperatures, or from `units.of`. The clinical units,
`mmHg` and `bpm` among them, are under
[Groups with the baseline only](groups-with-the-baseline-only.md).

A joinery cuts shelves to a customer's length, in whatever unit they give
it, and works out floors and paces from lengths:

```jadpo
type ShelfLength = Length {
    min: 10cm  // a bound written with its unit, held in metres
    max: 3metre
}

type Plank = Length {}

type RoomFloor = Area {}

type WalkingPace = Speed {}

type WalkTime = Duration {
    min: 1s
}

type Miles = Decimal {
    scale: 2
}

type FormText = Text {}

type ShelfUnreadable = Failure {
    kind: FailureKind.invalid_value
}

type ShelfUnitWrong = Failure {
    kind: FailureKind.invalid_value
}

type ShelfOutOfRange = Failure {
    kind: FailureKind.invalid_value
}

/// A shelf with a bracket at each end: lengths add, whatever unit made
/// them.
function with_brackets(shelf: ShelfLength, end_block: Plank) -> Plank {
    return Plank(shelf + end_block * 2)
}

/// A shelf length a customer typed, with its unit: `90 cm`, `3 ft`.
function typed_shelf(
    raw: FormText
) fails ShelfUnreadable, ShelfUnitWrong, ShelfOutOfRange -> ShelfLength {
    var shelf: ShelfLength = attempt text.parse(raw, as: ShelfLength) {
        malformed: ShelfUnreadable  // no number, or no unit
        unknown_unit: ShelfUnitWrong  // `90 kg`
        min: ShelfOutOfRange
        max: ShelfOutOfRange
    }
    return shelf
}

/// A room's floor: a length times a length is an area.
function floor_of(width: Plank, room_depth: Plank) -> RoomFloor {
    return RoomFloor(width * room_depth)
}

/// How fast a walk went: a length over a duration is a speed.
function pace_of(stroll: Plank, stroll_time: WalkTime) -> WalkingPace {
    return WalkingPace(stroll / stroll_time)
}

/// A distance in miles, to two places.
function in_miles(stroll: Plank) -> Miles {
    return Miles(units.in(stroll, LengthUnit.mile, scale: 2, rounding: Rounding.half_even))
}

/// A plank's length for the reader, in centimetres.
function plank_label(plank: Plank, customer: Locale) -> PresentationText {
    return text.format(plank, locale: customer, unit: LengthUnit.centimetre)
}
```

```jadpo
test "lengths add across units and are shown in any" {
    var fitted: Length = 2metre + 45cm + 3inch
    assert fitted == 2.5262metre
    assert 1km == 1000metre
    assert units.of(5, LengthUnit.kilometre) == 5km
    assert plank_label(Plank(fitted), Locale.en_gb) == "252.62 cm"
    assert plank_label(Plank(fitted), Locale.de_de) == "252,62 cm"
    assert text.format(fitted, locale: Locale.en_gb) == "2.5262 m"
    assert text.from(2.5kg) == "2.5 kg"
    assert in_miles(5km) == 3.11
}

test "kinds multiply and divide as the table says" {
    assert with_brackets(90cm, 2cm) == 94cm
    assert floor_of(3metre, 2metre) == 6sqm
    assert pace_of(100metre, 8s) == 12.5mps
    assert 25degC - 20degC == units.of(5, TemperatureDifferenceUnit.kelvin)
    assert units.sum([1km, 500metre]) == 1.5km
}

test "text is read with its kind's units" {
    var shelf: ShelfLength = attempt typed_shelf("90 cm")
    assert shelf == 0.9metre
    var in_feet: ShelfLength = attempt typed_shelf("3 ft")
    assert in_feet == 0.9144metre
    assert typed_shelf("90") fails ShelfUnreadable
    assert typed_shelf("90 kg") fails ShelfUnitWrong
    assert typed_shelf("5 metre") fails ShelfOutOfRange
}
```

## Quantity arithmetic

`+` and `-` work within one kind, and `*` and `/` by a plain number keep
the kind. One kind divided by the same kind gives a plain `Decimal`, as
`/` does for a named type
([Expressions and operators](expressions-and-operators.md)).

Within a kind, `+`, `-` and `/` follow the same named-type rule: the two
sides are one named type, or a plain quantity an operator or a `units`
operation made. A divisor's type must exclude zero; for a duration, its
type's `min:` counts.

These products and quotients are defined, and any other mix is a compile
error naming the two kinds:

| Left | Operator | Right | Gives |
|---|---|---|---|
| `Length` | `*` | `Length` | `Area` |
| `Area` | `*` | `Length` | `Volume` |
| `Area` | `/` | `Length` | `Length` |
| `Volume` | `/` | `Area` | `Length` |
| `Volume` | `/` | `Length` | `Area` |
| `Length` | `/` | `Duration` | `Speed` |
| `Speed` | `*` | `Duration` | `Length` |
| `Length` | `/` | `Speed` | `Duration` |
| `Energy` | `/` | `Duration` | `Power` |
| `Power` | `*` | `Duration` | `Energy` |
| `Voltage` | `*` | `Current` | `Power` |
| `Power` | `/` | `Voltage` | `Current` |
| `Power` | `/` | `Current` | `Voltage` |
| `Mass` | `/` | `Volume` | `Concentration` |
| `Concentration` | `*` | `Volume` | `Mass` |
| `DataSize` | `/` | `Duration` | `Bitrate` |
| `Bitrate` | `*` | `Duration` | `DataSize` |
| `Temperature` | `-` | `Temperature` | `TemperatureDifference` |
| `Temperature` | `+` or `-` | `TemperatureDifference` | `Temperature` |

- **Rounding.** A product is exact, and a quotient is worked out exactly
  where it ends. Either is then rounded half to even to the result
  kind's scale, as constructing into a type with a fixed `scale` rounds
  ([Creating values](creating-values.md)). For
  another rounding, use `units.in` with `rounding:`.
- **`Temperature` doesn't add to itself:** 20 °C plus 20 °C isn't 40 °C.
  It adds only a `TemperatureDifference`. `SoundLevel` is logarithmic, so
  it compares but has no `+` or `-`.
- **`Duration`** is still the type [Durations](durations.md)
  describes. It takes part in the table above without becoming a
  quantity.

## Storing quantities

A quantity is stored in one `numeric` column, in the base unit at the
kind's scale from the table above: `numeric(38, 6)` for a `Length`. The
column's comment names its kind and SI unit as the units table writes
it, `Length in metres (m)`. A migration sets the comment with the column
and changes it with the type. A query compares (`<`, `==`) and orders by
a quantity field as it does by a number.

## Quantities in JSON

A quantity is sent with its unit named: `{ "value": "2.5", "unit": "kg" }`.
A bare number in the base unit wouldn't do, because a client can't tell
it from a number in another unit. The number is decimal text, as every
`Decimal` is sent ([Values and types](values-and-types.md)).

Jadpo writes the base unit and reads any of the kind's units. A JSON
number for `value` fails as `type`, and another kind's unit as
`unknown_unit`. `json.read` and `json.write` use the same shape, and
OpenAPI lists the kind's units as the unit's `enum`.

## `units` operations

| Operation | Gives | Fails | SQL form |
|---|---|---|---|
| `units.of(value, unit)` | The quantity of `unit`'s kind: `value`, an `Int` or `Decimal`, converted exactly to the base unit and rounded half to even to the kind's scale | overflow is a defect | none |
| `units.in(value, unit, scale:, rounding:)` | `Decimal`, the quantity in `unit`, rounded | | none |
| `units.sum(items)` | The total of one kind; zero for none | overflow is a defect | none |

`units.in` needs both `scale:` and `rounding:`, as `decimal.round` does.
`units.sum` refuses a list of temperatures or sound levels
(`type.arithmetic_operand`), which don't add. A list written out takes
its first item's kind.

A shelf on file keeps its length in metres, one column, however it was
cut:

```jadpo
use joinery/types { CutLength }

/// A shelf cut to order.
entity CutShelf {
    id: Uuid
    cut_to: CutLength  // one `numeric(38, 6)` column, in metres
}
```

```jadpo
use joinery/entities { CutShelf }
use joinery/types { CutLength }

test "a shelf keeps its length as it was cut" {
    var shelf_made: CutShelf = cut_to_order(CutLength(2.5262metre))
    var shelf_on_file: CutShelf = attempt CutShelf.get(shelf_made.id)
    assert shelf_on_file.cut_to == 2metre + 45cm + 3inch
    assert text.format(
        shelf_on_file.cut_to,
        locale: Locale.en_gb,
        unit: LengthUnit.centimetre
    ) == "252.62 cm"
    assert text.format(
        shelf_on_file.cut_to,
        locale: Locale.de_de,
        unit: LengthUnit.centimetre
    ) == "252,62 cm"
}
```

`Percentage`, `Ratio`, `Fraction` and the `%` literal are under
[The standard operator table](the-standard-operator-table.md).
