Skip to content

CkNumber

Job: turn whatever a database, a payload or a form hands you into the exact C# number type you ask for — an int, a decimal, a double — without ever throwing.

Values arrive in all sorts of wrappers: a decimal from SQL Server, a double from JSON, the text "42" from a form field. CkNumber takes any of them — the input parameter is object?, so you can pass a boxed number of any numeric type (boxed = a number carried inside an object, the way a data reader hands values back) or plain text — and gives you back the type you named. When the value is missing, not a number, or too big for the type, you get the fallback instead — 0 unless you pick another. That is this class's standard rejection value: the fallback, never an exception. Native-sized integers (nint, nuint) are read like any other number; their range is the process's — 64-bit today — which only decides what values you can hold, never how a given value converts.

Text is read the plain, invariant way: an optional sign, digits, a dot for the decimal point, an optional exponent ("4.2e1" is 42). Grouping commas and currency symbols are not numbers here — "1,234" and "$5" are rejected. Money-dressed text is CkMoney's job.

Quick look

using SafeCoder.CommonKit;

CkNumber.Int32("42");         // -> 42
CkNumber.Int32("1e3");        // -> 1000   (exponent form is fine)
CkNumber.Decimal("12.50");    // -> 12.50m (trailing zero kept — ready for money)
CkNumber.Int32("$5");         // -> 0      (money text — rejected; that's CkMoney's job)
CkNumber.Int32OrNull(null);   // -> null   (missing stays missing)
CkNumber.IsBetween("5", 1, 10);   // -> true
CkNumber.IsPositive(0, allowZero: true);   // -> true
CkNumber.Fits<byte>("300");   // -> false  (outside 0 to 255)
CkNumber.Clamp("250", 1, 100);   // -> 100
CkNumber.Divide(5, 0, fallback: -1);   // -> -1m  (no crash)
CkNumber.Explain("1,234");     // -> "not a number"

Methods

The first thirteen methods are one conversion per C# number type. They all work the same way; you just pick the size and shape you need — the shared rules live here, each method's own type and range in its section below.

A value outside the type's range is rejected — never silently wrapped or clipped. So is anything that simply is not a number: a boolean, a char, a date, NaN, an infinity, nothing at all.

Rounding. The ten integer methods (Byte through UInt128) round a fractional value to the nearest whole number. Halves go to the even neighbour — this is called banker's rounding, and it stops thousands of .5 values from all drifting upward. If you would rather refuse fractions than round them, pass exact: true.

CkNumber.Int32(12.5);      // -> 12    (half rounds to the even neighbour)
CkNumber.Int32(13.5);      // -> 14    (so does this one)

CkNumber.Int32(
    input: "12.5",
    exact: true,
    fallback: 42
);                         // -> 42    (exact: true refuses fractions)

Single, Double and Decimal have no exact parameter — fractions are their normal food, so there is nothing to round or refuse.

Byte

A tiny whole number from 0 to 255.

