Osy#betaRun it on Osyrin: osyrin.com

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

They never mix, and that is on purpose#

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.

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#

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). 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##

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 and double alike — on the server, in the browser and inside a query.

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#

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#

Related

Crossing the network

An Osy# app runs in two places — the server, which holds the data, and the browser, which holds the screen — and the…

bigdecimal

Exact base-10 arithmetic with 38 significant digits — for warehouse columns and anything else too wide for `decimal`…