# format specifiers

> Formats a number to a string with a .NET format specifier — F2 for two decimal places, N0 for a grouped whole number, C for currency, P for a percentage. Formatting is invariant, so the same value renders identically in the browser and on the server.

<!-- id: function-format-specifiers · area: function · stability: stable · html: https://osysharp.com/reference/function/format-specifiers/ -->

## Summary        {#summary}
A number becomes a string with a **format specifier**: `$"{total:F2}"` renders `1.5` as `"1.50"`. The same specifier
works through `total.ToString("F2")` and `Convert.ToString(total, "F2")` — they are three spellings of one operation.

## Signature      {#signature}
```osy syntax
$"{<value>:<format>}"
<value>.ToString(<format>)
Convert.ToString(<value>, <format>)
```

## Description    {#description}

### The specifiers   {#specifiers}

A specifier is a letter plus an optional precision, e.g. `F2`, `N0`, `P1`. Precision defaults to 2 where it applies.

| Specifier | `1234.5678` renders as | What it is |
|---|---|---|
| `F2` | `1234.57` | **fixed-point** — the everyday one, and what money wants |
| `N2` | `1,234.57` | **number** — same as `F`, plus group separators |
| `C` | `¤1,234.57` | **currency** |
| `P2` | `123,456.78 %` | **percent** — multiplies by 100 |
| `E` | `1.234568E+003` | **scientific** |
| `G` | `1234.5678` | **general** — the plain digits |
| `D5` | *(integers only)* `00042` | **decimal digits**, zero-padded |
| `X` | *(integers only)* `2A` | **hexadecimal** |

Three of these behave in ways worth knowing before you rely on them:

- **Currency uses `¤`, the generic currency sign — not `$`.** Formatting is invariant (see below), and the invariant
  culture has no country, so it has no currency symbol either. If you want `$` or `€`, write it: `$"${total:N2}"`.
- **A negative currency is wrapped in parentheses:** `-1.5` renders as `(¤1.50)`, not `-¤1.50`.
- **`D` and `X` are integer-only.** Applied to a decimal they raise an error, even for a whole number like `0`.

### Rounding   {#rounding}

Formatting rounds **away from zero**: `1.005` to two places is `1.01`, and `-1.005` is `-1.01`. Because a `decimal`
holds exact base-10 digits, that is the digit you actually wrote — not the nearest binary approximation of it. A
value that rounds to zero prints as `0`, never `-0`.

### It renders the same everywhere   {#invariant}

Formatting is **invariant**: it does not consult the machine's locale. A price is the same string in the browser, on
the server, in a log line, and in a test — and it does not change when a user in another country opens the page.

That is also why formatting runs **in the browser**, with no round trip: rendering a grid of prices is local work.

### Custom patterns   {#custom-patterns}

A custom pattern like `"#,##0.00"` or `"0.00"` also works, and does exactly what it does in C#. Note that a custom
pattern is formatted on the server, so a UI action using one costs a network round trip where a standard specifier
would not — prefer `N2` over `#,##0.00` when they give the same answer.

## Examples       {#examples}
```osy title="a price in a grid" test app=text-search
string PriceLabel(decimal amount) {
  return $"{amount:N2}";
}
// PriceLabel(1234.5m)  ->  "1,234.50"
```

```osy title="a percentage and a padded reference" test app=text-search
string Summary(decimal rate, int reference) {
  return $"{rate:P1} · ref {reference:D6}";
}
// Summary(0.0825m, 42)  ->  "8.3 % · ref 000042"
```

## See also       {#see-also}
- [String interpolation & format specifiers](https://osysharp.com/reference/function/string-interpolation/) — the `$"…"` string itself
- [decimal](https://osysharp.com/reference/types/decimal/) — why exact base-10 digits are what makes `F2` trustworthy
- [execution side](https://osysharp.com/reference/function/execution-side/) — why this runs in the browser