byte Byte(
    object? input = null,
    bool exact = false,
    byte fallback = 0,
    bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
Parameter Type Default What it does
input object? null The value: a number of any type, or text.
exact bool false Rounds a fractional value to the nearest whole number first, halves to the even neighbour; pass true to accept only whole values, so 12.5 answers the fallback.
fallback byte 0 What comes back when the value cannot be converted.
throwIfInvalid bool CkSettings.DefaultThrowIfInvalid (false) Pass true to throw a CkNumberException that says why, instead of answering the fallback, when the value cannot be converted.

Returns

When Default With throwIfInvalid: true
The value reads as a number the type holds The converted value: 42 The converted value: 42
Rejected: missing, not a number, outside 0 to 255, or a fraction with exact: true The fallback (default 0) Throws CkNumberException — carries Input, TargetType and Reason

You get a byte — it holds 0 to 255.

CkNumber.Byte("42");    // -> 42
CkNumber.Byte("300");   // -> 0     (too big for a byte — the fallback)

SByte

A tiny whole number that allows a minus sign.

sbyte SByte(
    object? input = null,
    bool exact = false,
    sbyte fallback = 0,
    bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
Parameter Type Default What it does
input object? null The value: a number of any type, or text.
exact bool false Rounds a fractional value to the nearest whole number first, halves to the even neighbour; pass true to accept only whole values, so 12.5 answers the fallback.
fallback sbyte 0 What comes back when the value cannot be converted.
throwIfInvalid bool CkSettings.DefaultThrowIfInvalid (false) Pass true to throw a CkNumberException that says why, instead of answering the fallback, when the value cannot be converted.

Returns

When Default With throwIfInvalid: true
The value reads as a number the type holds The converted value: -100 The converted value: -100
Rejected: missing, not a number, outside -128 to 127, or a fraction with exact: true The fallback (default 0) Throws CkNumberException — carries Input, TargetType and Reason

You get an sbyte — it holds -128 to 127. Works like Byte, with the range shifted to make room for negatives.

CkNumber.SByte("-100");   // -> -100

Int16

A small whole number.

short Int16(
    object? input = null,
    bool exact = false,
    short fallback = 0,
    bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
Parameter Type Default What it does
input object? null The value: a number of any type, or text.
exact bool false Rounds a fractional value to the nearest whole number first, halves to the even neighbour; pass true to accept only whole values, so 12.5 answers the fallback.
fallback short 0 What comes back when the value cannot be converted.
throwIfInvalid bool CkSettings.DefaultThrowIfInvalid (false) Pass true to throw a CkNumberException that says why, instead of answering the fallback, when the value cannot be converted.

Returns

When Default With throwIfInvalid: true
The value reads as a number the type holds The converted value: 32000 The converted value: 32000
Rejected: missing, not a number, outside -32,768 to 32,767, or a fraction with exact: true The fallback (default 0) Throws CkNumberException — carries Input, TargetType and Reason

You get a short — it holds -32,768 to 32,767.

CkNumber.Int16("32000");   // -> 32000

UInt16

A small whole number that is never negative.

ushort UInt16(
    object? input = null,
    bool exact = false,
    ushort fallback = 0,
    bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
Parameter Type Default What it does
input object? null The value: a number of any type, or text.
exact bool false Rounds a fractional value to the nearest whole number first, halves to the even neighbour; pass true to accept only whole values, so 12.5 answers the fallback.
fallback ushort 0 What comes back when the value cannot be converted.
throwIfInvalid bool CkSettings.DefaultThrowIfInvalid (false) Pass true to throw a CkNumberException that says why, instead of answering the fallback, when the value cannot be converted.

Returns

When Default With throwIfInvalid: true
The value reads as a number the type holds The converted value: 65535 The converted value: 65535
Rejected: missing, not a number, outside 0 to 65,535, or a fraction with exact: true The fallback (default 0) Throws CkNumberException — carries Input, TargetType and Reason

You get a ushort — it holds 0 to 65,535. The U is for unsigned: the type has no minus sign, so a negative value is rejected — the same goes for the other U types below.

CkNumber.UInt16("65535");   // -> 65535
CkNumber.UInt16("-1");      // -> 0     (negative — the fallback)

Int32

The everyday whole number.

int Int32(
    object? input = null,
    bool exact = false,
    int fallback = 0,
    bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
Parameter Type Default What it does
input object? null The value: a number of any type, or text.
exact bool false Rounds a fractional value to the nearest whole number first, halves to the even neighbour; pass true to accept only whole values, so 12.5 answers the fallback.
fallback int 0 What comes back when the value cannot be converted.
throwIfInvalid bool CkSettings.DefaultThrowIfInvalid (false) Pass true to throw a CkNumberException that says why, instead of answering the fallback, when the value cannot be converted.

Returns

When Default With throwIfInvalid: true
The value reads as a number the type holds The converted value: 42 The converted value: 42
Rejected: missing, not a number, outside about ±2.1 billion, or a fraction with exact: true The fallback (default 0) Throws CkNumberException — carries Input, TargetType and Reason

You get an int — it holds about ±2.1 billion. This is the type most ids and counts live in.

CkNumber.Int32("12.00");   // -> 12    (a whole number in decimal clothes)

UInt32

The everyday whole number without the minus sign.

uint UInt32(
    object? input = null,
    bool exact = false,
    uint fallback = 0,
    bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
Parameter Type Default What it does
input object? null The value: a number of any type, or text.
exact bool false Rounds a fractional value to the nearest whole number first, halves to the even neighbour; pass true to accept only whole values, so 12.5 answers the fallback.
fallback uint 0 What comes back when the value cannot be converted.
throwIfInvalid bool CkSettings.DefaultThrowIfInvalid (false) Pass true to throw a CkNumberException that says why, instead of answering the fallback, when the value cannot be converted.

Returns

When Default With throwIfInvalid: true
The value reads as a number the type holds The converted value: 4000000000 The converted value: 4000000000
Rejected: missing, not a number, outside 0 to about 4.3 billion, or a fraction with exact: true The fallback (default 0) Throws CkNumberException — carries Input, TargetType and Reason

You get a uint — it holds 0 to about 4.3 billion.

CkNumber.UInt32("4000000000");   // -> 4000000000  (too big for an int, fine here)

Int64

A big whole number.

long Int64(
    object? input = null,
    bool exact = false,
    long fallback = 0,
    bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
Parameter Type Default What it does
input object? null The value: a number of any type, or text.
exact bool false Rounds a fractional value to the nearest whole number first, halves to the even neighbour; pass true to accept only whole values, so 12.5 answers the fallback.
fallback long 0 What comes back when the value cannot be converted.
throwIfInvalid bool CkSettings.DefaultThrowIfInvalid (false) Pass true to throw a CkNumberException that says why, instead of answering the fallback, when the value cannot be converted.

Returns

When Default With throwIfInvalid: true
The value reads as a number the type holds The converted value: 9000000000 The converted value: 9000000000
Rejected: missing, not a number, outside about ±9.2 quintillion, or a fraction with exact: true The fallback (default 0) Throws CkNumberException — carries Input, TargetType and Reason

You get a long — it holds about ±9.2 quintillion. Pick it when an int might overflow.

CkNumber.Int64("9000000000");   // -> 9000000000  (too big for an int, fine for a long)

UInt64

A big whole number without the minus sign.

ulong UInt64(
    object? input = null,
    bool exact = false,
    ulong fallback = 0,
    bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
Parameter Type Default What it does
input object? null The value: a number of any type, or text.
exact bool false Rounds a fractional value to the nearest whole number first, halves to the even neighbour; pass true to accept only whole values, so 12.5 answers the fallback.
fallback ulong 0 What comes back when the value cannot be converted.
throwIfInvalid bool CkSettings.DefaultThrowIfInvalid (false) Pass true to throw a CkNumberException that says why, instead of answering the fallback, when the value cannot be converted.

Returns

When Default With throwIfInvalid: true
The value reads as a number the type holds The converted value: 42 The converted value: 42
Rejected: missing, not a number, outside 0 to about 18.4 quintillion, or a fraction with exact: true The fallback (default 0) Throws CkNumberException — carries Input, TargetType and Reason

You get a ulong — it holds 0 to about 18.4 quintillion.

CkNumber.UInt64("42");   // -> 42

Int128

A huge whole number.

System.Int128 Int128(
    object? input = null,
    bool exact = false,
    System.Int128 fallback = default,
    bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
Parameter Type Default What it does
input object? null The value: a number of any type, or text.
exact bool false Rounds a fractional value to the nearest whole number first, halves to the even neighbour; pass true to accept only whole values, so 12.5 answers the fallback.
fallback System.Int128 default (0) What comes back when the value cannot be converted.
throwIfInvalid bool CkSettings.DefaultThrowIfInvalid (false) Pass true to throw a CkNumberException that says why, instead of answering the fallback, when the value cannot be converted.

Returns

When Default With throwIfInvalid: true
The value reads as a number the type holds The converted value: 9300000000000000000000 The converted value: 9300000000000000000000
Rejected: missing, not a number, outside about ±1.7 × 10³⁸, or a fraction with exact: true The fallback (default 0) Throws CkNumberException — carries Input, TargetType and Reason

You get a System.Int128 — it holds about ±1.7 × 10³⁸. Works like Int64, just far bigger.

CkNumber.Int128("9300000000000000000000");   // -> 9300000000000000000000  (past long's ceiling)

UInt128

A huge whole number without the minus sign.

System.UInt128 UInt128(
    object? input = null,
    bool exact = false,
    System.UInt128 fallback = default,
    bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
Parameter Type Default What it does
input object? null The value: a number of any type, or text.
exact bool false Rounds a fractional value to the nearest whole number first, halves to the even neighbour; pass true to accept only whole values, so 12.5 answers the fallback.
fallback System.UInt128 default (0) What comes back when the value cannot be converted.
throwIfInvalid bool CkSettings.DefaultThrowIfInvalid (false) Pass true to throw a CkNumberException that says why, instead of answering the fallback, when the value cannot be converted.

Returns

When Default With throwIfInvalid: true
The value reads as a number the type holds The converted value: 42 The converted value: 42
Rejected: missing, not a number, outside 0 to about 3.4 × 10³⁸, or a fraction with exact: true The fallback (default 0) Throws CkNumberException — carries Input, TargetType and Reason

You get a System.UInt128 — it holds 0 to about 3.4 × 10³⁸.

CkNumber.UInt128("-1");   // -> 0     (negative — the fallback)

Single

A compact number with a decimal point.

float Single(
    object? input = null,
    float fallback = 0,
    bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
Parameter Type Default What it does
input object? null The value: a number of any type, or text.
fallback float 0 What comes back when the value cannot be converted.
throwIfInvalid bool CkSettings.DefaultThrowIfInvalid (false) Pass true to throw a CkNumberException that says why, instead of answering the fallback, when the value cannot be converted.

Returns

When Default With throwIfInvalid: true
The value reads as a number the type holds The converted value: 12.5f The converted value: 12.5f
Rejected: missing, not a number (NaN and infinities included), or beyond float's range The fallback (default 0) Throws CkNumberException — carries Input, TargetType and Reason

You get a float — about 7 significant digits.

CkNumber.Single("12.5");   // -> 12.5f

Double

The everyday number with a decimal point.

double Double(
    object? input = null,
    double fallback = 0,
    bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
Parameter Type Default What it does
input object? null The value: a number of any type, or text.
fallback double 0 What comes back when the value cannot be converted.
throwIfInvalid bool CkSettings.DefaultThrowIfInvalid (false) Pass true to throw a CkNumberException that says why, instead of answering the fallback, when the value cannot be converted.

Returns

When Default With throwIfInvalid: true
The value reads as a number the type holds The converted value: 2.5 The converted value: 2.5
Rejected: missing, not a number (NaN and infinities included), or beyond double's range The fallback (default 0) Throws CkNumberException — carries Input, TargetType and Reason

You get a double — about 15 significant digits. This is the type JSON numbers usually arrive as.

CkNumber.Double("2.5");    // -> 2.5

Decimal

The exact number for money.

decimal Decimal(
    object? input = null,
    decimal fallback = 0,
    bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
Parameter Type Default What it does
input object? null The value: a number of any type, or text.
fallback decimal 0 What comes back when the value cannot be converted.
throwIfInvalid bool CkSettings.DefaultThrowIfInvalid (false) Pass true to throw a CkNumberException that says why, instead of answering the fallback, when the value cannot be converted.

Returns

When Default With throwIfInvalid: true
The value reads as a number the type holds The converted value: 12.50m The converted value: 12.50m
Rejected: missing, not a number (NaN and infinities included), or beyond decimal's range The fallback (default 0) Throws CkNumberException — carries Input, TargetType and Reason

You get a decimal — 28 significant digits, exact — right for money.

CkNumber.Decimal("12.50");    // -> 12.50m (trailing zero kept — ready for money)

CkNumber.Decimal(
    input: "1e30",
    fallback: 42
);                         // -> 42m   (beyond decimal's range)

Each conversion has a twin ending in OrNull — Int32OrNull gives an int?, DecimalOrNull a decimal?, and so on for all thirteen. Instead of a stand-in fallback, the twin answers null when there is nothing usable: a missing value (null or DBNull), a value that cannot be read — and, by default, a zero, because in many database columns 0 means "not set". That makes these the right pick for filling a nullable column: the column receives an honest NULL, never a fake number.

When zero is a real value in your data — a count, a balance — switch that off with treatZeroAsMissing: false.

The integer twins take exact too; the Single/Double/Decimal twins do not, for the same reason as above.

These twins pair naturally with CkDictionary when you are pulling values out of a parsed payload, and with CkSentinel.ToNull when you are writing to storage.

ByteOrNull

The OrNull twin of Byte.

byte? ByteOrNull(
    object? input = null,
    bool exact = false,
    bool treatZeroAsMissing = true
)
Parameter Type Default What it does
input object? null The value: a number of any type, or text.
exact bool false Rounds a fractional value to the nearest whole number first, halves to the even neighbour; pass true to accept only whole values, so 12.5 answers null.
treatZeroAsMissing bool true Reads a zero as missing, so it comes back as null too; pass false to keep a zero as the real value it sometimes is.

Returns

When You get
The value converts to a usable number The converted value: 42
Missing (null or DBNull), unreadable — not a number, outside 0 to 255, or a fraction with exact: true — or a zero with treatZeroAsMissing on (the default) null — this method never throws

You get a byte? — Byte's conversion rules, null instead of a fallback.

CkNumber.ByteOrNull("300");   // -> null  (too big for a byte)

SByteOrNull

The OrNull twin of SByte.

sbyte? SByteOrNull(
    object? input = null,
    bool exact = false,
    bool treatZeroAsMissing = true
)
Parameter Type Default What it does
input object? null The value: a number of any type, or text.
exact bool false Rounds a fractional value to the nearest whole number first, halves to the even neighbour; pass true to accept only whole values, so 12.5 answers null.
treatZeroAsMissing bool true Reads a zero as missing, so it comes back as null too; pass false to keep a zero as the real value it sometimes is.

Returns

When You get
The value converts to a usable number The converted value: -100
Missing (null or DBNull), unreadable — not a number, outside -128 to 127, or a fraction with exact: true — or a zero with treatZeroAsMissing on (the default) null — this method never throws

You get an sbyte?.

CkNumber.SByteOrNull(0);   // -> null  (zero reads as "not set" by default)

Int16OrNull

The OrNull twin of Int16.

short? Int16OrNull(
    object? input = null,
    bool exact = false,
    bool treatZeroAsMissing = true
)
Parameter Type Default What it does
input object? null The value: a number of any type, or text.
exact bool false Rounds a fractional value to the nearest whole number first, halves to the even neighbour; pass true to accept only whole values, so 12.5 answers null.
treatZeroAsMissing bool true Reads a zero as missing, so it comes back as null too; pass false to keep a zero as the real value it sometimes is.

Returns

When You get
The value converts to a usable number The converted value: 32000
Missing (null or DBNull), unreadable — not a number, outside -32,768 to 32,767, or a fraction with exact: true — or a zero with treatZeroAsMissing on (the default) null — this method never throws

You get a short?.

CkNumber.Int16OrNull("junk");   // -> null  (unreadable)

UInt16OrNull

The OrNull twin of UInt16.

ushort? UInt16OrNull(
    object? input = null,
    bool exact = false,
    bool treatZeroAsMissing = true
)
Parameter Type Default What it does
input object? null The value: a number of any type, or text.
exact bool false Rounds a fractional value to the nearest whole number first, halves to the even neighbour; pass true to accept only whole values, so 12.5 answers null.
treatZeroAsMissing bool true Reads a zero as missing, so it comes back as null too; pass false to keep a zero as the real value it sometimes is.

Returns

When You get
The value converts to a usable number The converted value: 65535
Missing (null or DBNull), unreadable — not a number, outside 0 to 65,535, or a fraction with exact: true — or a zero with treatZeroAsMissing on (the default) null — this method never throws

You get a ushort?.

CkNumber.UInt16OrNull(null);   // -> null  (missing stays missing)

Int32OrNull

The OrNull twin of Int32.

int? Int32OrNull(
    object? input = null,
    bool exact = false,
    bool treatZeroAsMissing = true
)
Parameter Type Default What it does
input object? null The value: a number of any type, or text.
exact bool false Rounds a fractional value to the nearest whole number first, halves to the even neighbour; pass true to accept only whole values, so 12.5 answers null.
treatZeroAsMissing bool true Reads a zero as missing, so it comes back as null too; pass false to keep a zero as the real value it sometimes is.

Returns

When You get
The value converts to a usable number The converted value: 42
Missing (null or DBNull), unreadable — not a number, outside about ±2.1 billion, or a fraction with exact: true — or a zero with treatZeroAsMissing on (the default) null — this method never throws

You get an int? — the twin you will reach for most when filling nullable columns.

CkNumber.Int32OrNull("42");     // -> 42
CkNumber.Int32OrNull(0);        // -> null  (zero reads as "not set" by default)
CkNumber.Int32OrNull("junk");   // -> null  (unreadable)
CkNumber.Int32OrNull("0.4");    // -> null  (rounds to 0 first — then zero means missing)

CkNumber.Int32OrNull(
    input: 0,
    treatZeroAsMissing: false
);                              // -> 0     (a real zero, kept on request)

UInt32OrNull

The OrNull twin of UInt32.

uint? UInt32OrNull(
    object? input = null,
    bool exact = false,
    bool treatZeroAsMissing = true
)
Parameter Type Default What it does
input object? null The value: a number of any type, or text.
exact bool false Rounds a fractional value to the nearest whole number first, halves to the even neighbour; pass true to accept only whole values, so 12.5 answers null.
treatZeroAsMissing bool true Reads a zero as missing, so it comes back as null too; pass false to keep a zero as the real value it sometimes is.

Returns

When You get
The value converts to a usable number The converted value: 4000000000
Missing (null or DBNull), unreadable — not a number, outside 0 to about 4.3 billion, or a fraction with exact: true — or a zero with treatZeroAsMissing on (the default) null — this method never throws

You get a uint?.

CkNumber.UInt32OrNull("-1");   // -> null  (negative — nothing usable)

Int64OrNull

The OrNull twin of Int64.

long? Int64OrNull(
    object? input = null,
    bool exact = false,
    bool treatZeroAsMissing = true
)
Parameter Type Default What it does
input object? null The value: a number of any type, or text.
exact bool false Rounds a fractional value to the nearest whole number first, halves to the even neighbour; pass true to accept only whole values, so 12.5 answers null.
treatZeroAsMissing bool true Reads a zero as missing, so it comes back as null too; pass false to keep a zero as the real value it sometimes is.

Returns

When You get
The value converts to a usable number The converted value: 9000000000
Missing (null or DBNull), unreadable — not a number, outside about ±9.2 quintillion, or a fraction with exact: true — or a zero with treatZeroAsMissing on (the default) null — this method never throws

You get a long?.

CkNumber.Int64OrNull(DBNull.Value);   // -> null  (a database NULL is missing too)

UInt64OrNull

The OrNull twin of UInt64.

ulong? UInt64OrNull(
    object? input = null,
    bool exact = false,
    bool treatZeroAsMissing = true
)
Parameter Type Default What it does
input object? null The value: a number of any type, or text.
exact bool false Rounds a fractional value to the nearest whole number first, halves to the even neighbour; pass true to accept only whole values, so 12.5 answers null.
treatZeroAsMissing bool true Reads a zero as missing, so it comes back as null too; pass false to keep a zero as the real value it sometimes is.

Returns

When You get
The value converts to a usable number The converted value: 42
Missing (null or DBNull), unreadable — not a number, outside 0 to about 18.4 quintillion, or a fraction with exact: true — or a zero with treatZeroAsMissing on (the default) null — this method never throws

You get a ulong?.

CkNumber.UInt64OrNull("42");   // -> 42

Int128OrNull

The OrNull twin of Int128.

System.Int128? Int128OrNull(
    object? input = null,
    bool exact = false,
    bool treatZeroAsMissing = true
)
Parameter Type Default What it does
input object? null The value: a number of any type, or text.
exact bool false Rounds a fractional value to the nearest whole number first, halves to the even neighbour; pass true to accept only whole values, so 12.5 answers null.
treatZeroAsMissing bool true Reads a zero as missing, so it comes back as null too; pass false to keep a zero as the real value it sometimes is.

Returns

When You get
The value converts to a usable number The converted value: 9300000000000000000000
Missing (null or DBNull), unreadable — not a number, outside about ±1.7 × 10³⁸, or a fraction with exact: true — or a zero with treatZeroAsMissing on (the default) null — this method never throws

You get a System.Int128?.

CkNumber.Int128OrNull("9300000000000000000000");   // -> 9300000000000000000000

UInt128OrNull

The OrNull twin of UInt128.

System.UInt128? UInt128OrNull(
    object? input = null,
    bool exact = false,
    bool treatZeroAsMissing = true
)
Parameter Type Default What it does
input object? null The value: a number of any type, or text.
exact bool false Rounds a fractional value to the nearest whole number first, halves to the even neighbour; pass true to accept only whole values, so 12.5 answers null.
treatZeroAsMissing bool true Reads a zero as missing, so it comes back as null too; pass false to keep a zero as the real value it sometimes is.

Returns

When You get
The value converts to a usable number The converted value: 42
Missing (null or DBNull), unreadable — not a number, outside 0 to about 3.4 × 10³⁸, or a fraction with exact: true — or a zero with treatZeroAsMissing on (the default) null — this method never throws

You get a System.UInt128?.

CkNumber.UInt128OrNull(0);   // -> null  (zero reads as "not set" by default)

SingleOrNull

The OrNull twin of Single.

float? SingleOrNull(
    object? input = null,
    bool treatZeroAsMissing = true
)
Parameter Type Default What it does
input object? null The value: a number of any type, or text.
treatZeroAsMissing bool true Reads a zero as missing, so it comes back as null too; pass false to keep a zero as the real value it sometimes is.

Returns

When You get
The value converts to a usable number The converted value: 12.5f
Missing (null or DBNull), unreadable — not a number (NaN and infinities included), or beyond float's range — or a zero with treatZeroAsMissing on (the default) null — this method never throws

You get a float?.

CkNumber.SingleOrNull("12.5");   // -> 12.5f

DoubleOrNull

The OrNull twin of Double.

double? DoubleOrNull(
    object? input = null,
    bool treatZeroAsMissing = true
)
Parameter Type Default What it does
input object? null The value: a number of any type, or text.
treatZeroAsMissing bool true Reads a zero as missing, so it comes back as null too; pass false to keep a zero as the real value it sometimes is.

Returns

When You get
The value converts to a usable number The converted value: 2.5
Missing (null or DBNull), unreadable — not a number (NaN and infinities included), or beyond double's range — or a zero with treatZeroAsMissing on (the default) null — this method never throws

You get a double?.

CkNumber.DoubleOrNull(0.0);   // -> null  (zero reads as "not set" by default)

DecimalOrNull

The OrNull twin of Decimal.

decimal? DecimalOrNull(
    object? input = null,
    bool treatZeroAsMissing = true
)
Parameter Type Default What it does
input object? null The value: a number of any type, or text.
treatZeroAsMissing bool true Reads a zero as missing, so it comes back as null too; pass false to keep a zero as the real value it sometimes is.

Returns

When You get
The value converts to a usable number The converted value: 19.99m
Missing (null or DBNull), unreadable — not a number (NaN and infinities included), or beyond decimal's range — or a zero with treatZeroAsMissing on (the default) null — this method never throws

You get a decimal? — the right shape for a nullable money or rate column.

CkNumber.DecimalOrNull("19.99");   // -> 19.99m

Percent

A percentage as the rate it stands for.

decimal Percent(
    object? input = null,
    decimal fallback = 0,
    bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
Parameter Type Default What it does
input object? null The value: text with or without a trailing %, or a number.
fallback decimal 0 What comes back when the value cannot be read.
throwIfInvalid bool CkSettings.DefaultThrowIfInvalid (false) Pass true to throw a CkNumberException that says why, instead of answering the fallback, when the value cannot be read.

Returns

When Default With throwIfInvalid: true
The value reads as a percentage or a bare rate The rate: 0.15m The rate: 0.15m
Rejected: missing, or unreadable — grouping, currency symbols, junk The fallback (default 0) Throws CkNumberException — carries Input, TargetType and Reason

Percent reads a percentage and answers the rate — the number you would multiply by. A trailing % (with or without a space before it) divides the value by 100. A bare value — no % sign — is taken as the rate it already is, untouched.

CkNumber.Percent("15%");     // -> 0.15m
CkNumber.Percent("2.5 %");   // -> 0.025m
CkNumber.Percent("0.15");    // -> 0.15m  (bare values are rates already)
CkNumber.Percent(15);        // -> 15m    (bare 15 is the rate 15 — that is 1,500%!)

CkNumber.Percent(
    input: "junk",
    fallback: -1
);                           // -> -1m

That last pair is the big gotcha: CkNumber never guesses which scale a bare number meant. 0.15 and 15 are both perfectly honest rates, so rescaling one by guess would corrupt the other. If your form posts whole percent numbers without the sign, send the % along or divide by 100 upstream.

PercentOrNull

The OrNull twin of Percent.

decimal? PercentOrNull(
    object? input = null,
    bool treatZeroAsMissing = true
)
Parameter Type Default What it does
input object? null The value: text with or without a trailing %, or a number.
treatZeroAsMissing bool true Reads a zero rate as missing, so it comes back as null too; pass false to keep a real 0%.

Returns

When You get
The value reads as a usable rate The rate: 0.15m
Missing (null or DBNull), unreadable, or a zero rate with treatZeroAsMissing on (the default) null — this method never throws

PercentOrNull is the family's OrNull twin: null for a missing value, an unreadable one, or — by default — a zero rate (treatZeroAsMissing: false keeps a real 0%).

CkNumber.PercentOrNull("0%");   // -> null  (0% reads as "not set" by default)

IsNumber

Does it read as a number at all?

bool IsNumber(
    object? input = null,
    bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
Parameter Type Default What it does
input object? null The value: a number of any type, or plain invariant text.
throwIfInvalid bool CkSettings.DefaultThrowIfInvalid (false) Pass true to throw a CkNumberException that says why, instead of answering false, when the value is not a number.

Returns

When Default With throwIfInvalid: true
A boxed number of any type, or plain invariant text such as "42", "-1.5", "4.2e1" true true
Rejected: not a number — missing, grouping or a currency symbol, a boolean, NaN false Throws CkNumberException — carries Input, TargetType and Reason

The yes-or-no form of the readers, under their rules: "1,234" and "$5" are not numbers here, that is CkMoney's job; booleans, chars, dates, NaN and the infinities are not numbers either. Zero is a number here, whatever the OrNull family says about it. When the value is going to be stored, read it with the typed OrNull reader instead and check for null.

CkNumber.IsNumber("4.2e1");   // -> true
CkNumber.IsNumber("1,234");   // -> false  (grouping — CkMoney's job)
CkNumber.IsNumber(true);      // -> false

IsInteger

A whole number?

bool IsInteger(
    object? input = null,
    bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
Parameter Type Default What it does
input object? null The value: a number of any type, or plain invariant text.
throwIfInvalid bool CkSettings.DefaultThrowIfInvalid (false) Pass true to throw a CkNumberException that says why, instead of answering false, when the value is not a number.

Returns

When Default With throwIfInvalid: true
A number with no fractional part: 12, "12.0", "1e3" true true
A number with a fraction: "12.5", 0.1 false false
Rejected: not a number — missing, grouping or a currency symbol, a boolean, NaN false Throws CkNumberException — carries Input, TargetType and Reason

The exact: true rule of the integer readers, as a verdict, without choosing a width — a value too large for Int128 is still whole. HasDecimals with its default of 0 asks the same question.

CkNumber.IsInteger("12.0");   // -> true
CkNumber.IsInteger("12.5");   // -> false

IsPositive

Above zero?

bool IsPositive(
    object? input = null,
    bool allowZero = false,
    bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
Parameter Type Default What it does
input object? null The value: a number of any type, or plain invariant text.
allowZero bool false Count zero as well.
throwIfInvalid bool CkSettings.DefaultThrowIfInvalid (false) Pass true to throw a CkNumberException that says why, instead of answering false, when the value is not a number.

Returns

When Default With throwIfInvalid: true
A number above zero — or zero itself with allowZero true true
Zero (by default), or a number below it false false
Rejected: not a number — missing, grouping or a currency symbol, a boolean, NaN false Throws CkNumberException — carries Input, TargetType and Reason

Strict by default: "quantity must be positive". With allowZero: true it is "must not be negative": a balance, a count that may be empty.

CkNumber.IsPositive("0.5");                    // -> true
CkNumber.IsPositive(0);                        // -> false
CkNumber.IsPositive(0, allowZero: true);       // -> true

IsNegative

Below zero?

bool IsNegative(
    object? input = null,
    bool allowZero = false,
    bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
Parameter Type Default What it does
input object? null The value: a number of any type, or plain invariant text.
allowZero bool false Count zero as well.
throwIfInvalid bool CkSettings.DefaultThrowIfInvalid (false) Pass true to throw a CkNumberException that says why, instead of answering false, when the value is not a number.

Returns

When Default With throwIfInvalid: true
A number below zero — or zero itself with allowZero true true
Zero (by default), or a number above it false false
Rejected: not a number — missing, grouping or a currency symbol, a boolean, NaN false Throws CkNumberException — carries Input, TargetType and Reason

The mirror of IsPositive. IsNegative, IsZero and IsPositive partition the numbers: exactly one of them is true for any value that reads.

CkNumber.IsNegative("-0.5");   // -> true
CkNumber.IsNegative(0);        // -> false

IsZero

Exactly zero?

bool IsZero(
    object? input = null,
    bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
Parameter Type Default What it does
input object? null The value: a number of any type, or plain invariant text.
throwIfInvalid bool CkSettings.DefaultThrowIfInvalid (false) Pass true to throw a CkNumberException that says why, instead of answering false, when the value is not a number.

Returns

When Default With throwIfInvalid: true
A number equal to zero: 0, "0.0", "-0" true true
Any other number false false
Rejected: not a number — missing, grouping or a currency symbol, a boolean, NaN false Throws CkNumberException — carries Input, TargetType and Reason

The OrNull readers call zero "missing" by default; this says so out loud, for data-quality checks and for telling "nobody set it" from "not a number" — the same job CkUuid.IsNil and CkDate.IsPlaceholder do. Note that Int32OrNull("0.4") is null because 0.4 rounds to a missing 0, while IsZero("0.4") is false: it is not zero.

CkNumber.IsZero("0.0");    // -> true
CkNumber.IsZero("junk");   // -> false  (not a number, not zero)

IsBetween

Inside a range?

bool IsBetween(
    object? input = null,
    object? min = null,
    object? max = null,
    bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
Parameter Type Default What it does
input object? null The value: a number of any type, or plain invariant text.
min object? null The lower bound, inclusive, read the same way. null means no lower bound.
max object? null The upper bound, inclusive, read the same way. null means no upper bound.
throwIfInvalid bool CkSettings.DefaultThrowIfInvalid (false) Pass true to throw a CkNumberException that says which value is not a number 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 CkNumberException — carries Input, TargetType and Reason

IsBetween(age, 0, 120). Bounds are read the same way as the value, so "100" and 100 both work, and a null bound is open: IsBetween(qty, 1, null) is "at least one". Comparison is exact, in decimal; only a value beyond its range compares as a double.

CkNumber.IsBetween("5", 1, 10);        // -> true
CkNumber.IsBetween(10, 1, 10);         // -> true   (both ends count)
CkNumber.IsBetween("0.15", 0.1, 0.2);  // -> true   (exact decimals)
CkNumber.IsBetween(5, 10, 1);          // -> false  (contradictory bounds)

IsAtLeast

At least this much?

bool IsAtLeast(
    object? input = null,
    object? min = null,
    bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
Parameter Type Default What it does
input object? null The value: a number of any type, or plain invariant text.
min object? null The lower bound, inclusive, read the same way.
throwIfInvalid bool CkSettings.DefaultThrowIfInvalid (false) Pass true to throw a CkNumberException that says why, instead of answering false, when either value is not a number.

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 a number false Throws CkNumberException — carries Input, TargetType and Reason

IsAtLeast(qty, 1), IsAtLeast(age, 18). The bound counts. The same as IsBetween with an open upper bound, spelled the way validation code reads.

CkNumber.IsAtLeast(5, 5);       // -> true
CkNumber.IsAtLeast("4.5", 5);   // -> false

IsAtMost

At most this much?

bool IsAtMost(
    object? input = null,
    object? max = null,
    bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
Parameter Type Default What it does
input object? null The value: a number of any type, or plain invariant text.
max object? null The upper bound, inclusive, read the same way.
throwIfInvalid bool CkSettings.DefaultThrowIfInvalid (false) Pass true to throw a CkNumberException that says why, instead of answering false, when either value is not a number.

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 a number false Throws CkNumberException — carries Input, TargetType and Reason

IsAtMost(items, 100), IsAtMost(rate, 1). The bound counts. The mirror of IsAtLeast.

CkNumber.IsAtMost(100, 100);       // -> true
CkNumber.IsAtMost("100.01", 100);  // -> false

HasDecimals

No more than so many decimal places?

bool HasDecimals(
    object? input = null,
    int atMost = 0,
    bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
Parameter Type Default What it does
input object? null The value: a number of any type, or plain invariant text.
atMost int 0 The most decimal places allowed. Below 0 acts as 0.
throwIfInvalid bool CkSettings.DefaultThrowIfInvalid (false) Pass true to throw a CkNumberException that says why, instead of answering false, when the value is not a number.

Returns

When Default With throwIfInvalid: true
A number with at most atMost decimal places once trailing zeros are dropped true true
A number that needs more false false
Rejected: not a number — missing, grouping or a currency symbol, a boolean, NaN false Throws CkNumberException — carries Input, TargetType and Reason

Judged on the value, not its spelling: "12.50" has one decimal place, "12.00" has none, 12.345 has three. For quantities and rates with a precision limit — "no more than two decimals". With the default of 0 it is IsInteger.

CkNumber.HasDecimals("12.50", 1);   // -> true
CkNumber.HasDecimals("12.50", 0);   // -> false
CkNumber.HasDecimals("12.345", 2);  // -> false

IsEven

An even whole number?

bool IsEven(
    object? input = null,
    bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
Parameter Type Default What it does
input object? null The value: a number of any type, or plain invariant text.
throwIfInvalid bool CkSettings.DefaultThrowIfInvalid (false) Pass true to throw a CkNumberException that says why, instead of answering false, when the value is not a number.

Returns

When Default With throwIfInvalid: true
A whole, even number: 2, "4", "10.0", 0 true true
An odd number, or one with a fraction, which has no parity false false
Rejected: not a number — missing, grouping or a currency symbol, a boolean, NaN false Throws CkNumberException — carries Input, TargetType and Reason

IsOdd is the twin. A whole number is one or the other; a fraction is neither, and so is anything that is not a number — so neither method is the other's negation.

CkNumber.IsEven("10.0");   // -> true
CkNumber.IsEven("2.5");    // -> false  (no parity)

IsOdd

An odd whole number?

bool IsOdd(
    object? input = null,
    bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
Parameter Type Default What it does
input object? null The value: a number of any type, or plain invariant text.
throwIfInvalid bool CkSettings.DefaultThrowIfInvalid (false) Pass true to throw a CkNumberException that says why, instead of answering false, when the value is not a number.

Returns

When Default With throwIfInvalid: true
A whole, odd number: 3, "-3", "7.0" true true
An even number, or one with a fraction false false
Rejected: not a number — missing, grouping or a currency symbol, a boolean, NaN false Throws CkNumberException — carries Input, TargetType and Reason

The twin of IsEven, with the same rules.

CkNumber.IsOdd("-3");   // -> true
CkNumber.IsOdd(0);      // -> false

Fits

Does it convert to this type without loss?

bool Fits<T>(
    object? input = null,
    bool exact = true,
    bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
    where T : struct, INumber<T>, IMinMaxValue<T>
Parameter Type Default What it does
T type parameter — The numeric type to test against: int, decimal, byte …
input object? null The value: a number of any type, or plain invariant text.
exact bool true Refuse a fractional value for an integer type. Pass false to round it first, as the readers do.
throwIfInvalid bool CkSettings.DefaultThrowIfInvalid (false) Pass true to throw a CkNumberException that says why, instead of answering false, when the value does not fit.

Returns

When Default With throwIfInvalid: true
The value converts to T under the typed reader's rules true true
Rejected: out of the type's range, a fraction for an integer type, or not a number false Throws CkNumberException — carries Input, TargetType and Reason — with TargetType set to T

One generic check in place of a twin for each of the thirteen readers: Fits<byte>("255") is true, Fits<byte>("300") and Fits<uint>(-1) are false. Unlike the readers it refuses a fraction for an integer type by default, since "fits" means without loss; Fits<int>("12.5", exact: false) accepts what Int32 would round.

CkNumber.Fits<byte>("300");                 // -> false  (outside 0 to 255)
CkNumber.Fits<int>("12.5");                 // -> false  (a fraction)
CkNumber.Fits<int>("12.5", exact: false);   // -> true   (rounds to 12)
CkNumber.Fits<double>("1e30");              // -> true
CkNumber.Fits<decimal>("1e30");             // -> false

Clamp

Read it, and keep it within bounds.

T Clamp<T>(
    object? input = null,
    T min = default,
    T max = default,
    T? fallback = null,
    bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
    where T : struct, INumber<T>, IMinMaxValue<T>
Parameter Type Default What it does
T type parameter inferred from the bounds Clamp(x, 1, 100) answers an int, Clamp(x, 0.0, 1.0) a double.
input object? null The value: a number of any type, or plain invariant text.
min T — The smallest value allowed.
max T — The largest value allowed.
fallback T? null What comes back when the value cannot be read, or the bounds contradict each other; the lower bound when not given.
throwIfInvalid bool CkSettings.DefaultThrowIfInvalid (false) Pass true to throw a CkNumberException that says why, instead of answering the fallback, when the value cannot be read or the bounds contradict each other.

Returns

When Default With throwIfInvalid: true
The value reads and lies inside the bounds The value, an integer type rounding a fraction as Int32 does The value
It reads and lies outside The nearer bound The nearer bound
Rejected: not a number, bounds that contradict each other, or a bound that is NaN The fallback, the lower bound by default Throws CkNumberException — carries Input, Expected and Reason

Page sizes, percentages, retry counts — anything a caller may set but not set wildly. The value is read as widely as it can be before the bounds apply, so "300" still clamps to a byte's 255.

CkNumber.Clamp("250", 1, 100);                    // -> 100
CkNumber.Clamp(bag["pageSize"], 1, 100);                 // -> 1 when the field is junk: the lower bound
CkNumber.Clamp(bag["pageSize"], 1, 100, fallback: 20);   // -> 20 when the field is junk
CkNumber.Clamp("0.75", 0.0, 1.0);                 // -> 0.75

Round

Read it, and round it the stated way.

decimal Round(
    object? input = null,
    int decimals = 0,
    MidpointRounding rounding = CkSettings.NumberRounding,
    decimal fallback = 0,
    bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
Parameter Type Default What it does
input object? null The value: a number of any type, or text.
decimals int 0 The places to keep, 0 to 28; outside acts as the nearer end.
rounding MidpointRounding CkSettings.NumberRounding (ToEven) How a half rounds. AwayFromZero is the schoolbook rule.
fallback decimal 0 What comes back when the value cannot be read.
throwIfInvalid bool CkSettings.DefaultThrowIfInvalid (false) Pass true to throw a CkNumberException that says why, instead of answering the fallback, when the value cannot be read.

Returns

When Default With throwIfInvalid: true
The value reads The rounded value, exact in decimal The rounded value
Rejected: not a number, or beyond the decimal range The fallback Throws CkNumberException — carries Input, Expected and Reason

Halves go to the even neighbour by default — Round(2.5) is 2, Round(3.5) is 4 — the same rule the integer readers apply, which is also the framework's default and the one that surprises everyone once. Pass MidpointRounding.AwayFromZero for 2.5 to become 3; CkMoney.Round uses that rule for money.

CkNumber.Round("2.345", 2);                                     // -> 2.34m
CkNumber.Round(2.5);                                            // -> 2m   (halves to even)
CkNumber.Round(2.5, rounding: MidpointRounding.AwayFromZero);   // -> 3m

Divide

A quotient that cannot crash.

decimal Divide(
    object? numerator = null,
    object? denominator = null,
    decimal fallback = 0,
    bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
Parameter Type Default What it does
numerator object? null The value to divide: a number of any type, or text.
denominator object? null The value to divide by, read the same way.
fallback decimal 0 What comes back when a value cannot be read, the denominator is zero, or the quotient is out of range.
throwIfInvalid bool CkSettings.DefaultThrowIfInvalid (false) Pass true to throw a CkNumberException that says why, instead of answering the fallback, when the division cannot be done.

Returns

When Default With throwIfInvalid: true
Both read and the denominator is not zero The exact quotient The quotient
Rejected: a value that is not a number, a zero denominator, an out-of-range quotient The fallback Throws CkNumberException — carries Input, Expected and Reason — "division by zero" names the denominator

Divide(sold, stocked) for a ratio, Divide(total, count) for an average. Division by zero is the commonest crash in reporting code; here it is the fallback, or a reason.

CkNumber.Divide(1, 4);                    // -> 0.25m
CkNumber.Divide("10", "3");               // -> 3.3333333333333333333333333333m
CkNumber.Divide(5, 0, fallback: -1);      // -> -1m

Explain

Why not — in words, without an exception.

string Explain(
    object? input = null
)
Parameter Type Default What it does
input object? null The value: a number of any type, or plain invariant text.

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.

CkNumber.Explain("42");       // -> ""
CkNumber.Explain("1,234");    // -> "not a number"
CkNumber.Explain(null);       // -> "no value"

The fallback

Every conversion method takes a fallback — the value that comes back when the input cannot be read. It is already the right type, so it comes back exactly as you gave it, and it defaults to 0. Pick a fallback that your code can tell apart from a real value when that matters.

CkNumber.Int32(
    input: "not a number",
    fallback: -1
);   // -> -1

The OrNull twins take no fallback — their answer for anything unusable is null — and neither do the validators, whose answer is false.

Good to know

  • Beyond the decimal range, integers still compare by their digits. A 128-bit value or a run of digits past 7.9e28 keeps its exact digits for the comparisons; a double past the range, or below 1e-28, compares as the double it is. Such a double is out of the decimal range for Decimal and its OrNull twin, not zero.
  • Grouping and currency symbols are rejected. "1,234" and "$5" answer the fallback. Text dressed as money — symbols, thousands separators, accounting negatives — belongs to CkMoney.
  • Halves round to the even neighbour. 12.5 becomes 12 and 13.5 becomes 14 — banker's rounding, so long runs of .5 values do not all drift upward. Pass exact: true when a fraction should be refused instead.
  • Int32OrNull("0.4") is null. The fraction rounds to 0 first, and the OrNull family reads zero as missing by default. Pass treatZeroAsMissing: false when small values must survive as 0.
  • A bare percent is a rate as it stands. Percent(15) is 15m — that is 1,500%, not 15%. Only a trailing % divides by 100. Send the sign or divide upstream.
  • NaN and infinities are not numbers here. Neither are booleans, chars or dates — they all answer the fallback (or null from the twins).
  • Reading is culture-invariant. The decimal point is always a dot, whatever the server's regional settings — the same text reads the same everywhere.
  • A 0 result can mean two things — a real zero, or a rejected value falling back to the default. When you need to know which, use the OrNull twin, pick a fallback you can recognise, or ask for an exception.
  • The validators treat zero as a number. IsNumber("0"), IsInteger(0) and IsBetween(0, 0, 10) are all true, whatever the OrNull family says about zero; IsZero is the one that asks about it. All of them answer false for anything unreadable, and every one takes throwIfInvalid.
  • Comparisons are exact. IsBetween, IsAtLeast and IsAtMost compare as decimal; only a value beyond its range, past about 7.9 × 10²⁸, compares as a double. HasDecimals counts on the value, so "12.50" has one decimal place.

If you want errors instead

Pass throwIfInvalid: true and a value that cannot be converted raises a CkNumberException (from SafeCoder.CommonKit.Exceptions) instead of answering the fallback. The exception carries the Input, the TargetType and a short Reason, flattened into a one-line message.

CkNumber.Int32(
    input: "twelve",
    throwIfInvalid: true
);   // throws CkNumberException — the message says why

The OrNull twins have no such switch — they never throw: null is their answer for anything unusable. When you want an error instead, use the plain sibling with throwIfInvalid: true.