# decimal

> Exact base-10 arithmetic, for money and anything else where a fraction of a cent matters. It behaves identically whether your code runs on the server or in the browser.

<!-- id: types-decimal · area: types · stability: stable · html: https://osysharp.com/reference/types/decimal/ -->

## Summary        {#summary}

Exact base-10 arithmetic, for money and anything else where a fraction of a cent matters. `decimal` is not a binary
float: `0.1 + 0.2` is exactly `0.3`, and it behaves identically whether your code runs on the server or in the browser.

## Signature      {#signature}

```osy syntax
decimal Total = 19.99m;      // the `m` suffix makes a literal a decimal
```

## Description    {#description}

Use `decimal` for money, quantities, tax rates, percentages — anything where the answer has to be *the* answer rather
than a very close one. Use `double` for measurements and scientific values, where the extra range matters more than the
last digit.

The difference is not cosmetic. A binary float cannot represent `0.1` exactly, so `0.1 + 0.2` lands a hair above `0.3`
and `1.005 * 100` lands a hair *below* `100.50` — which then rounds to `100.49`. A `decimal` stores the digits you
wrote, so it gives the answer you would give.

```osy title="why a double gets money wrong" syntax
decimal price = 1.005m;
decimal total = price * 100m;      // exactly 100.500 — a double would say 100.49999999999999
```

### They never mix, and that is on purpose   {#no-mixing}

There is **no conversion between `decimal` and `double` in either direction**, so no operator over the pair has a
type — exactly as in C#. Every other numeric combination widens (`int` → `long` → `decimal`, and `int` → `long` →
`double`); only the rung between these two is missing, because crossing it would have to throw away either the exact
digits or the range, and only you know which.

The one that catches people is the literal: a bare `1.5` is a **`double`**, so `total * 1.5` on a `decimal` is a
mix and is refused. Write the literal in the type you mean, or convert explicitly.

```osy title="say which type you mean" syntax
decimal total = 100.50m;
decimal a = total * 1.5m;            // ✓ decimal × decimal
decimal b = total * 1.5;             // ✗ refused — a bare 1.5 is a double
decimal c = total * (decimal)ratio;  // ✓ explicit, and you can see where the conversion happens
double  d = (double)total * ratio;   // ✓ the other direction, equally explicit
```

Comparisons follow the same rule: `total == ratio` and `total > ratio` are refused too, for the same reason — one
side would have to be converted to answer, and neither conversion is lossless.

### The same answer everywhere   {#same-everywhere}

An Osy# expression means one thing. A `decimal` is exact **wherever the function runs** — server-side, or in-process in
the browser (see [Crossing the network](https://osysharp.com/reference/function/execution-side/)). A comparison that is true on one side is true on the other; a total that
prints `"1.10"` on one side prints `"1.10"` on the other. You do not need to know, or care, where a piece of code
executes in order to trust its arithmetic.

### Rounding goes *half to even*, as in C#   {#rounding}

`Math.Round` breaks a tie **to the even neighbour**, which is C#'s own default: `2.5` rounds to `2`, `3.5` to `4` and
`-2.5` to `-2`. That is the rule for every numeric type here — `decimal`, [`bigdecimal`](https://osysharp.com/reference/types/bigdecimal/) and
`double` alike — on the server, in the browser and inside a query.

```osy title="Math.Round goes HALF TO EVEN, as C# does" syntax
Math.Round(2.5m)    // 2
Math.Round(3.5m)    // 4
Math.Round(-2.5m)   // -2
Math.Round(1.005m, 2)   // 1
Math.Round(1.005m, 2, MidpointRounding.AwayFromZero)   // 1.01
```

⚠ **Money often wants the other rule.** Most invoices and tax rules round a half UP (away from zero), so a price,
a VAT amount or a converted total says so: `Math.Round(amount, 2, MidpointRounding.AwayFromZero)`. Every mode is
listed at [[function-math#round]].

Round explicitly when you present a value. Rounding *as you go* accumulates error just as surely as a float would.

### Trailing zeros are part of the value   {#trailing-zeros}

`1.10m` keeps its two decimal places and prints as `"1.10"` — a currency total keeps its cents column. But equality is
numeric, so `1.1m == 1.10m` is **true**. Arithmetic follows the same rules you would use on paper: addition takes the
wider of the two scales, and multiplication adds them.

```osy title="trailing zeros survive, but equality is numeric" syntax
1.10m + 2.20m       // 3.30
1.5m * 1.5m         // 2.25
1.1m == 1.10m       // true
```

## Examples       {#examples}

```osy title="a line total is exact" test app=decimal-basics
class Line {
  decimal Price;
  int Quantity;

  decimal Total() {
    return Price * Quantity;
  }
}
```

```osy title="round once, at the end" test app=decimal-basics
decimal WithTax(decimal subtotal, decimal rate) {
  return Math.Round(subtotal * (1m + rate), 2);
}
```

## See also       {#see-also}
- [Crossing the network](https://osysharp.com/reference/function/execution-side/) — where a function runs; a decimal is exact on either side
