CkMoney
Job: read, convert, format, round and split amounts of money the way payment processors handle them — safely, and without ever losing a cent.
Money data arrives in messy shapes: "$1,234.56", "USD 10.99", the accounting
negative "(123.45)", a plain number from a database. CkMoney reads them all into
decimal — the C# type built for money, which stores amounts exactly (unlike double,
which stores close-enough approximations that drift in arithmetic). It also converts to
and from the integer "minor units" Stripe charges in, and formats amounts exactly how
PayPal expects them. When a value cannot be read, the number methods answer your
fallback (0 unless you say otherwise), the string methods answer "", and Split
answers an empty array — nothing throws unless you ask.
Currency codes follow ISO 4217 — the international standard of three-letter codes
like "USD", "EUR", "JPY" that payment systems key on.
Quick look
using SafeCoder.CommonKit;
using SafeCoder.CommonKit.Enums;
CkMoney.Amount("$1,234.56"); // -> 1234.56
CkMoney.MinorUnits("$10.99"); // -> 1099 (the cents Stripe charges)
CkMoney.Format(10); // -> "10.00" (the shape PayPal expects)
CkMoney.Currency("usd"); // -> "USD"
CkMoney.Amount("banana"); // -> 0 (unreadable — the default fallback)
CkMoney.IsRounded("10.999"); // -> false (more decimals than US dollars have)
CkMoney.IsAtLeast("$50.00", "$10.00"); // -> true
CkMoney.SameAmount("$1,234.56", 1234.56m); // -> true
CkMoney.Multiply("$19.99", 1.0825); // -> 21.64m (with 8.25% tax)
CkMoney.Total(new[] { "$1,234.56", "(12.00)" }); // -> 1222.56m
CkMoney.Allocate(100.01m, new[] { 70, 30 }); // -> [70.01, 30] (never loses a cent)
CkMoney.Divide("$100", 3); // -> 33.33m
CkMoney.Subtract("$100.00", "$25.50"); // -> 74.50m
CkMoney.Convert("$100", 0.92m, "USD", "EUR"); // -> 92.00m (at your rate)
CkMoney.Display(1234.5m, "EUR"); // -> "1,234.50 EUR" (for people, not machines)
CkMoney.CurrencyOf("Romania"); // -> "RON"
CkMoney.Decimals("JPY"); // -> 0
CkMoney.NumericCode("EUR"); // -> "978" (what a card network carries)
CkMoney.Currency("978"); // -> "EUR"
CkMoney.Explain("ten"); // -> "letters in the amount"
Methods
Amount
Read an amount from anything.
decimal Amount(
object? input = null,
DecimalMark mark = CkSettings.DefaultDecimalMark,
decimal fallback = 0,
bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
| Parameter | Type | Default | What it does |
|---|---|---|---|
input |
object? |
null |
The value: a number of any type, or money text. |
mark |
DecimalMark |
CkSettings.DefaultDecimalMark (DecimalMark.Dot) |
Which character is the decimal point. Comma declares the European mirror: dots group, the comma decides. |
fallback |
decimal |
0 |
What comes back when the value cannot be read. |
throwIfInvalid |
bool |
CkSettings.DefaultThrowIfInvalid (false) |
Pass true to throw a CkMoneyException that says why, instead of returning the fallback, when the value cannot be read. |
Returns
| When | Default | With throwIfInvalid: true |
|---|---|---|
| The value reads as an amount | The exact amount: 10.99 |
The exact amount: 10.99 |
Rejected: missing, letters in the amount, or grouping that does not match the declared mark |
The fallback (default 0) |
Throws CkMoneyException — carries Input, Expected and Reason |
A 0 answer can also be a real zero amount — set a distinctive fallback when you
need to tell them apart.
Takes a number of any type, or text in the shapes real data comes in: plain "10.99",
grouped "$1,234.56", the accounting negatives "(123.45)" and "123.45-". The
symbols $, €, £, ¥ are ignored, and a known ISO 4217 code at either end
("USD 10.99", "10.99 EUR") is dropped. The amount comes back exactly as read — no
rounding. Text naming two different codes, "USD 10.99 EUR", is rejected.
CkMoney.Amount("USD 10.99"); // -> 10.99
CkMoney.Amount("EUR 1,234.56"); // -> 1234.56
CkMoney.Amount("(123.45)"); // -> -123.45 (accounting negative)
CkMoney.Amount("123.45-"); // -> -123.45 (trailing minus)
By default a dot is the decimal point and commas group digits, as in the US. For
European text ("1.234,56") you declare the mirror with the DecimalMark enum — it is
never guessed, because "1.234" means two different numbers in the two notations:
CkMoney.Amount(
input: "1.234,56",
mark: DecimalMark.Comma
); // -> 1234.56
CkMoney.Amount("1,23"); // -> 0 (comma decimal not declared — rejected, never misread)
A fallback replaces an unreadable value:
CkMoney.Amount(
input: "junk",
fallback: -1m
); // -> -1
AmountOrNull
Null when there is nothing.
decimal? AmountOrNull(
object? input = null,
DecimalMark mark = CkSettings.DefaultDecimalMark,
bool treatZeroAsMissing = true
)
| Parameter | Type | Default | What it does |
|---|---|---|---|
input |
object? |
null |
The value: a number, or money text. |
mark |
DecimalMark |
CkSettings.DefaultDecimalMark (DecimalMark.Dot) |
Which character is the decimal mark in text. |
treatZeroAsMissing |
bool |
true |
Read a zero amount as missing, so it answers null too; pass false to keep a zero as the real amount it sometimes is. |
Returns
| When | You get |
|---|---|
| The value reads as an amount worth keeping | The amount: 1234.50 |
Missing (null, DBNull), unreadable, or a zero with treatZeroAsMissing on |
null — this method never throws |
Reads exactly like Amount, but answers null instead of a fallback when there is
nothing usable: a missing value, unreadable text — or a zero, because in many database
columns 0.00 really means "no amount". That last part is on by default and switched
off with treatZeroAsMissing: false. Built for filling nullable columns honestly.
CkMoney.AmountOrNull("$1,234.50"); // -> 1234.50
CkMoney.AmountOrNull("junk"); // -> null
CkMoney.AmountOrNull("0.00"); // -> null (zero counts as missing by default)
CkMoney.AmountOrNull(
input: "0.00",
treatZeroAsMissing: false
); // -> 0.00 (a real zero, kept on request)
Processors like Stripe avoid decimal points entirely: they charge in minor units,
a currency's smallest coin, as a whole number. US$10.99 is 1099 cents. The next two
methods convert to and from that integer.
How many decimals a currency has comes from the built-in ISO 4217 table — and it is
not always two. The Japanese yen has no smaller coin at all, so ¥1099 is 1099 minor
units; the Bahraini dinar has three decimals, so 3.141 dinars is 3141.
MinorUnits
An amount into integer cents.
long MinorUnits(
object? input = null,
string? currency = "USD",
int? decimals = null,
int? scale = null,
DecimalMark mark = CkSettings.DefaultDecimalMark,
long fallback = 0,
bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
| Parameter | Type | Default | What it does |
|---|---|---|---|
input |
object? |
null |
The amount: a number, or text. |
currency |
string? |
"USD" |
The ISO 4217 code deciding the scale — how many decimals make one minor unit. |
decimals |
int? |
null |
How many decimals the amount may carry, in place of the currency's own — for a receiver that disagrees with ISO 4217, or a unit price kept at four. 0 to CkSettings.MaxMoneyDecimals (8); null for the currency's own. |
scale |
int? |
null |
The power of ten the whole units are written at, when it is not the decimals — a receiver that writes a currency without decimals as if it had two (5 ISK as 500). null for the decimals. |
mark |
DecimalMark |
CkSettings.DefaultDecimalMark (DecimalMark.Dot) |
Which character is the decimal point in text. |
fallback |
long |
0 |
What comes back when a value cannot be used. |
throwIfInvalid |
bool |
CkSettings.DefaultThrowIfInvalid (false) |
Pass true to throw a CkMoneyException that says why, instead of returning the fallback, when a value cannot be used. |
Returns
| When | Default | With throwIfInvalid: true |
|---|---|---|
| The amount and the currency both read | The minor units: 1099 |
The minor units: 1099 |
| Rejected: an unknown currency, an unreadable amount, or one outside the range of minor units | The fallback (default 0) |
Throws CkMoneyException — carries Input, Expected and Reason |
More decimals than the currency has are rounded away from zero before converting.
MinorUnits converts an amount to that integer of minor units.
Most receivers count a currency's decimals the way ISO 4217 does, and the defaults are right.
A few do not, and they say so in their documentation: one writes a currency that has no
decimals as if it had two, so 5 ISK goes out as 500. Say it with scale, and the amount
is still rounded to whole króna first. See Taking a payment for the
cases known today.
CkMoney.MinorUnits(
input: 5,
currency: "ISK",
scale: 2
); // -> 500 (no decimals, written as two)
CkMoney.MinorUnits("$10.99"); // -> 1099 (USD is the default currency)
CkMoney.MinorUnits(
input: "£99.50",
currency: "GBP"
); // -> 9950 (pence)
CkMoney.MinorUnits(
input: 1099,
currency: "JPY"
); // -> 1099 (the yen has no decimals)
CkMoney.MinorUnits("10.005"); // -> 1001 (extra decimals round away from zero first)
FromMinorUnits
Integer cents back into an amount.
decimal FromMinorUnits(
object? input = null,
string? currency = "USD",
int? decimals = null,
int? scale = null,
decimal fallback = 0,
bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
| Parameter | Type | Default | What it does |
|---|---|---|---|
input |
object? |
null |
The minor units: a whole number, or text holding one. |
currency |
string? |
"USD" |
The ISO 4217 code deciding the scale. |
decimals |
int? |
null |
How many decimals the amount may carry, in place of the currency's own — for a receiver that disagrees with ISO 4217, or a unit price kept at four. 0 to CkSettings.MaxMoneyDecimals (8); null for the currency's own. |
scale |
int? |
null |
The power of ten the whole units are written at, when it is not the decimals — a receiver that writes a currency without decimals as if it had two (5 ISK as 500). null for the decimals. |
fallback |
decimal |
0 |
What comes back when a value cannot be used. |
throwIfInvalid |
bool |
CkSettings.DefaultThrowIfInvalid (false) |
Pass true to throw a CkMoneyException that says why, instead of returning the fallback, when a value cannot be used. |
Returns
| When | Default | With throwIfInvalid: true |
|---|---|---|
| The count and the currency both read | The amount: 10.99 |
The amount: 10.99 |
| Rejected: an unknown currency, or a count that is not a whole number | The fallback (default 0) |
Throws CkMoneyException — carries Input, Expected and Reason |
Minor units are whole by definition — a fractional count rejects rather than rounds.
Works like MinorUnits in reverse: it
converts a count of minor units back into a decimal amount. Minor units are whole by
definition, so FromMinorUnits rejects a fractional count like 1099.5 — that is bad
data, and you get the fallback.
With scale, a count written at a finer scale than the currency's decimals reads back:
500 at scale 2 is 5 ISK. A count that would not be whole — 550 — is rejected as bad data.
CkMoney.FromMinorUnits(1099); // -> 10.99
CkMoney.FromMinorUnits(
input: 3141,
currency: "BHD"
); // -> 3.141 (the dinar has three)
Currency
Validate an ISO 4217 code.
string Currency(
string? input = null,
string? fallback = null,
TextCase textCase = TextCase.Uppercase,
bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
| Parameter | Type | Default | What it does |
|---|---|---|---|
input |
string? |
null |
The raw code. |
fallback |
string? |
null |
Backup code used when the value is rejected. It goes through the same cleaning and checking; a fallback that is itself rejected answers "". |
textCase |
TextCase |
TextCase.Uppercase |
The casing the code is written in: TextCase.Lowercase for a receiver that takes "usd"; upper case, the ISO form, for any other value. |
throwIfInvalid |
bool |
CkSettings.DefaultThrowIfInvalid (false) |
Pass true to throw a CkMoneyException that says why, instead of answering "", when neither the value nor the fallback can be used. |
Returns
| When | Default | With throwIfInvalid: true |
|---|---|---|
The value (or the fallback) is an ISO 4217 code the library knows |
The upper-case code: "USD" |
The upper-case code: "USD" |
| Rejected: missing, not three letters or three digits, or not a known code | Empty string | Throws CkMoneyException — carries Input, Expected and Reason |
The table holds the active codes people are paid in — 158 of them, XCG, VED and KPW
since 1.3.0 — plus the two most recently withdrawn, ANG and BGN, which stored records
still carry. A long-retired code (DEM, FRF, ROL) rejects like any other word, and so do
the fund and accounting codes nobody is paid in (BOV, CHE, USN, …).
Cleans a currency code and checks it against the built-in ISO 4217 table.
CkMoney.Currency("usd"); // -> "USD"
CkMoney.Currency("eur"); // -> "EUR"
CkMoney.Currency("USD", textCase: TextCase.Lowercase); // -> "usd"
CkMoney.Currency("yen"); // -> "" (an everyday word, not the ISO code — that's "JPY")
CkMoney.Currency(
input: "xyz",
fallback: "USD"
); // -> "USD"
CurrencyOf
The currency a country uses.
string CurrencyOf(
string? country = null,
string? fallback = null,
bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
| Parameter | Type | Default | What it does |
|---|---|---|---|
country |
string? |
null |
The country: an ISO 3166-1 code, two or three letters, or a name — read the way CkCountry.Alpha2 reads one. |
fallback |
string? |
null |
A currency code that replaces an empty result, once it has been through the same validation Currency gives one. |
throwIfInvalid |
bool |
CkSettings.DefaultThrowIfInvalid (false) |
Pass true to throw a CkMoneyException that says why, instead of returning an empty string, when a value cannot be used. |
Returns
| When | Default | With throwIfInvalid: true |
|---|---|---|
| The country reads and has a currency on record | The upper-case code: "RON" |
The upper-case code: "RON" |
| Rejected: not a country, or a place with no currency on record, and no usable fallback | Empty string | Throws CkMoneyException — carries Input, Expected and Reason |
For defaulting a form or a price list from the customer's country. Where a country has
more than one legal tender the one in everyday use answers: Panama is PAB, El Salvador
USD, Bhutan BTN, Haiti HTG, Lesotho LSL, Namibia NAD, Uruguay UYU, Venezuela
VES — and where ISO 4217 lists none, the one in everyday use: Palestine ILS, South
Georgia GBP. Antarctica has a country code and no currency, so it is rejected.
CkMoney.CurrencyOf("ro"); // -> "RON"
CkMoney.CurrencyOf("Germany"); // -> "EUR"
CkMoney.CurrencyOf("JPN"); // -> "JPY"
CkMoney.CurrencyOf("Atlantis"); // -> ""
CkMoney.CurrencyOf(
country: "Atlantis",
fallback: "usd"
); // -> "USD"
NumericCode
The three digits card networks carry.
string NumericCode(
string? currency = null,
string fallback = "",
bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
| Parameter | Type | Default | What it does |
|---|---|---|---|
currency |
string? |
null |
The ISO 4217 code: three letters in any casing, or three digits. |
fallback |
string |
"" |
What comes back when the code cannot be used, as given. |
throwIfInvalid |
bool |
CkSettings.DefaultThrowIfInvalid (false) |
Pass true to throw a CkMoneyException that says why, instead of returning the fallback, when the code cannot be used. |
Returns
| When | Default | With throwIfInvalid: true |
|---|---|---|
| The code is one the library knows | Its numeric code, three digits: "978" |
Its numeric code, three digits: "978" |
| Rejected: missing, not three letters or three digits, or not a known code | The fallback (default empty) |
Throws CkMoneyException — carries Input, Expected and Reason |
Stripe and PayPal speak letters — "eur", "EUR". A card network, an acquirer's
settlement file or a bank's ISO 8583 message speaks digits: field 49 of an authorisation
and EMV tag 5F2A carry 978 for the euro. NumericCode writes them, as text, because
the zeros count: the Albanian lek is "008".
The way back needs no method of its own. Currency, and the currency parameter of
every method, read three digits as the letters they stand for — so a value taken straight
from such a message works everywhere:
CkMoney.NumericCode("eur"); // -> "978"
CkMoney.NumericCode("ALL"); // -> "008" (three digits, the zeros kept)
CkMoney.NumericCode("lei"); // -> ""
CkMoney.Currency("978"); // -> "EUR"
CkMoney.MinorUnits(
input: "1099",
currency: "392"
); // -> 1099 (392 is JPY: no decimals)
ISO hands a number on when one currency takes another's place: ANG and the XCG that
replaced it in 2025 are both "532", and "532" reads as XCG, the currency in use.
Digits inside an amount are never a currency — "978 10.99" is a number, not ten euros.
Decimals
How many decimals a currency has.
int Decimals(
string? currency = null,
int fallback = -1,
bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
| Parameter | Type | Default | What it does |
|---|---|---|---|
currency |
string? |
null |
The ISO 4217 code, in any casing. |
fallback |
int |
-1 |
What comes back when the code cannot be used — not 0, which is a real answer. |
throwIfInvalid |
bool |
CkSettings.DefaultThrowIfInvalid (false) |
Pass true to throw a CkMoneyException that says why, instead of returning the fallback, when a value cannot be used. |
Returns
| When | Default | With throwIfInvalid: true |
|---|---|---|
| The code is one the library knows | Its decimals, 0 to 4: 2 |
Its decimals, 0 to 4: 2 |
| Rejected: missing, not three letters, or not a known code | The fallback (default -1) |
Throws CkMoneyException — carries Input, Expected and Reason |
What a form needs for its input step, and a message for its wording ("at most two
decimals"). It is the number Format writes with and MinorUnits scales by.
CkMoney.Decimals("usd"); // -> 2
CkMoney.Decimals("JPY"); // -> 0 (the yen has no smaller coin)
CkMoney.Decimals("BHD"); // -> 3
CkMoney.Decimals("lei"); // -> -1 (a word, not a code)
Format
Write it the way an API expects.
string Format(
object? input = null,
string? currency = "USD",
int? decimals = null,
DecimalMark mark = CkSettings.DefaultDecimalMark,
string? fallback = null,
bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
| Parameter | Type | Default | What it does |
|---|---|---|---|
input |
object? |
null |
The amount: a number, or text. |
currency |
string? |
"USD" |
The ISO 4217 code deciding the decimals. |
decimals |
int? |
null |
How many decimals the amount may carry, in place of the currency's own — for a receiver that disagrees with ISO 4217, or a unit price kept at four. 0 to CkSettings.MaxMoneyDecimals (8); null for the currency's own. |
mark |
DecimalMark |
CkSettings.DefaultDecimalMark (DecimalMark.Dot) |
Which character is the decimal point in text. |
fallback |
string? |
null |
An amount to use when the input cannot be read, put through the same reading and formatting; a fallback that is itself rejected answers "". |
throwIfInvalid |
bool |
CkSettings.DefaultThrowIfInvalid (false) |
Pass true to throw a CkMoneyException that says why, instead of answering "", when a value cannot be used. |
Returns
| When | Default | With throwIfInvalid: true |
|---|---|---|
| The amount and the currency both read | The formatted text: "10.00" |
The formatted text: "10.00" |
| Rejected: an unknown currency, or an unreadable amount with no usable fallback | Empty string | Throws CkMoneyException — carries Input, Expected and Reason |
The fallback stands in for an unreadable amount only — an unknown currency always
answers empty.
Writes an amount with exactly its currency's decimals: plain digits, a dot, no
grouping, no symbol — the shape PayPal's API wants ("10.00", never "10"). Extra
decimals are rounded away from zero first.
With decimals, the text has that many decimals in place of the currency's own — 0 for a
receiver that refuses a decimal forint amount, 4 for a unit price.
CkMoney.Format(
input: "1000.50",
currency: "HUF",
decimals: 0
); // -> "1001"
CkMoney.Format(10); // -> "10.00" (USD is the default)
CkMoney.Format(
input: "99.5",
currency: "GBP"
); // -> "99.50"
CkMoney.Format(
input: 1099,
currency: "JPY"
); // -> "1099" (no decimals at all)
CkMoney.Format(
input: 3.1m,
currency: "BHD"
); // -> "3.100"
Display
Write it the way a person reads it.
string Display(
object? input = null,
string? currency = "USD",
DecimalMark into = CkSettings.DefaultDecimalMark,
bool showCurrency = true,
DecimalMark mark = CkSettings.DefaultDecimalMark,
string fallback = "",
bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
| Parameter | Type | Default | What it does |
|---|---|---|---|
input |
object? |
null |
The amount: a number, or text. |
currency |
string? |
"USD" |
The ISO 4217 code whose decimals apply. Money text that names another currency is refused. |
into |
DecimalMark |
CkSettings.DefaultDecimalMark (DecimalMark.Dot) |
Which character is the decimal point in the text written; the other one groups the digits. |
showCurrency |
bool |
true |
Write the currency code after the amount. |
mark |
DecimalMark |
CkSettings.DefaultDecimalMark (DecimalMark.Dot) |
Which character is the decimal point in the text read. |
fallback |
string |
"" |
What comes back when a value cannot be used, as given. |
throwIfInvalid |
bool |
CkSettings.DefaultThrowIfInvalid (false) |
Pass true to throw a CkMoneyException that says why, instead of returning the fallback, when a value cannot be used. |
Returns
| When | Default | With throwIfInvalid: true |
|---|---|---|
| The amount and the currency both read | The text: "1,234.50 EUR" |
The text: "1,234.50 EUR" |
| Rejected: an unknown currency, or an unreadable amount | The fallback (default empty) |
Throws CkMoneyException — carries Input, Expected and Reason |
Format is the shape for machines; this is the one for a screen, an e-mail, a PDF:
digits grouped in threes, exactly the currency's decimals, the code after it. The code
and not a symbol, on purpose — $ is a dozen currencies, and .NET's ToString("C")
takes its symbol from the culture, so it prints a dollar sign on a euro amount. No
culture is consulted here. What Display writes, Amount reads back under the same mark.
CkMoney.Display(1234.5m, "EUR"); // -> "1,234.50 EUR"
CkMoney.Display(1099, "JPY"); // -> "1,099 JPY"
CkMoney.Display(
input: 1234.5m,
currency: "EUR",
into: DecimalMark.Comma
); // -> "1.234,50 EUR"
CkMoney.Display(
input: "(1234.5)",
showCurrency: false
); // -> "-1,234.50"
Round
To the currency's decimals, away from zero.
decimal Round(
object? input = null,
string? currency = "USD",
DecimalMark mark = CkSettings.DefaultDecimalMark,
decimal fallback = 0,
bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
| Parameter | Type | Default | What it does |
|---|---|---|---|
input |
object? |
null |
The amount: a number, or text. |
currency |
string? |
"USD" |
The ISO 4217 code deciding the decimals. |
mark |
DecimalMark |
CkSettings.DefaultDecimalMark (DecimalMark.Dot) |
Which character is the decimal point in text. |
fallback |
decimal |
0 |
What comes back when a value cannot be used. |
throwIfInvalid |
bool |
CkSettings.DefaultThrowIfInvalid (false) |
Pass true to throw a CkMoneyException that says why, instead of returning the fallback, when a value cannot be used. |
Returns
| When | Default | With throwIfInvalid: true |
|---|---|---|
| The amount and the currency both read | The rounded amount: 10.01 |
The rounded amount: 10.01 |
| Rejected: an unknown currency, or an unreadable amount | The fallback (default 0) |
Throws CkMoneyException — carries Input, Expected and Reason |
Rounds by CkSettings.MoneyRounding (MidpointRounding.AwayFromZero) — the same
rule every CkMoney conversion uses.
Rounds an amount to its currency's decimals the accounting way: a midpoint moves away from zero, so US$10.005 becomes 10.01. (.NET's default is banker's rounding, which would round that midpoint to the nearest even digit — 10.00 — good for statistics, surprising on an invoice.)
CkMoney.Round(10.005m); // -> 10.01 (USD is the default)
CkMoney.Round(
input: "99.999",
currency: "GBP"
); // -> 100.00
CkMoney.Round(
input: 10.5m,
currency: "JPY"
); // -> 11
Split
Divide without losing a cent.
decimal[] Split(
object? input = null,
int parts = 2,
string? currency = "USD",
DecimalMark mark = CkSettings.DefaultDecimalMark,
bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
| Parameter | Type | Default | What it does |
|---|---|---|---|
input |
object? |
null |
The amount: a number, or text. |
parts |
int |
2 |
How many parts, 1 to CkSettings.MaxMoneySplitParts (1000). |
currency |
string? |
"USD" |
The ISO 4217 code deciding the minor unit. |
mark |
DecimalMark |
CkSettings.DefaultDecimalMark (DecimalMark.Dot) |
Which character is the decimal point in text. |
throwIfInvalid |
bool |
CkSettings.DefaultThrowIfInvalid (false) |
Pass true to throw a CkMoneyException that says why, instead of answering an empty array, when a value cannot be used. |
Returns
| When | Default | With throwIfInvalid: true |
|---|---|---|
| The amount, the currency and the count all read | The parts, in order: [33.34, 33.33, 33.33] |
The parts, in order: [33.34, 33.33, 33.33] |
| Rejected: an unreadable amount, an unknown currency, or a part count out of range | Empty array — never null |
Throws CkMoneyException — carries Input, Expected and Reason |
The amount is first rounded to the currency's decimals — parts can only differ by whole minor units.
Divides an amount into parts (2 by default, up to 1000) that always sum back to
exactly the whole. Naive division of 100 by 3 gives three 33.33s and a lost cent;
Split hands the extra minor units to the earlier parts instead.
CkMoney.Split(
input: 100m,
parts: 3
); // -> [33.34, 33.33, 33.33] (sums back to exactly 100.00)
CkMoney.Split(
input: "£10",
parts: 3,
currency: "GBP"
); // -> [3.34, 3.33, 3.33]
CkMoney.Split(
input: -10m,
parts: 3
); // -> [-3.34, -3.33, -3.33] (negatives split the same way)
Allocate
Split by ratio, never losing a cent.
decimal[] Allocate(
object? input = null,
object? ratios = null,
string? currency = "USD",
DecimalMark mark = CkSettings.DefaultDecimalMark,
bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
| Parameter | Type | Default | What it does |
|---|---|---|---|
input |
object? |
null |
The amount: a number, or text. |
ratios |
object? |
null |
The ratios: a list of numbers, none below zero and at least one above, up to CkSettings.MaxMoneySplitParts (1000). Only their proportions matter — 70 and 30, 7 and 3, 0.7 and 0.3 are one split. |
currency |
string? |
"USD" |
The ISO 4217 code whose decimals apply. Money text that names another currency is refused. |
mark |
DecimalMark |
CkSettings.DefaultDecimalMark (DecimalMark.Dot) |
Which character is the decimal point in text. |
throwIfInvalid |
bool |
CkSettings.DefaultThrowIfInvalid (false) |
Pass true to throw a CkMoneyException that says why, instead of returning an empty array, when a value cannot be used. |
Returns
| When | Default | With throwIfInvalid: true |
|---|---|---|
| Everything reads | The parts, in the order of the ratios: [70.01, 30.00] |
The parts, in the order of the ratios: [70.01, 30.00] |
| Rejected: an unreadable amount or ratio, a ratio below zero, no ratios, all zeros, an unknown currency | An empty array | Throws CkMoneyException — carries Input, Expected and Reason |
A revenue share, a discount spread over invoice lines by their value, a payment divided
between accounts. Doing it with Multiply loses or invents cents — half of US$0.05, twice,
is 0.03 + 0.03. Here each part gets its proportional share rounded down to the minor
unit, and the units left over go one at a time to the parts that lost the most in the
rounding (the largest-remainder method; a tie goes to the earlier part). A part whose
ratio is zero gets nothing. Split is the same with equal ratios.
CkMoney.Allocate(100.01m, new[] { 70, 30 }); // -> [70.01, 30]
CkMoney.Allocate("$1.00", new[] { 2, 3, 1 }); // -> [0.33, 0.5, 0.17]
CkMoney.Allocate(0.05m, new[] { 1, 1 }); // -> [0.03, 0.02] (never two 0.03s)
CkMoney.Allocate(1.00m, new[] { 0, 2, 1 }); // -> [0, 0.67, 0.33]
// a 25.00 discount over three invoice lines, by their value
CkMoney.Allocate(
input: "25.00",
ratios: new[] { 199.99m, 49.50m, 12.00m }
); // -> [19.12, 4.73, 1.15] (sums back to exactly 25.00)
IsAmount
Does it read as money at all?
bool IsAmount(
object? input = null,
DecimalMark mark = CkSettings.DefaultDecimalMark,
bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
| Parameter | Type | Default | What it does |
|---|---|---|---|
input |
object? |
null |
The value: a number, or money text in any shape Amount reads. |
mark |
DecimalMark |
CkSettings.DefaultDecimalMark (DecimalMark.Dot) |
Which character is the decimal point. |
throwIfInvalid |
bool |
CkSettings.DefaultThrowIfInvalid (false) |
Pass true to throw a CkMoneyException that says why, instead of answering false, when the value is not an amount. |
Returns
| When | Default | With throwIfInvalid: true |
|---|---|---|
A number, or money text: "10.99", "$1,234.56", "(123.45)", "USD 10.99" |
true |
true |
| Rejected: not an amount — missing, letters, an undeclared decimal comma, an unknown code | false |
Throws CkMoneyException — carries Input, Expected and Reason |
The yes-or-no form of Amount. Zero is an amount here, whatever
AmountOrNull says about it; when the value is going to be stored, read it
with that method instead and check for null.
CkMoney.IsAmount("$1,234.56"); // -> true
CkMoney.IsAmount("1,23"); // -> false (a decimal comma needs declaring)
CkMoney.IsAmount("1e5"); // -> false (letters are junk in money)
IsCurrency
A known ISO 4217 code?
bool IsCurrency(
string? input = null,
bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
| Parameter | Type | Default | What it does |
|---|---|---|---|
input |
string? |
null |
The raw code. |
throwIfInvalid |
bool |
CkSettings.DefaultThrowIfInvalid (false) |
Pass true to throw a CkMoneyException that says why, instead of answering false, when the value is not a known code. |
Returns
| When | Default | With throwIfInvalid: true |
|---|---|---|
| Three letters in any casing that name a known code | true |
true |
| Rejected: not three letters, or not a known code | false |
Throws CkMoneyException — carries Input, Expected and Reason |
The yes-or-no form of Currency; call that instead when you want the upper-case spelling back.
CkMoney.IsCurrency("usd"); // -> true
CkMoney.IsCurrency("US$"); // -> false
InCurrencies
One of the currencies we settle in?
bool InCurrencies(
string? input = null,
params IEnumerable<string?> codes
)
| Parameter | Type | Default | What it does |
|---|---|---|---|
input |
string? |
null |
The raw code. |
codes |
params IEnumerable<string?> |
— | The codes to accept, in any casing. Entries that are not known codes are skipped. |
Returns
| When | You get |
|---|---|
| The value is a known code and an entry names the same one | true |
| It is not a known code, no entry matches, or the list is empty | false — an answer, not a failure; it never throws |
The value and every entry are read the way Currency reads them. A params
list must be last, so there is no throwIfInvalid; call Currency with it first when you
need the exception.
CkMoney.InCurrencies("usd", "USD", "EUR", "GBP"); // -> true
CkMoney.InCurrencies("JPY", "USD", "EUR", "GBP"); // -> false
IsRounded
Already at the currency's precision?
bool IsRounded(
object? input = null,
string? currency = "USD",
int? decimals = null,
DecimalMark mark = CkSettings.DefaultDecimalMark,
bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
| Parameter | Type | Default | What it does |
|---|---|---|---|
input |
object? |
null |
The value: a number, or money text in any shape Amount reads. |
currency |
string? |
"USD" |
The ISO 4217 code whose decimals apply. |
decimals |
int? |
null |
How many decimals the amount may carry, in place of the currency's own — for a receiver that disagrees with ISO 4217, or a unit price kept at four. 0 to CkSettings.MaxMoneyDecimals (8); null for the currency's own. |
mark |
DecimalMark |
CkSettings.DefaultDecimalMark (DecimalMark.Dot) |
Which character is the decimal point. |
throwIfInvalid |
bool |
CkSettings.DefaultThrowIfInvalid (false) |
Pass true to throw a CkMoneyException that says why, instead of answering false, when the value is not an amount or the currency is not a known code. |
Returns
| When | Default | With throwIfInvalid: true |
|---|---|---|
| The amount carries no more decimals than the currency allows | true |
true |
| It carries more | false |
false |
| Rejected: not an amount, or an unknown currency | false |
Throws CkMoneyException — carries Input, Expected and Reason |
The pre-charge check: a processor rejects an amount it cannot express in minor units.
10.99 US dollars yes, 10.999 no; 1099 yen yes, 1099.5 no; 3.141 Bahraini dinars
yes. Trailing zeros are not extra precision, so "10.990" passes. The same as
Round(x) == Amount(x) in one call.
With decimals, the check is against that many: IsRounded("1000.50", "HUF", decimals: 0)
is false — it fits the forint, and not a receiver that takes whole forint only.
CkMoney.IsRounded("10.99"); // -> true (USD by default)
CkMoney.IsRounded("10.999"); // -> false
CkMoney.IsRounded("1099.5", "JPY"); // -> false
IsPositive
Above zero?
bool IsPositive(
object? input = null,
bool allowZero = false,
DecimalMark mark = CkSettings.DefaultDecimalMark,
bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
| Parameter | Type | Default | What it does |
|---|---|---|---|
input |
object? |
null |
The value: a number, or money text in any shape Amount reads. |
allowZero |
bool |
false |
Count zero as well. |
mark |
DecimalMark |
CkSettings.DefaultDecimalMark (DecimalMark.Dot) |
Which character is the decimal point. |
throwIfInvalid |
bool |
CkSettings.DefaultThrowIfInvalid (false) |
Pass true to throw a CkMoneyException that says why, instead of answering false, when the value is not an amount. |
Returns
| When | Default | With throwIfInvalid: true |
|---|---|---|
An amount above zero — or zero itself with allowZero |
true |
true |
Zero (by default), or a negative amount, "(123.45)" included |
false |
false |
| Rejected: not an amount — missing, letters, an undeclared decimal comma, an unknown code | false |
Throws CkMoneyException — carries Input, Expected and Reason |
"A payment must be positive"; with allowZero: true, "a balance must not be negative".
CkNumber.IsPositive refuses "$5", which is why this one exists.
CkMoney.IsPositive("$5"); // -> true
CkMoney.IsPositive("$0.00"); // -> false
CkMoney.IsPositive("$0.00", allowZero: true); // -> true
IsNegative
Below zero?
bool IsNegative(
object? input = null,
bool allowZero = false,
DecimalMark mark = CkSettings.DefaultDecimalMark,
bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
| Parameter | Type | Default | What it does |
|---|---|---|---|
input |
object? |
null |
The value: a number, or money text in any shape Amount reads. |
allowZero |
bool |
false |
Count zero as well. |
mark |
DecimalMark |
CkSettings.DefaultDecimalMark (DecimalMark.Dot) |
Which character is the decimal point. |
throwIfInvalid |
bool |
CkSettings.DefaultThrowIfInvalid (false) |
Pass true to throw a CkMoneyException that says why, instead of answering false, when the value is not an amount. |
Returns
| When | Default | With throwIfInvalid: true |
|---|---|---|
An amount below zero — "(123.45)", "123.45-", "-$5" — or zero itself with allowZero |
true |
true |
| Zero (by default), or a positive amount | false |
false |
| Rejected: not an amount — missing, letters, an undeclared decimal comma, an unknown code | false |
Throws CkMoneyException — carries Input, Expected and Reason |
"A refund must be negative", "a credit note". The mirror of IsPositive;
IsNegative, IsZero and IsPositive partition the amounts.
CkMoney.IsNegative("(123.45)"); // -> true
CkMoney.IsNegative("$5"); // -> false
IsZero
Exactly zero?
bool IsZero(
object? input = null,
DecimalMark mark = CkSettings.DefaultDecimalMark,
bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
| Parameter | Type | Default | What it does |
|---|---|---|---|
input |
object? |
null |
The value: a number, or money text in any shape Amount reads. |
mark |
DecimalMark |
CkSettings.DefaultDecimalMark (DecimalMark.Dot) |
Which character is the decimal point. |
throwIfInvalid |
bool |
CkSettings.DefaultThrowIfInvalid (false) |
Pass true to throw a CkMoneyException that says why, instead of answering false, when the value is not an amount. |
Returns
| When | Default | With throwIfInvalid: true |
|---|---|---|
An amount equal to zero: 0, "0.00", "$0", "(0.00)" |
true |
true |
| Any other amount | false |
false |
| Rejected: not an amount — missing, letters, an undeclared decimal comma, an unknown code | false |
Throws CkMoneyException — carries Input, Expected and Reason |
AmountOrNull calls a zero amount "missing" by default; this says so out loud, for data-quality checks and for telling "nothing charged" from "not an amount".
CkMoney.IsZero("$0.00"); // -> true
CkMoney.IsZero("junk"); // -> false (not an amount, not zero)
IsBetween
Inside a range?
bool IsBetween(
object? input = null,
object? min = null,
object? max = null,
DecimalMark mark = CkSettings.DefaultDecimalMark,
bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
| Parameter | Type | Default | What it does |
|---|---|---|---|
input |
object? |
null |
The value: a number, or money text in any shape Amount reads. |
min |
object? |
null |
The lower bound, inclusive, read the same way — "$10.00" works. null means no lower bound. |
max |
object? |
null |
The upper bound, inclusive, read the same way. null means no upper bound. |
mark |
DecimalMark |
CkSettings.DefaultDecimalMark (DecimalMark.Dot) |
Which character is the decimal point. |
throwIfInvalid |
bool |
CkSettings.DefaultThrowIfInvalid (false) |
Pass true to throw a CkMoneyException that says which value is not an amount and why — or that the bounds contradict each other — instead of answering false. |
Returns
| When | Default | With throwIfInvalid: true |
|---|---|---|
| The value and the given bounds read, and the value lies inside, both ends included | true |
true |
| Everything reads, and the value lies outside | false |
false |
| Rejected: an unreadable value or bound, or bounds that contradict each other | false |
Throws CkMoneyException — carries Input, Expected and Reason |
"Minimum order", "transfer limit". Bounds are read as amounts too, and the mark applies
to all three; a null bound is open, so IsBetween(total, 10, null) is "at least ten".
CkMoney.IsBetween("$50.00", "$10.00", "$10,000.00"); // -> true
CkMoney.IsBetween("9.99", 10, 100); // -> false
CkMoney.IsBetween("50", 100, 10); // -> false (contradictory bounds)
IsAtLeast
At least this much?
bool IsAtLeast(
object? input = null,
object? min = null,
DecimalMark mark = CkSettings.DefaultDecimalMark,
bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
| Parameter | Type | Default | What it does |
|---|---|---|---|
input |
object? |
null |
The value: a number, or money text in any shape Amount reads. |
min |
object? |
null |
The lower bound, inclusive, read the same way. |
mark |
DecimalMark |
CkSettings.DefaultDecimalMark (DecimalMark.Dot) |
Which character is the decimal point. |
throwIfInvalid |
bool |
CkSettings.DefaultThrowIfInvalid (false) |
Pass true to throw a CkMoneyException that says why, instead of answering false, when either value is not an amount. |
Returns
| When | Default | With throwIfInvalid: true |
|---|---|---|
Both read, and the value is greater than or equal to min |
true |
true |
Both read, and the value is below min |
false |
false |
| Rejected: either is not an amount | false |
Throws CkMoneyException — carries Input, Expected and Reason |
"A minimum order of $10", "a deposit of at least 100". The bound counts.
CkMoney.IsAtLeast("$10.01", "$10.00"); // -> true
CkMoney.IsAtLeast("9.99", 10); // -> false
IsAtMost
At most this much?
bool IsAtMost(
object? input = null,
object? max = null,
DecimalMark mark = CkSettings.DefaultDecimalMark,
bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
| Parameter | Type | Default | What it does |
|---|---|---|---|
input |
object? |
null |
The value: a number, or money text in any shape Amount reads. |
max |
object? |
null |
The upper bound, inclusive, read the same way. |
mark |
DecimalMark |
CkSettings.DefaultDecimalMark (DecimalMark.Dot) |
Which character is the decimal point. |
throwIfInvalid |
bool |
CkSettings.DefaultThrowIfInvalid (false) |
Pass true to throw a CkMoneyException that says why, instead of answering false, when either value is not an amount. |
Returns
| When | Default | With throwIfInvalid: true |
|---|---|---|
Both read, and the value is less than or equal to max |
true |
true |
Both read, and the value is above max |
false |
false |
| Rejected: either is not an amount | false |
Throws CkMoneyException — carries Input, Expected and Reason |
"A transfer limit of $10,000", "a refund no larger than the payment". The bound counts.
CkMoney.IsAtMost("9,999.99", "$10,000"); // -> true
CkMoney.IsAtMost("10,000.01", 10000); // -> false
SameAmount
The same sum, whatever the dressing?
bool SameAmount(
object? a = null,
object? b = null,
decimal tolerance = 0,
DecimalMark mark = CkSettings.DefaultDecimalMark,
bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
| Parameter | Type | Default | What it does |
|---|---|---|---|
a |
object? |
null |
The first value: a number, or money text. |
b |
object? |
null |
The second value, read the same way. |
tolerance |
decimal |
0 |
How far apart the two may be and still count as the same. Below 0 acts as 0. |
mark |
DecimalMark |
CkSettings.DefaultDecimalMark (DecimalMark.Dot) |
Which character is the decimal point. |
throwIfInvalid |
bool |
CkSettings.DefaultThrowIfInvalid (false) |
Pass true to throw a CkMoneyException that says which value is not an amount and why, instead of answering false. |
Returns
| When | Default | With throwIfInvalid: true |
|---|---|---|
Both read as amounts and differ by at most tolerance |
true |
true |
| Both read, and differ by more | false |
false |
| Rejected: either is not an amount | false |
Throws CkMoneyException — carries Input, Expected and Reason |
The reconciliation check — "the payment equals the invoice" — comparing the amounts
alone: "USD 10.99" and "10.99 EUR" are the same sum here, since the dressing is not
compared. Pass a tolerance of 0.01m for feeds that round differently. Two values that
are not amounts are never the same.
CkMoney.SameAmount("$1,234.56", 1234.56m); // -> true
CkMoney.SameAmount("10.99", "10.98"); // -> false
CkMoney.SameAmount("10.99", "10.98", tolerance: 0.01m); // -> true
HasCurrency
Does the text say which currency?
bool HasCurrency(
object? input = null,
DecimalMark mark = CkSettings.DefaultDecimalMark,
bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
| Parameter | Type | Default | What it does |
|---|---|---|---|
input |
object? |
null |
The value: a number, or money text in any shape Amount reads. |
mark |
DecimalMark |
CkSettings.DefaultDecimalMark (DecimalMark.Dot) |
Which character is the decimal point. |
throwIfInvalid |
bool |
CkSettings.DefaultThrowIfInvalid (false) |
Pass true to throw a CkMoneyException that says why, instead of answering false, when the value is not an amount. |
Returns
| When | Default | With throwIfInvalid: true |
|---|---|---|
An amount whose text carries a known code at either end or one of the symbols $ € £ ¥ |
true |
true |
| A bare amount, or a boxed number, which declares nothing | false |
false |
| Rejected: not an amount — missing, letters, an undeclared decimal comma, an unknown code | false |
Throws CkMoneyException — carries Input, Expected and Reason |
For multi-currency intake, where a bare "10.99" is ambiguous and should be refused. It
says that a currency was declared, not which: $ alone cannot tell US, Canadian and
Australian dollars apart. The accounting wrapper does not hide the code, so
"(USD 10.99)" declares one.
CkMoney.HasCurrency("USD 10.99"); // -> true
CkMoney.HasCurrency("$10.99"); // -> true
CkMoney.HasCurrency("10.99"); // -> false
Multiply
Tax, discounts, rounded the accounting way.
decimal Multiply(
object? input = null,
object? factor = null,
string? currency = "USD",
DecimalMark mark = CkSettings.DefaultDecimalMark,
decimal fallback = 0,
bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
| Parameter | Type | Default | What it does |
|---|---|---|---|
input |
object? |
null |
The amount: a number, or money text. |
factor |
object? |
null |
The multiplier, a rate such as 1.0825, not a percentage. |
currency |
string? |
"USD" |
The ISO 4217 code whose decimals apply. |
mark |
DecimalMark |
DecimalMark.Dot |
Which character is the decimal point in the amount. |
fallback |
decimal |
0 |
What comes back when a value cannot be read. |
throwIfInvalid |
bool |
CkSettings.DefaultThrowIfInvalid (false) |
Pass true to throw a CkMoneyException that says why, instead of answering the fallback, when a value cannot be used. |
Returns
| When | Default | With throwIfInvalid: true |
|---|---|---|
| The amount, the factor and the currency read | The product, rounded to the currency's decimals away from zero | The product |
| Rejected: any of them unusable | The fallback |
Throws CkMoneyException — carries Input, Expected and Reason — Expected names which |
The multiplication that goes wrong when it is done in double or rounded by
halves-to-even. Rounded as Round rounds.
CkMoney.Multiply("$19.99", 1.0825); // -> 21.64m (the price with 8.25% tax)
CkMoney.Multiply(total, 0.9); // -> a ten-percent discount
Divide
An amount by a number, rounded once.
decimal Divide(
object? input = null,
object? divisor = null,
string? currency = "USD",
DecimalMark mark = CkSettings.DefaultDecimalMark,
decimal fallback = 0,
bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
| Parameter | Type | Default | What it does |
|---|---|---|---|
input |
object? |
null |
The amount: a number, or text. |
divisor |
object? |
null |
The divisor: a number, or text that reads as one; not zero. A plain number, not money. |
currency |
string? |
"USD" |
The ISO 4217 code whose decimals apply. Money text that names another currency is refused. |
mark |
DecimalMark |
CkSettings.DefaultDecimalMark (DecimalMark.Dot) |
Which character is the decimal point in text. |
fallback |
decimal |
0 |
What comes back when a value cannot be used. |
throwIfInvalid |
bool |
CkSettings.DefaultThrowIfInvalid (false) |
Pass true to throw a CkMoneyException that says why, instead of returning the fallback, when a value cannot be used. |
Returns
| When | Default | With throwIfInvalid: true |
|---|---|---|
| Everything reads | The quotient, rounded to the currency: 33.33 |
The quotient, rounded to the currency: 33.33 |
| Rejected: division by zero, an unreadable amount or divisor, an unknown currency | The fallback (default 0) |
Throws CkMoneyException — carries Input, Expected and Reason |
A unit price from a total and a quantity; a monthly figure from a yearly one. Multiply
cannot do it exactly — a third has no exact decimal. To share an amount out so that the
parts add back to the whole, use Split or Allocate: three 33.33s are 99.99.
CkMoney.Divide("$100", 3); // -> 33.33
CkMoney.Divide(1099, 2, "JPY"); // -> 550 (whole yen)
CkMoney.Divide("10", 0); // -> 0 (division by zero: the fallback)
Convert
To another currency, at your rate.
decimal Convert(
object? input = null,
object? rate = null,
string? from = null,
string? to = null,
DecimalMark mark = CkSettings.DefaultDecimalMark,
decimal fallback = 0,
bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
| Parameter | Type | Default | What it does |
|---|---|---|---|
input |
object? |
null |
The amount: a number, or text. Money text that names a currency must name from. |
rate |
object? |
null |
The exchange rate, above zero: how many units of to one unit of from buys. |
from |
string? |
null |
The ISO 4217 code the amount is in. |
to |
string? |
null |
The ISO 4217 code to convert it to; its decimals decide the rounding. |
mark |
DecimalMark |
CkSettings.DefaultDecimalMark (DecimalMark.Dot) |
Which character is the decimal point in text. |
fallback |
decimal |
0 |
What comes back when a value cannot be used. |
throwIfInvalid |
bool |
CkSettings.DefaultThrowIfInvalid (false) |
Pass true to throw a CkMoneyException that says why, instead of returning the fallback, when a value cannot be used. |
Returns
| When | Default | With throwIfInvalid: true |
|---|---|---|
| Everything reads | The converted amount, rounded to to: 92.00 |
The converted amount, rounded to to: 92.00 |
Rejected: an unknown currency, a rate that is not above zero, an unreadable amount, text naming another currency than from |
The fallback (default 0) |
Throws CkMoneyException — carries Input, Expected and Reason |
The library holds no rates: where they come from, and how fresh they are, is yours to
decide. What it adds is the rounding for the target currency — dollars into yen lose
their decimals — and the check that a value saying "100 EUR" is not converted from
dollars.
CkMoney.Convert("$100", 0.92m, "USD", "EUR"); // -> 92.00
CkMoney.Convert(100, 157.35m, "USD", "JPY"); // -> 15735 (the yen has no decimals)
CkMoney.Convert("100 EUR", 0.92m, "USD", "EUR"); // -> 0 (the text says EUR, not USD)
Subtract
One amount less another.
decimal Subtract(
object? input = null,
object? amount = null,
string? currency = "USD",
DecimalMark mark = CkSettings.DefaultDecimalMark,
decimal fallback = 0,
bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
| Parameter | Type | Default | What it does |
|---|---|---|---|
input |
object? |
null |
The amount to take from: a number, or text. |
amount |
object? |
null |
The amount to take away: a number, or text — money-dressed like the first. |
currency |
string? |
"USD" |
The ISO 4217 code whose decimals apply. Money text that names another currency is refused, in either value. |
mark |
DecimalMark |
CkSettings.DefaultDecimalMark (DecimalMark.Dot) |
Which character is the decimal point in text. |
fallback |
decimal |
0 |
What comes back when a value cannot be used. |
throwIfInvalid |
bool |
CkSettings.DefaultThrowIfInvalid (false) |
Pass true to throw a CkMoneyException that says why, instead of returning the fallback, when a value cannot be used. |
Returns
| When | Default | With throwIfInvalid: true |
|---|---|---|
| Both amounts and the currency read | The difference, rounded to the currency: 74.50 |
The difference, rounded to the currency: 74.50 |
| Rejected: an unreadable amount, an unknown currency, text naming another currency | The fallback (default 0) |
Throws CkMoneyException — carries Input, Expected and Reason |
A total less a discount, a charge less a refund. Both values are read as money, so either
may carry a symbol or grouping; the difference is exact and rounded once. It goes below zero
when the second is the larger. To add, use Total.
CkMoney.Subtract("$100.00", "$25.50"); // -> 74.50
CkMoney.Subtract(10, 25); // -> -15
CkMoney.Subtract("100 USD", "25 EUR"); // -> 0 (the second value names another currency)
Total
A column of money, summed exactly.
decimal Total(
object? values = null,
DecimalMark mark = CkSettings.DefaultDecimalMark,
decimal fallback = 0,
bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
| Parameter | Type | Default | What it does |
|---|---|---|---|
values |
object? |
null |
Any list or array of numbers or money text. |
mark |
DecimalMark |
DecimalMark.Dot |
Which character is the decimal point. |
fallback |
decimal |
0 |
What comes back when the list is missing or an entry cannot be read. |
throwIfInvalid |
bool |
CkSettings.DefaultThrowIfInvalid (false) |
Pass true to throw a CkMoneyException that says why, instead of answering the fallback, when the list or an entry cannot be used. |
Returns
| When | Default | With throwIfInvalid: true |
|---|---|---|
| Every entry reads | The exact sum; an empty list totals zero | The sum |
| Rejected: no list, or an entry that is not an amount | The fallback |
Throws CkMoneyException — carries Input, Expected and Reason — the reason names the entry |
Strict on purpose: one entry that is not an amount rejects the whole total, since a sum that silently skipped a row is worse than none. A string is one value, not a list.
CkMoney.Total(new[] { "$1,234.56", "(12.00)", "9.99" }); // -> 1232.55m
Explain
Why not — in words, without an exception.
string Explain(
object? input = null,
DecimalMark mark = CkSettings.DefaultDecimalMark,
string? currency = null
)
| Parameter | Type | Default | What it does |
|---|---|---|---|
input |
object? |
null |
The value: a number, or money text. |
mark |
DecimalMark |
DecimalMark.Dot |
Which character is the decimal point. |
currency |
string? |
null |
The ISO 4217 code the amount is meant in. Given, the value is judged the way Format and MinorUnits judge it — known code, and no other currency named in the text. null judges the amount alone. |
Returns
| When | You get |
|---|---|
| The value passes the reader | "" |
| It does not | The reason, the same words the exception would carry — an answer, not a failure; it never throws |
The form-friendly twin of throwIfInvalid: the message to show beside the field,
without a try and catch. Every class with a reader has one, on the reader's own
rules, so the reason is exactly what the reader would say.
CkMoney.Explain("ten"); // -> "letters in the amount"
CkMoney.Explain("1.234,56"); // -> "a comma after the decimal point (declare DecimalMark.Comma to read European notation)"
CkMoney.Explain(
input: "1099 JPY",
currency: "USD"
); // -> "the text says JPY, the currency asked for is USD"
Good to know
- A currency named in the text is checked, never just dropped (since 1.3.0). A code at
either end of the text is dressing, and
currencydefaults to"USD"— soMinorUnits("1099 JPY")once answered109900, yen scaled as dollars. Every method that is told acurrencynow refuses a value whose text names another; passcurrency: "JPY"and it is1099. The methods that compare or add several values —SameAmount,IsAtLeast,IsAtMost,IsBetween,Total— refuse two values that name different currencies:Total(new[] { "10 USD", "5 EUR" })is the fallback, not 15, andSameAmount("50 EUR", "50 USD")is false. A value that names no currency goes with any. The symbols$ € £ ¥never count: each is shared by several currencies. - Decimal commas are declared, never guessed.
"1.234,56"reads only withmark: DecimalMark.Comma; without it the value is rejected, and dot-decimal text is rejected underComma— so an amount is never silently misread as a different number. AmountOrNulltreats zero as missing by default. PasstreatZeroAsMissing: falsewhen a zero is a real amount in your data.- Minor units use each currency's real ISO 4217 decimals — 2 for most, 0 for the yen family, 3 for the dinars. Never assume "times 100".
Roundgoes away from zero, not banker's-style — the accounting convention.Splitnever loses a cent and the earlier parts carry the extras.- CkNumber will not read
"$5"or"1,234"— money dressing is this class's job; plain numbers are CkNumber's. - Stray letters reject. Only known ISO codes are dropped from amount text, and
"1e5"is not read as scientific notation — you get the fallback, not a surprise. - Everything is
decimaland culture-invariant. The machine's locale is never consulted, so results are the same on every server. Pull raw values out of a payload bag with CkDictionary, then read them here. - The validators read money text and treat zero as an amount.
IsPositive("$5")is the money spelling ofCkNumber.IsPositive(5);IsZero("$0.00")istrueeven thoughAmountOrNullcalls it missing. All of them answerfalsefor anything unreadable, and every one butInCurrenciestakesthrowIfInvalid. IsRoundedis the pre-charge check. An amount with more decimals than its currency allows cannot become minor units without rounding; check it beforeMinorUnits, or letRounddecide the rounding for you.
If you want errors instead
Every method except AmountOrNull and InCurrencies takes throwIfInvalid: true; a
value that cannot be used then raises a CkMoneyException (from SafeCoder.CommonKit.Exceptions) instead of returning the
fallback, "", false or the empty array. The exception carries the Input, what was
Expected, and a short Reason, flattened into a one-line message. AmountOrNull
never throws — null is its answer for anything unusable; when you want errors, read
the value with Amount and throwIfInvalid: true instead.
CkMoney.Amount(
input: "ten dollars",
throwIfInvalid: true
); // throws CkMoneyException — the message says why