# The standard operator table

Operators on standard types work by one fixed table rather than a
library function per job, so `price * 20%` and `distance / 2h` read as
they would on paper. The compiler knows each row and checks an operator
as strictly as a call. Anything not in the table is a compile error
naming the two types.

The table extends the rule for time in
[Arithmetic and comparison](arithmetic-and-comparison.md),
where `Instant - Instant` gives a `Duration`. The rules for numbers and
for `+` on text in
[Expressions and operators](expressions-and-operators.md) still
hold.

| Left | Operator | Right | Gives | Notes |
|---|---|---|---|---|
| `Instant` | `+`, `-` | `Duration` | `Instant` | [Time](arithmetic-and-comparison.md) |
| `Instant` | `-` | `Instant` | `Duration` | [Time](arithmetic-and-comparison.md) |
| `Duration` | `+`, `-` | `Duration` | `Duration` | [Time](arithmetic-and-comparison.md) |
| `CalendarDate` | `+`, `-` | `CalendarPeriod` | `CalendarDate` | Years, then months, then days; a day past the month's end clamps to its last day |
| `CalendarPeriod` | `+`, `-` | `CalendarPeriod` | `CalendarPeriod` | Part by part (`P1M + P10D` is `P1M10D`) |
| `CalendarPeriod` | `*` | `Int` | `CalendarPeriod` | Each part multiplied |
| A quantity | `+`, `-`, `*`, `/` | as tabled | as tabled | "Physical quantities" below |
| `Money` | `+`, `-`, `<` ... | `Money` | `Money`, `Bool` | One currency (below) |
| `Money` | `*` | a number or `Percentage` | `Money` | Rounded by the type's `rounding:` |
| `Money` | `/` | `Decimal` | `Money` | A rate (`/ 1.20` takes VAT off), rounded by the type's `rounding:` |
| `Money` | `/` | `Money` | `Decimal` | One currency; as `/` gives for `Decimal` ([Logic](expressions-and-operators.md)) |
| `Money` | `/` | `Int` | refused | `type.money_split`: use `money.split` or `money.allocate` |
| `Money` | `+`, `-` | `Percentage` | refused | Ambiguous: write `price * 120%` or `price + price * 20%` |
| `Percentage` | `+`, `-` | `Percentage` | `Percentage` | `20% + 5%` is `25%` |
| `Percentage` | `*` | a number or `Percentage` | `Percentage` | `50% * 20%` is `10%` |
| A number or quantity | `*` | `Percentage` | the same kind | `distance * 10%` |

## Money

