Osy#the first language built for agents
Agents firstAgentic appsWorkflowsDurable Execution — built inSecurityTestingThe editorThe UI modelOne program

Reference / Types

decimal

decimal Total = 19.99m;

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.

stable2 examples compiled by CItypesnumbersmoneyauthoring

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#

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

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.

decimal price = 1.005m;
decimal total = price * 100m;      // exactly 100.500 — a double would say 100.49999999999999

The same answer everywhere#

An Osy# expression means one thing. A decimal is exact wherever the function runs — server-side, or in-process in the browser (see 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 is banker's rounding#

Math.Round rounds half to even, not half up. That is deliberate: always rounding .5 upward biases a long column of figures steadily upward, which is exactly what you do not want in a ledger.

Math.Round(2.5m)    // 2   — not 3
Math.Round(3.5m)    // 4
Math.Round(1.005m, 2)   // 1.00

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#

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.

1.10m + 2.20m       // 3.30
1.5m * 1.5m         // 2.25
1.1m == 1.10m       // true

Examples#

class Line {
  decimal Price;
  int Quantity;

  decimal Total() {
    return Price * Quantity;
  }
}
decimal WithTax(decimal subtotal, decimal rate) {
  return Math.Round(subtotal * (1m + rate), 2);
}

See also#

  • execution side — where a function runs; a decimal is exact on either side

Related

execution side

Where a function runs. Osy# infers it from the body: a function that reads data runs on the server, a function that…