Math functions
| C# | SQL | Notes |
|---|---|---|
Math.Abs(x) |
abs(x) |
|
Math.Round(x) |
round(x) / round(x, 0) |
SQL Server supplies the required length argument. |
Math.Round(x, digits) |
round(x, digits) |
PostgreSQL casts a double/float first argument to numeric (round((x)::numeric, digits)), because it has no round(double precision, integer). |
Math.Truncate(x) |
trunc(x) / round(x, 0, 1) |
SQL Server has no trunc. |
Math.Log(x) |
natural logarithm: ln(x) (SQLite, PostgreSQL) / log(x) (SQL Server) |
Single-argument form only. |
Math.Log10(x) |
log10(x) |
|
Math.Acos(x) / Math.Asin(x) / Math.Atan(x) |
acos(x) / asin(x) / atan(x) |
|
Math.Atan2(y, x) |
atan2(y, x) (SQL Server: atn2(y, x)) |
var values = dataContext.From<IComplexEntity>()
.Select(e => Math.Abs(e.Id - 5))
.ToList();
// ids 1, 2, 3 -> 4, 3, 2
select abs((id - 5))
from complex_entity
The Output: tables below show the rows returned by each example against the integration-test seed data (tests/nextorm.integration.tests/Providers/TestProviders/SqliteTestProvider.cs).
Output:
| Abs |
|---|
| 4 |
| 3 |
| 2 |
Portable trigonometric and angle functions
Besides the Math.* translation above, SqlFunctions.Sql exposes
the angle functions that have no BCL member. They render natively where the provider has them (gated
per name by IScalarFunctions.Supports):
| C# | SQL | Notes |
|---|---|---|
SqlFunctions.Sql.cot(x) |
cot(x) |
PostgreSQL, SQL Server and MySQL/MariaDB; ClickHouse and SQLite have no cot and reject it. |
SqlFunctions.Sql.degrees(x) |
degrees(x) |
All providers (SQL Server casts the argument to float). |
SqlFunctions.Sql.radians(x) |
radians(x) |
All providers (SQL Server casts the argument to float). |
SqlFunctions.Sql.pi() |
pi() |
All providers. |
Math.Acos/Asin/Atan/Atan2 need no wrapper — they translate directly, with SQL Server emitting
its atn2 spelling for the two-argument form.
var rows = dataContext.From<IComplexEntity>()
.Select(e => new
{
Angle = SqlFunctions.Sql.degrees(SqlFunctions.Sql.pi()),
Cot = SqlFunctions.Sql.cot(1.0)
})
.ToList();
SQL Server bit functions (2022+)
SqlFunctions.SqlServer exposes the five T-SQL bit functions, each as one overload set per operand type
(byte?/short?/int?/long?), and every call renders the native function behind a typed-NULL guard:
| C# | SQL Server | Result |
|---|---|---|
left_shift(value, shift) / right_shift(value, shift) |
LEFT_SHIFT / RIGHT_SHIFT (logical, zero-fill) |
the operand's own type: byte?/short?/int?/long? |
bit_count(value) |
BIT_COUNT |
long? (bigint) |
get_bit(value, offset) |
GET_BIT |
bool? (bit) |
set_bit(value, offset) |
SET_BIT, native two-argument form (bit set to 1) |
the operand's own type |
set_bit(value, offset, bitValue) |
SET_BIT, three-argument form |
the operand's own type |
var rows = dataContext.From<IComplexEntity>()
.Select(e => new
{
Shifted = SqlFunctions.SqlServer.left_shift((int?)1, 2), // 4
Negative = SqlFunctions.SqlServer.left_shift((int?)-8, 1), // -16
RightShifted = SqlFunctions.SqlServer.right_shift((int?)-8, 1), // 2147483644 (logical shift)
Bits = SqlFunctions.SqlServer.bit_count((byte?)5), // 2 (long)
Bit = SqlFunctions.SqlServer.get_bit((byte?)5, 1), // true (bool)
Set = SqlFunctions.SqlServer.set_bit((short?)5, 1), // 7
Cleared = SqlFunctions.SqlServer.set_bit((short?)5, 1, false), // 5
NullOperand = SqlFunctions.SqlServer.left_shift((byte?)null, 1) // null
})
.ToList();
CASE WHEN 1 IS NULL OR 2 IS NULL THEN CAST(NULL AS int)
ELSE left_shift(cast(1 as int), cast(2 as bigint)) END
The operand decides the SQL type — byte→tinyint, short→smallint, int→int, long→bigint —
the shift amount is cast to bigint, the bit offset to int, and the three-argument SET_BIT value to
bit. bit_count is bigint on the engine, so its CLR shape is long?; get_bit is bit, so it is
bool?. right_shift is the engine's logical (zero-fill) shift, not an arithmetic one:
right_shift(-8, 1) is 2147483644, not -4.
A bare left_shift(NULL, …) is rejected by the server with Msg 8116 ("Argument data type NULL is
invalid"), so the renderer types every argument and guards a NULL operand, shift, offset or bit value with a
typed NULL of the result type — a NULL never reaches the native call.
The whole family is SQL Server 2022 (major version 16) or later, gated by the version-only
SupportsSqlServer2022Functions flag. A
context declared for 15 or an unversioned context refuses every name before SQL, for example The SET_BIT function requires SQL Server major version 16 or later; declared major version: 15. (or … unspecified.).
Overload resolution. Because the family is one overload set per operand type, an untyped constant binds to the smallest overload that can hold it:
left_shift(1, 2)selectsbyte?and renderstinyint, andleft_shift(-8, 1)selectsshort?. The rendered SQL type therefore follows the compiler's pick, not the literal's intent — cast the operand explicitly (left_shift((int?)1, 2)) whenever a specific SQL Server type is required.
For MariaDB numeric, bit and vector functions, including the MariaDB 11.7 vector gates, see the MariaDB catalogue.
ClickHouse-only math
Beyond the portable SqlFunctions.Sql surface,
SqlFunctions.ClickHouse exposes four ClickHouse-only math
functions. They are not part of the cross-provider Math.*/BCL translation and were not added to
CommonFunctions; every other provider (PostgreSQL, SQL Server,
MySQL, MariaDB, SQLite) and the in-memory provider rejects a call with NotSupportedException instead of
emulating it.
| C# | SQL | Result |
|---|---|---|
SqlFunctions.ClickHouse.log2(x) / log2(x) |
log2(x) |
Base-2 logarithm. |
SqlFunctions.ClickHouse.cbrt(x) / cbrt(x) |
cbrt(x) |
Cube root. |
SqlFunctions.ClickHouse.exp2(x) / exp2(x) |
exp2(x) |
Two raised to the power x. |
SqlFunctions.ClickHouse.exp10(x) / exp10(x) |
exp10(x) |
Ten raised to the power x. |
Each function has two overloads, double f(double x) and double? f(double? x). ClickHouse returns
Float64 for the non-nullable form and Nullable(Float64) for the nullable one; a NULL argument
yields NULL, never zero. On the pinned 25.8 baseline log2(8) = 3, cbrt(8) = 2, exp2(3) = 8 and
exp10(2) = 100.
var rows = dataContext.From<IComplexEntity>()
.Select(e => new
{
L2 = SqlFunctions.ClickHouse.log2((double)e.Id),
Cbrt = SqlFunctions.ClickHouse.cbrt((double)e.Id),
Exp2 = SqlFunctions.ClickHouse.exp2((double)e.Id),
E10 = SqlFunctions.ClickHouse.exp10((double)e.Id),
NullableL2 = SqlFunctions.ClickHouse.log2(e.Double)
})
.ToList();
Delivered numeric, rounding and bit functions (#286, T286b)
The remaining ClickHouse-only numeric families are typed members too. is_finite/is_infinite/is_nan
return bool, the integer forms keep the vendor width (int_div/int_exp2/int_exp10 are UInt64 →
ulong), the rounding family returns the argument's integer width, and the bit family declares
UInt64-widened arithmetic (bitAnd/bitOr/bitXor/bit_count/bit_hamming_distance → ulong) with
the width-preserving shifts/rotations and the bitTest* booleans. Each has a NULL-propagating
overload where the vendor accepts Nullable.
| Function | SQL | Result |
|---|---|---|
SqlFunctions.ClickHouse.erf(x) / erfc(x) |
erf(x) / erfc(x) |
double (Nullable for double?) |
SqlFunctions.ClickHouse.lgamma(x) / tgamma(x) |
lgamma(x) / tgamma(x) |
double |
SqlFunctions.ClickHouse.gcd(a, b) / lcm(a, b) |
gcd(a, b) / lcm(a, b) |
ulong |
SqlFunctions.ClickHouse.is_finite(x) / is_infinite(x) / is_nan(x) |
isFinite(x) / isInfinite(x) / isNaN(x) |
bool |
SqlFunctions.ClickHouse.hypot(a, b) |
hypot(a, b) |
double |
SqlFunctions.ClickHouse.int_div(a, b) / int_exp2(x) / int_exp10(x) |
intDiv(a, b) / intExp2(x) / intExp10(x) |
ulong |
SqlFunctions.ClickHouse.sigmoid(x) / sqr(x) |
sigmoid(x) / sqr(x) |
double |
SqlFunctions.ClickHouse.round_age(x) / round_bankers(x) / round_down(x) / round_duration(x) / round_to_exp2(x) |
roundAge / roundBankers / roundDown / roundDuration / roundToExp2 |
the argument's integer width |
SqlFunctions.ClickHouse.bitAnd(a, b) / bitOr / bitXor |
bitAnd(a, b) / bitOr / bitXor |
ulong |
SqlFunctions.ClickHouse.bitNot(a) / bitShiftLeft(a, n) / bitShiftRight(a, n) |
bitNot(a) / bitShiftLeft(a, n) / bitShiftRight(a, n) |
the operand width (byte/int/uint/ulong) |
SqlFunctions.ClickHouse.bitRotateLeft(a, n) / bitRotateRight(a, n) |
bitRotateLeft(a, n) / bitRotateRight(a, n) |
the operand width |
SqlFunctions.ClickHouse.bitCount(a) |
bitCount(a) |
byte |
SqlFunctions.ClickHouse.bitTest(a, i) / bitTestAll / bitTestAny |
bitTest(a, i) / bitTestAll / bitTestAny |
bool |
SqlFunctions.ClickHouse.bitHammingDistance(a, b) |
bitHammingDistance(a, b) |
ulong (string overload included) |
The portable equivalents of e, plus, minus, multiply, divide and negate are the Math.E
constant and the CLR arithmetic operators, not ClickHouse-only members.
PostgreSQL extended math
The remaining math functions are part of the extended scalar library
(SupportsExtendedScalarFunctions, PostgreSQL only).
degrees/radians/pi are part of the portable
SqlFunctions.Sql surface above:
| C# | SQL |
|---|---|
SqlFunctions.Postgres.asin(x) / acos(x) / atan(x) |
asin(x) / acos(x) / atan(x) |
SqlFunctions.Postgres.atan2(y, x) |
atan2(y, x) |
SqlFunctions.Postgres.cbrt(x) |
cbrt(x) |
SqlFunctions.Postgres.sinh(x) / cosh(x) / tanh(x) |
sinh(x) / cosh(x) / tanh(x) |
SqlFunctions.Postgres.asinh(x) / acosh(x) / atanh(x) |
asinh(x) / acosh(x) / atanh(x) |
SqlFunctions.Postgres.random() |
random() |
SqlFunctions.Postgres.log(base, x) |
log((base)::numeric, (x)::numeric) — both operands are cast to numeric, because PostgreSQL's only two-argument form is log(numeric, numeric); one-argument log stays ln |
SqlFunctions.Postgres.gcd(a, b) / lcm(a, b) |
gcd(a, b) / lcm(a, b) |
SqlFunctions.Postgres.factorial(n) |
factorial(n) |
SqlFunctions.Postgres.width_bucket(x, low, high, count) |
width_bucket(x, low, high, count) |
SqlFunctions.Postgres.setseed(seed) renders setseed(seed) and is gated separately by
SupportsRandomSeed (PostgreSQL only). The
PostgreSQL function returns void, so a projected value is always null and the call is made for its
side effect (subsequent random() calls in the session become reproducible).
Delivered math and degree-trigonometric helpers (#281)
The remainder of the PostgreSQL math catalogue is exposed on the same surface, one member per server
overload. Every name is gated individually by
IScalarFunctions.Supports, so a
provider that does not implement the interface rejects the call with NotSupportedException:
| C# | SQL | Result |
|---|---|---|
SqlFunctions.Postgres.div(y, x) |
div(y, x) |
decimal? — numeric division truncated toward zero |
SqlFunctions.Postgres.erf(x) |
erf(x) |
double? — the error function, in -1..1 |
SqlFunctions.Postgres.erfc(x) |
erfc(x) |
double? — 1 - erf(x), in 0..2 |
SqlFunctions.Postgres.random_normal(mean, stddev) |
random_normal(mean, stddev) |
double? — volatile, a new value per execution |
SqlFunctions.Postgres.acosd(x) |
acosd(x) |
double? — arccosine in degrees |
SqlFunctions.Postgres.asind(x) |
asind(x) |
double? — arcsine in degrees |
SqlFunctions.Postgres.atand(x) |
atand(x) |
double? — arctangent in degrees |
SqlFunctions.Postgres.atan2d(y, x) |
atan2d(y, x) |
double? — arctangent of y/x in degrees, using the signs of both |
SqlFunctions.Postgres.cosd(x) |
cosd(x) |
double? — cosine of an angle in degrees |
SqlFunctions.Postgres.cotd(x) |
cotd(x) |
double? — cotangent of an angle in degrees |
SqlFunctions.Postgres.sind(x) |
sind(x) |
double? — sine of an angle in degrees |
SqlFunctions.Postgres.tand(x) |
tand(x) |
double? — tangent of an angle in degrees |
div is truncating numeric division, not a rounding one: div(7, 2) is 3 and div(-7, 2) is
-3; division by zero raises on the server. The *d family takes its argument in degrees and
returns degrees, so cosd(60) is 0.5; its radian counterparts are the
asin/acos/atan/atan2 rows above. Only the two-argument random_normal(mean, stddev) is
exposed; the server's zero- and one-argument forms are not. A NULL argument yields NULL.
A name the pinned server cannot run is refused while the query is prepared:
| Family | Refused member | Tested reason |
|---|---|---|
| numeric | gamma(x) |
absent on the pinned PostgreSQL 17.11 — missing from pg_proc and from the PG17 mathematical-functions table, and a live call fails with 42883; the name does not exist in any release, so there is no rename story. |
| numeric | tgamma(x) |
the same absence on 17.11 (42883). |
| numeric | lgamma(x) |
the same absence on 17.11 (42883). |
PostgreSQL numeric scale
Three PostgreSQL-only functions describe the scale of a numeric value. They are gated per name
by IScalarFunctions.Supports, so every
other provider rejects them with NotSupportedException instead of rendering PostgreSQL SQL:
| C# | SQL | Result |
|---|---|---|
SqlFunctions.Postgres.scale(decimal? value) |
scale(value) |
int?: digits in the fractional part, trailing zeroes included |
SqlFunctions.Postgres.min_scale(decimal? value) |
min_scale(value) |
int?: digits that remain after the trailing zeroes are removed |
SqlFunctions.Postgres.trim_scale(decimal? value) |
trim_scale(value) |
decimal?: the value with its trailing fractional zeroes removed |
scale is available on every supported PostgreSQL version; min_scale and trim_scale were added in
PostgreSQL 13. All three return NULL for a NULL argument. trim_scale only removes trailing zeroes —
it is not round/trunc and never changes a significant digit:
var rows = dataContext.From<IComplexEntity>()
.Select(e => new
{
Scale = SqlFunctions.Postgres.scale(12.3400m),
MinScale = SqlFunctions.Postgres.min_scale(12.3400m),
Trimmed = SqlFunctions.Postgres.trim_scale(12.3400m)
})
.ToList();
// Scale = 4, MinScale = 2, Trimmed = 12.34 (decimal scale 2)
12.3400 reports scale 4 because the fractional part holds four digits, while min_scale ignores the
trailing zeroes and reports 2. trim_scale(12.3456m) returns 12.3456 unchanged, keeping scale 4, and
trim_scale(0.0000m) returns 0 with scale 0.
SQL Server random
SqlFunctions.SqlServer.rand() / rand(seed) render rand() / rand(seed) and are gated per name
by ISqlServerFunctions (SQL Server only; every other
provider throws NotSupportedException):
| C# | SQL |
|---|---|
rand() / rand(seed) |
rand() / rand(seed) |
rand without a seed is non-deterministic and evaluated by the server; a fixed integer seed makes
the sequence reproducible. The PostgreSQL random()/setseed() equivalent is in the
extended math section above.