- **Money in one currency.** Where both types fix the currency
  (`type CakePrice = Money { currency: Currency.gbp }`), adding two
  currencies is a compile error. Where a currency is known only while the
  program runs, as in a SaaS app billing each customer in their own, `+`,
  `-` and comparisons can fail with the condition `currency_mismatch`. You
  map it as any construction rule is mapped:
  `attempt subtotal + line.amount { currency_mismatch: InvoiceCurrencyMismatch }`.
  Inside `if (money.currency(a) == money.currency(b))` the checker has
  the proof and the operator is plain, as a range proves a construction
  ([Conditions as proof](creating-values.md#conditions-as-proof)).
- **Two amounts follow the named-type rule** in
  [Expressions and operators](expressions-and-operators.md):
  `+`, `-` and `/` take two amounts of one named type, or one and a plain
  amount that an operator or a `money` operation made. A currency that
  both sides fix differently is reported first, as
  `type.arithmetic_operand` or `type.ordering_operand`.
- **What the checker knows of a result's currency.** Operators give a
  plain `Money`, and the checker carries what it knows of its currency,
  as it carries a number's range: a type's `currency:`, a currency
  written out in `money.from_minor`, `of` or `sum`, one currency through
  `+`, `-`, `*` and `/`, and a condition that compares currencies.
- **`==` and `!=` never fail.** Two amounts in different currencies are
  unequal, as the contract's `==` says. Only `<`, `<=`, `>` and `>=` need
  one currency.
- **An `attempt` around a chain of operators covers every operator in
  it,** as in `attempt a + b + c { currency_mismatch: ... }`, but not an
  operator inside a call. In an `attempt`, a block straight after a
  local's value (`line.amount { ... }`) is the attempt's mappings, not a
  value built from a block, since only a type builds one. `attempt` still
  starts a statement, so a comparison under `attempt` is written as a
  `var`.
- **The money is written first:** `price * 2`, never `2 * price`, which is
  `type.arithmetic_operand`, whose next step writes it the other way.
  `%`, `-price` and `price * price` are refused. `Money / Money` takes a
  divisor as `/` does, a type whose `min` is at least 1, and gives `/`'s
  `Decimal`.
- **Rounding is the type's.** A `Money` type may declare `rounding:`
  (`type CakePrice = Money { currency: Currency.gbp  rounding: Rounding.half_up }`).
  `*` and `/` round to a whole minor unit by the left amount's type's
  `rounding:`, or by `half_even` where it declares none, as constructing
  into a type with a fixed `scale` rounds
  ([Values and types](values-and-types.md)). So multiplying needs no
  call, and there is no `money.multiply` or `money.divide`.
- **`/` by an `Int` is refused,** with a fix to `money.split` or
  `money.allocate`. Dividing money by a count is almost always sharing it
  out, and rounding division loses a minor unit: `£10.00 / 3` is `£3.33`,
  and three of those make `£9.99`. `split` hands every penny to someone.
  `/` by a `Decimal` is allowed, because that is a rate, not a share:
  `gross / 1.20` takes VAT off. Halving a price is `price * 50%`.

## Percentages

- **`Percentage` is a type built into the language,** held as the exact
  fraction it is (`20%` is 0.20). Like `Money`, it is a representation: a
  signature names a type built on it
  (`type VatRate = Percentage { min: 0%  max: 100% }`), whose `min:` and
  `max:` are written with `%`, while a `var` may be a plain `Percentage`.
  Percentages are ordered.
- **The `%` literal.** `20%` is a `Percentage`: digits and `%` with
  nothing between. Its fraction keeps the places written and two more
  (`12.5%` is 0.125). It may be negative (`-5%`), as a quantity literal
  may.
- **`%` as remainder** ([Expressions and operators](expressions-and-operators.md))
  is always written with spaces, as the formatter writes every binary
  operator, so the two never meet in formatted code. A number, `%` and a
  name or number all touching (`20%3`, `20%rate`) is
  `syntax.percent_ambiguous`, naming both readings. It offers no edit,
  since either could be meant, and the text is read as the remainder so
  the file still parses.
- **A bare number where a percentage goes is refused,** as an argument,
  in a comparison, beside `+` or as a bound, since confusing 0.2 and 20
  is what the type exists to end. The error is `type.mismatch` (or
  `type.incomparable`, `type.ordering_operand`, `type.arithmetic_operand`
  or `type.setting_value`, depending on where the number stands). Its next
  step names both readings, `25%` if `0.25` is a ratio, or `0.25%`, with
  no edit.
- **A ratio becomes a `Percentage` by construction.** Dividing two
  quantities gives a plain `Decimal` (`lip / total`), and
  `Percentage(ratio)` makes it a percentage for display: `0.25` gives
  `25%`. Every value becomes a named type this way, so there is no
  `units` operation for it.
- **`Percentage(ratio)` takes a `Decimal`,** a value or a decimal
  literal, into `Percentage` or any type built on it, checking that
  type's rules. A whole number (`VatRate(20)`) or an `Int` value is
  `type.constructor_input`, naming both readings. A named percentage
  never becomes the plain `Percentage`, as for any representation
  ([Values and types](values-and-types.md)).
- **A percentage scales what it follows.** "The same kind" in the table,
  for a number, is a number: `Int * Percentage` gives a `Decimal`, as `*`
  by a `Decimal` does, rather than rounding to a whole number. A
  percentage is written after the amount, number or quantity it scales,
  as a number is (`price * 20%`). `20% * price` and `15% * length` are
  refused, with the fix written the other way, while `20% * 3` is a
  percentage, as the table's own row says. A quantity times a percentage
  rounds half to even to its kind's scale, as it does when multiplied by
  a number. `+` and `-` of two percentages follow the named-type rule.
- **No other row.** Nothing divides a percentage (`rate / 2` is refused;
  `rate * 50%` halves it), adds one to a number or a quantity, or negates
  a percentage value (`-rate`). A quantity and a percentage are reported
  as different kinds. `Money + Percentage` is `type.arithmetic_operand`,
  whose next step writes both readings with the percentage worked out:
  `price * 120%` or `price + price * 20%` (`price * 80%` or
  `price - price * 20%` for `-`), and for a percentage value,
  `price * (100% + rate)`.
- **A percentage's text and wire.** The canonical text is the fraction
  as a percentage with no trailing zeros: `20%`, `12.5%`. `text.parse`
  reads a number, one optional space and `%`, since French and German
  write `20 %`; anything else is `malformed`. On the wire it is that
  text, `"20%"`, rather than the fraction a `Decimal` would send, so no
  client reads 0.2 as 0.2%. A JSON number is the problem `type`, text
  without `%` is `format`, and OpenAPI shows a string with its pattern.
- **A percentage in storage** is one `numeric` column holding the
  fraction, 0.15 for 15%, at whatever places the value has.
- **A percentage for people.** `text.format` is ICU4X's percent formatter
  over CLDR's pattern: the locale's digits and separators, every place the
  value keeps as a percentage, and the `%` where the locale puts it, as
  in `12.5%`, or `20 %` in `de_de` with a no-break space. It takes no
  options. `Display` writes `20%`.
- **A percentage in a message.** In a message's wording,
  `{rate, number, percent}` is the style for a field built on
  `Percentage`, as `{price, number, currency}` is money's, and shows it as
  `text.format` does. `{rate}` alone for a percentage, or either style on
  a field of another kind, is `translation.placeholder_style`, whose edit
  writes the right one. Any other style is `translation.wording_invalid`.

## Ratios and fractions

`Ratio` and `Fraction` are standard objects. A `Ratio`'s `numerator` and
`denominator` are `Decimal`s of any value, as an exchange rate's are. A
`Fraction`'s are whole numbers, its `denominator` at least 1.

Each is held as written, so `16:9` and `32:18`, or `1/2` and `2/4`, are
two different values, as any object's fields compare. Neither is
ordered: no operation reduces them or compares them by value.

Their canonical texts are `16:9` (each number with the places it was
given) and `3/4`, with no spaces, and they are read back exactly so; a
denominator below 1 is `malformed`. Each is sent as that text, shown as it
whatever the locale, and stored in a column per part: `numeric` for a
`Ratio` and `bigint` for a `Fraction`. `type.standard_exists` matches an
object of a `numerator` and a `denominator`, by what they are built on.

## No user-defined operators

Your app's types can't declare operators, so `a + b` always means a row
of this table or of
[Expressions and operators](expressions-and-operators.md). A
reviewer never has to find an overload to know what an operator does,
whether it can fail or what it costs.

## VAT, offcuts and pass rates

A bakery adds VAT to its cakes, trims its shelves and reports how much of
a batch passed, each with a percentage:

```jadpo
type CakePrice = Money {
    currency: Currency.gbp
}

type VatRate = Percentage {
    min: 0%
    max: 100%
}

type PassRate = Percentage {}

type Offcut = Length {}

type Share = Decimal {
    scale: 4
}

/// The VAT on a cake's price at `rate`, rounded half to even to a penny.
function vat_on(price: CakePrice, rate: VatRate) -> CakePrice {
    return CakePrice(price * rate)
}

/// A cake's price with VAT added: the whole price and the rate on top.
function with_vat(price: CakePrice, rate: VatRate) -> CakePrice {
    return CakePrice(price * (100% + rate))
}

/// What is trimmed off a shelf cut from a 2.5262 m board: 15% of it.
function offcut() -> Offcut {
    var total: Length = 2metre + 45cm + 3inch
    var trim: Length = total * 15%
    return Offcut(trim)
}

/// The offcut for the customer, in centimetres.
function offcut_label(customer: Locale) -> PresentationText {
    return text.format(offcut(), locale: customer, unit: LengthUnit.centimetre)
}

/// How much of a batch passed, from the share that did.
function pass_rate(good_share: Share) -> PassRate {
    return PassRate(good_share)
}

/// A rate for the customer, as their locale writes a percentage.
function rate_label(rate: PassRate, customer: Locale) -> PresentationText {
    return text.format(rate, locale: customer)
}

/// Each of four people's share of a cake.
function cake_share() -> Fraction {
    return Fraction {
        numerator: 1
        denominator: 4
    }
}
```

```jadpo
test "a percentage scales money, numbers and quantities" {
    var cake: CakePrice = CakePrice(money.from_minor(1250, Currency.gbp))
    assert vat_on(cake, 20%) == money.from_minor(250, Currency.gbp)
    assert with_vat(cake, 20%) == money.from_minor(1500, Currency.gbp)
    var penny_cake: CakePrice = CakePrice(money.from_minor(100, Currency.gbp))
    assert vat_on(penny_cake, 12.5%) == money.from_minor(12, Currency.gbp)
    assert 50% * 20% == 10%
    assert 20% + 5% == 25%
}

test "the offcut is exact in metres and shown in centimetres" {
    assert offcut() == 0.37893metre
    assert offcut_label(Locale.en_gb) == "37.893 cm"
    assert offcut_label(Locale.de_de) == "37,893 cm"
}

test "a ratio becomes a percentage, and fractions are written as people write them" {
    assert pass_rate(0.25) == 25%
    assert rate_label(pass_rate(0.175), Locale.en_gb) == "17.5%"
    assert text.from(pass_rate(0.175)) == "17.5%"
    assert text.from(cake_share()) == "1/4"
    assert cake_share().denominator == 4
}
```

A voucher keeps its discount in one `numeric` column, as the fraction it
is. Here `Voucher` is an [entity](entities-and-the-id.md), a kind
of stored record:

```jadpo
use bakery/types { DiscountRate }

/// A voucher a customer brings to the counter.
entity Voucher {
    id: Uuid
    reduction: DiscountRate  // one `numeric` column: 0.15 for 15%
}
```

```jadpo
use bakery/entities { Voucher }

test "a voucher keeps its discount as it was issued" {
    var voucher_made: Voucher = issue_voucher(15%)
    var voucher_on_file: Voucher = attempt Voucher.get(voucher_made.id)
    assert voucher_on_file.reduction == 15%
    assert text.from(voucher_on_file.reduction) == "15%"
}
```
