# bigdecimal

> Exact base-10 arithmetic with 38 significant digits — for warehouse columns and anything else too wide for `decimal`. Every rule it follows is `decimal`'s rule; the only difference is how many digits fit. Like `decimal`, it behaves identically on the server and in the browser.

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

## Summary        {#summary}

`bigdecimal` is `decimal` with more room: **38 significant digits** instead of ~28. Everything else — that scale is
part of the value, how arithmetic changes it, how rounding breaks a tie — is *identical*, on purpose. One set of
rules, two widths.

Reach for it when a number genuinely does not fit in a `decimal`: a Snowflake `NUMBER(38,0)` (which is Snowflake's
**default** integer), a Databricks `DECIMAL(38,s)`, a ledger keyed by a very large identifier. For money, keep using
[`decimal`](https://osysharp.com/reference/types/decimal/) — it is narrower, cheaper, and wide enough.

## Signature      {#signature}

```osy syntax
bigdecimal units = 99999999999999999999999999999999999999n;   // 38 nines — the widest value it holds
bigdecimal rate  = 1.10n;                                      // scale 2, and it stays scale 2
```

## Description    {#description}

### The rules, and why each one is what it is   {#rules}

These are the answers to every question the type can be asked. They are `decimal`'s answers, and they are written
out here rather than left to be discovered, because a rounding rule you find out about from a reconciliation report
is a rule that was not documented.

**Scale is part of the value.** `1.10n` has scale 2 and prints `"1.10"` — not `"1.1"`. Scale is data, not
formatting; a total that renders as `1.1` on an invoice is wrong even though the number is right.

**…but equality ignores it.** `1.1n == 1.10n` is **true**. Those two rules sound contradictory and both are C#'s:
arithmetic *preserves* scale, comparison does *not consider* it.

**Arithmetic changes scale by fixed rules**, so a result's precision is predictable rather than a side effect:

| operation | the result's scale |
|---|---|
| `a + b`, `a - b`, `a % b` | the **larger** of the two scales |
| `a * b` | the two scales **added** — `1.5n * 1.5n` is `2.25n`, not `2.3n` |
| `a / b` | computed to 39 decimal **places** (truncating), rounded to 38 places **half-even**, then **trailing zeros removed** |
| `-a`, `Math.Abs(a)` | unchanged — `Math.Abs(-2.50n)` is `2.50n` |
| `Math.Floor`, `Math.Ceiling`, `Math.Truncate` | zero |
| `Math.Round(a, places)` | `places` |

**Division is the one that trims, and only division.** `1n / 2n` is `0.5n` — not `0.5000…` padded to 38 places —
while `1n / 3n` keeps its full 38-digit tail. Addition and multiplication never trim: `1.10n + 2.20n` is `3.30n`.

**`Math.Round` breaks a tie HALF TO EVEN, as C# does.** `Math.Round(2.5n)` is `2` and `Math.Round(-2.5n)` is `-2`.
That is the rule for **every** numeric type, so `Math.Round` means the same thing whichever kind of number you hand
it. To round a half away from zero, say so: `Math.Round(2.5n, MidpointRounding.AwayFromZero)` is `3`.

⚠ **Division rounds differently, and only division.** Its last digit — the 38th — is settled **half to even**,
because that step is not a rounding you asked for but the point at which an endless quotient has to stop. It is the
one place the two rules differ, and it is below anything `Math.Round` can reach.

**Remainder takes the sign of the left operand.** `-7n % 3n` is `-1n`, not `2n`. Same as C#, and as JavaScript.

**`Floor`, `Ceiling` and `Truncate` differ on negatives**, so pick deliberately:

| | `-2.5n` | `2.5n` |
|---|---|---|
| `Math.Floor` — toward −∞ | `-3` | `2` |
| `Math.Ceiling` — toward +∞ | `-2` | `3` |
| `Math.Truncate` — toward zero | `-2` | `2` |

**Too big is an error, never a wrap.** A result past 38 digits raises rather than silently truncating or wrapping.
Too many *decimal places* is different and not fatal: a scale past 38 is rounded (to even) down to 38 first. Only
the magnitude is fatal.

**A value that is not a number is an error, never zero.** Parsing `"abc"` raises. Exponent form (`1.5e3`, `1e-3`)
is accepted, because both the wire and every warehouse use it.

### Choosing between it and `decimal`   {#choosing}

They do not mix implicitly, for the reason `decimal` and `double` do not: a conversion has to lose something, and
only you know which loss is acceptable. Widening is exact and explicit; narrowing can raise.

```osy syntax
decimal    money = 19.99m;
bigdecimal wide  = (bigdecimal)money;      // ✓ exact — every decimal fits
decimal    back  = (decimal)wide;          // ✓ explicit, and raises if it no longer fits
bigdecimal mixed = money * wide;           // ✗ refused — say which type you mean
```

### It is the same number on both sides   {#parity}

Like `decimal`, `bigdecimal` gives identical answers in a server function and in a browser action — same digits,
same scale, same rounding. That is not a promise made in prose: both runtimes are held to **one shared table of
cases** generated by a third implementation, so neither can drift toward the other's bugs.

## Examples       {#examples}

```osy title="a warehouse integer too wide for a decimal" test app=bigdecimal-units
// A Snowflake NUMBER(38,0) — its DEFAULT integer column — which a decimal cannot hold.
bigdecimal Units(bigdecimal a, bigdecimal b) {
  return a + b;
}
```

```osy title="a line total that keeps its scale" test app=bigdecimal-line
// Scale survives arithmetic, which is what makes a rendered total trustworthy.
bigdecimal Line(bigdecimal unitPrice, bigdecimal qty) {
  return unitPrice * qty;        // 1.10n * 3n → 3.30n, and it prints "3.30"
}
```

## See also       {#see-also}
- [decimal](https://osysharp.com/reference/types/decimal/) — narrower, cheaper, and the right choice for money
- [long](https://osysharp.com/reference/types/long/) — an exact 64-bit integer, when 19 digits is enough and you want no scale at all
- [Crossing the network](https://osysharp.com/reference/function/execution-side/) — why a type behaving identically on both sides matters
