# Math functions
> Round and cut numbers, take powers, roots and logarithms, divide safely, put values in buckets, and make random numbers.
- Category: Functions — math
- Keywords: math, number, rounding, %, +, -, \*, /, arithmetic, division by zero, truncate, cut decimals, cube root, log base 2, greatest common divisor, highest common factor, hcf, least common multiple, n!, combinations, histogram, bucket, bin, limit to a range, circuit limit, between bounds, divide by zero, ifnull division, zero if division by zero, ratio, percentage, banker's rounding, round half to even, NaN, not a number, infinity, inf, rand, random sample, shuffle, seed
- See also: [Trigonometry and probability](functions-math-more.md), [Finance and price functions](functions-finance.md), [Conversion functions](functions-conversion.md), [Aggregate functions](functions-aggregate.md)
These functions work on numbers: `INT`, `LONG`, `DOUBLE`, `DECIMAL` and the other number types. Sine, cosine and the normal distribution are in [trigonometry and probability](functions-math-more.md); money functions (interest, loans, NPV) are in [finance functions](functions-finance.md).
- A `NULL` argument gives `NULL` (except `greatest` and `least`, which skip NULLs).
- A result that is not a real number (like `sqrt(-1)` or `ln(0)`) is `NULL`, not an error.
- Text is not turned into a number by itself: `round('5')` is an error. Use `CAST('5' AS DOUBLE)`.
**Example: Price change of CITY, rounded to 2 decimals** (sample data: sample)
```sql
SELECT time, close,
round(close - lag(close), 2) AS change,
round((close - lag(close)) / lag(close) * 100, 2) AS change_pct
FROM ochl_day
WHERE symbol = 'CITY'
LIMIT 5;
```
Result:
| time (TIMESTAMP) | close (DOUBLE) | change (DOUBLE) | change_pct (DOUBLE) |
|---|---|---|---|
| 2025-12-28T00:00:00.000Z | 542.2 | NULL | NULL |
| 2025-12-29T00:00:00.000Z | 539.7 | -2.5 | -0.46 |
| 2025-12-30T00:00:00.000Z | 539.3 | -0.4 | -0.07 |
| 2025-12-31T00:00:00.000Z | 542.4 | 3.1 | 0.57 |
| 2026-08-23T00:00:00.000Z | 507.9 | -34.5 | -6.36 |
(5 rows)
## abs
Syntax:
```sql
abs(x)
```
The number without its minus sign: `abs(-5)` is `5`.
| Argument | Type | Meaning |
|---|---|---|
| `x` | any number | the value |
- **Returns:** the same type as `x` (INT stays INT, DECIMAL stays DECIMAL).
- **NULL:** `abs(NULL)` is `NULL`.
**Example**
```sql
SELECT abs(-5) AS a, abs(-2.75) AS b, abs(CAST(-3.5 AS DECIMAL(2))) AS c, abs(NULL) AS d;
```
Result:
| a (LONG) | b (DOUBLE) | c (DECIMAL(2)) | d (STRING) |
|---|---|---|---|
| 5 | 2.75 | 3.50 | NULL |
(1 row)
**Example: How far the close is from the open, for each day** (sample data: sample)
```sql
SELECT time, symbol, open, close, abs(close - open) AS move
FROM ochl_day
WHERE symbol = 'COAST'
ORDER BY move DESC
LIMIT 3;
```
Result:
| time (TIMESTAMP) | symbol (SYMBOL) | open (DOUBLE) | close (DOUBLE) | move (DOUBLE) |
|---|---|---|---|---|
| 2026-09-09T00:00:00.000Z | COAST | 398 | 380.1 | 17.9 |
| 2026-09-07T00:00:00.000Z | COAST | 373.7 | 379.2 | 5.5 |
| 2026-08-26T00:00:00.000Z | COAST | 378.1 | 373 | 5.10000000000002 |
(3 rows)
## round
Syntax:
```sql
round(x)
round(x, digits)
```
Rounds `x` to `digits` places after the decimal point. Halves go away from zero: `round(2.5)` is `3` and `round(-2.5)` is `-3`.
| Argument | Type | Meaning |
|---|---|---|
| `x` | any number | the value to round |
| `digits` | whole number | places after the point; default `0`. A negative number rounds to tens, hundreds, ... |
- **Returns:** `DOUBLE`. An `INT` or `LONG` with `digits` 0 or more comes back unchanged. A `DECIMAL` stays an exact `DECIMAL` with `digits` places: `round(2.345::NUMERIC(10,3), 2)` is `2.35`.
- **NULL:** `NULL` in, `NULL` out.
- **Notes:** a `DOUBLE` can not hold every decimal exactly. `round` goes by the decimal a `DOUBLE` is written as, so `round(1.005, 2)` is `1.01` and `round(2.675, 2)` is `2.68`, as in PostgreSQL. For money that must be exact, use a `DECIMAL(2)` column.
- **Notes:** to store the same numbers as a program that rounds (for example to 6 places), round in the procedure too: [matching another system's rounding](procedures.md#rounding).
**Example**
```sql
SELECT round(512.456, 2) AS two_places, round(512.5) AS whole, round(-2.5) AS negative, round(1234.5, -2) AS hundreds, round(7) AS int_value, round(1.005, 2) AS half_cent;
```
Result:
| two_places (DOUBLE) | whole (DOUBLE) | negative (DOUBLE) | hundreds (DOUBLE) | int_value (LONG) | half_cent (DOUBLE) |
|---|---|---|---|---|---|
| 512.46 | 513 | -3 | 1200 | 7 | 1.01 |
(1 row)
**Example: Average close per symbol, rounded for a report** (sample data: sample)
```sql
SELECT symbol, round(avg(close), 2) AS avg_close, count(*) AS days
FROM ochl_day
GROUP BY symbol
ORDER BY symbol;
```
Result:
| symbol (SYMBOL) | avg_close (DOUBLE) | days (LONG) |
|---|---|---|
| CITY | 511.81 | 19 |
| COAST | 385.93 | 19 |
| VOLT | 180.97 | 19 |
(3 rows)
**Example: Text is not a number** (shows an error)
```sql
SELECT round('5');
```
Result:
Error: `line 1, column 8: round: argument 1 must be a number, got 5`
## round_half_even
Syntax:
```sql
round_half_even(x)
round_half_even(x, digits)
```
Rounds like [round](#round), but a value exactly **halfway** goes to the **even** neighbour: `round_half_even(2.5)` is `2`, `round_half_even(3.5)` is `4` (banker's rounding, QuestDB's `round_half_even`). Adding many rounded amounts then does not drift upwards.
| Argument | Type | Meaning |
|---|---|---|
| `x` | any number | the value to round |
| `digits` | whole number | places after the point; default `0`. A negative number rounds to tens, hundreds, ... |
- **Returns:** the same kinds as `round`: a `DECIMAL` stays an exact `DECIMAL` with `digits` places, a whole number with `digits` 0 or more is unchanged, anything else is a `DOUBLE`.
- **NULL:** `NULL` in, `NULL` out.
- **Notes:** a `DOUBLE` is judged by the decimal it is written as, as `round` does: `round_half_even(0.125::float8, 2)` is `0.12`, and `round_half_even(0.1251::float8, 2)` is `0.13` because it is past the half. `round` itself still rounds halves away from zero.
**Example**
```sql
SELECT round_half_even(2.5) AS two, round_half_even(3.5) AS four, round_half_even(-2.5) AS minus_two,
round_half_even(2.345, 2) AS even_cent, round_half_even(2.355, 2) AS up_cent, round_half_even(1250, -2) AS hundreds;
```
Result:
| two (DOUBLE) | four (DOUBLE) | minus_two (DOUBLE) | even_cent (DOUBLE) | up_cent (DOUBLE) | hundreds (LONG) |
|---|---|---|---|---|---|
| 2 | 4 | -2 | 2.34 | 2.36 | 1200 |
(1 row)
## floor
Syntax:
```sql
floor(x)
```
Rounds down to the whole number at or below `x`: `floor(5.7)` is `5`, `floor(-5.2)` is `-6`.
- **Returns:** `DOUBLE` for `DOUBLE` input; a `DECIMAL` gives an exact `DECIMAL` with no places; an `INT` or `LONG` comes back unchanged.
- **NULL:** `NULL` in, `NULL` out.
**Example**
```sql
SELECT floor(5.7) AS a, floor(-5.2) AS b, floor(9) AS c;
```
Result:
| a (DOUBLE) | b (DOUBLE) | c (LONG) |
|---|---|---|
| 5 | -6 | 9 |
(1 row)
**Example: Price bands of 10 dollars** (sample data: sample)
```sql
SELECT floor(close / 10) * 10 AS band, count(*) AS days
FROM ochl_day
WHERE symbol = 'CITY'
GROUP BY floor(close / 10) * 10
ORDER BY band;
```
Result:
| band (DOUBLE) | days (LONG) |
|---|---|
| 490 | 5 |
| 500 | 7 |
| 510 | 3 |
| 530 | 2 |
| 540 | 2 |
(5 rows)
## ceil, ceiling
Syntax:
```sql
ceil(x)
ceiling(x)
```
Rounds up to the whole number at or above `x`: `ceil(5.2)` is `6`, `ceil(-5.7)` is `-5`. `ceiling` is the same function.
- **Returns:** `DOUBLE` for `DOUBLE` input; a `DECIMAL` gives an exact `DECIMAL` with no places; an `INT` or `LONG` comes back unchanged.
- **NULL:** `NULL` in, `NULL` out.
**Example**
```sql
SELECT ceil(5.2) AS a, ceiling(-5.7) AS b, ceil(4) AS c;
```
Result:
| a (DOUBLE) | b (DOUBLE) | c (LONG) |
|---|---|---|
| 6 | -5 | 4 |
(1 row)
**Example: How many pages for 956 rows, 100 rows per page** (sample data: sample)
```sql
SELECT count(*) AS rows, ceil(count(*) / 100.0) AS pages FROM ochl_minutes;
```
Result:
| rows (LONG) | pages (DOUBLE) |
|---|---|
| 956 | 10 |
(1 row)
## trunc, truncate
Syntax:
```sql
trunc(x)
trunc(x, digits)
truncate(x, digits)
```
Cuts off the digits after `digits` places, without rounding: `trunc(512.789, 2)` is `512.78`, while `round` gives `512.79`. It moves toward zero: `trunc(-5.7)` is `-5` (but `floor(-5.7)` is `-6`). `truncate` is the same function.
| Argument | Type | Meaning |
|---|---|---|
| `x` | any number | the value to cut |
| `digits` | whole number | places after the point to keep; default `0`. A negative number cuts to tens, hundreds, ... |
- **Returns:** `DOUBLE`. An `INT` or `LONG` with `digits` 0 or more comes back unchanged (as a `LONG`).
- **NULL:** `NULL` when `x` or `digits` is `NULL`.
- **Notes:** `digits` must be a whole number: `trunc(5.99, 1.5)` is an error.
**Example**
```sql
SELECT trunc(512.789, 2) AS two_places, round(512.789, 2) AS rounded, trunc(-5.7) AS negative, floor(-5.7) AS floor, truncate(1234.5, -2) AS hundreds, trunc(7) AS int_value;
```
Result:
| two_places (DOUBLE) | rounded (DOUBLE) | negative (DOUBLE) | floor (DOUBLE) | hundreds (DOUBLE) | int_value (LONG) |
|---|---|---|---|---|---|
| 512.78 | 512.79 | -5 | -6 | 1200 | 7 |
(1 row)
**Example: Average trade rate: cut and rounded to 1 decimal** (sample data: sample)
```sql
SELECT symbol, avg(rate) AS avg_rate, trunc(avg(rate), 1) AS cut, round(avg(rate), 1) AS rounded
FROM trade_log
GROUP BY symbol
ORDER BY symbol;
```
Result:
| symbol (SYMBOL) | avg_rate (DOUBLE) | cut (DOUBLE) | rounded (DOUBLE) |
|---|---|---|---|
| CITY | 515.590909090909 | 515.5 | 515.6 |
| COAST | 399.363636363636 | 399.3 | 399.4 |
| TELCO | 880.5 | 880.5 | 880.5 |
| VOLT | 185 | 185 | 185 |
(4 rows)
**Example: digits must be a whole number** (shows an error)
```sql
SELECT trunc(5.99, 1.5);
```
Result:
Error: `line 1, column 8: trunc: argument 2 must be a whole number, got '1.5'`
## sqrt
Syntax:
```sql
sqrt(x)
```
Square root: `sqrt(16)` is `4`.
- **Returns:** `DOUBLE`.
- **NULL:** `NULL` in, `NULL` out. A negative `x` also gives `NULL`.
**Example**
```sql
SELECT sqrt(16) AS a, sqrt(2) AS b, sqrt(-1) AS negative;
```
Result:
| a (DOUBLE) | b (DOUBLE) | negative (STRING) |
|---|---|---|
| 4 | 1.4142135623731 | NULL |
(1 row)
**Example: Daily volatility from the variance of the close** (sample data: sample)
```sql
SELECT symbol, round(sqrt(var_samp(close)), 3) AS stddev_close
FROM ochl_day
GROUP BY symbol
ORDER BY symbol;
```
Result:
| symbol (SYMBOL) | stddev_close (DOUBLE) |
|---|---|
| CITY | 16.198 |
| COAST | 21.379 |
| VOLT | 8.637 |
(3 rows)
## cbrt
Syntax:
```sql
cbrt(x)
```
The **cube root**: the number that, multiplied by itself three times, gives `x`. `cbrt(27)` is `3`, because 3 × 3 × 3 = 27. Unlike `sqrt`, it works for negative numbers: `cbrt(-8)` is `-2`.
| Argument | Type | Meaning |
|---|---|---|
| `x` | any number | the value |
- **Returns:** `DOUBLE`.
- **NULL:** `NULL` in, `NULL` out.
- **Notes:** `cbrt(x)` is the same as `pow(x, 1.0 / 3)` for positive `x`, but `pow` gives `NULL` for a negative `x`.
**Example**
```sql
SELECT cbrt(27) AS a, cbrt(-8) AS b, cbrt(2) AS c, pow(-8, 1.0 / 3) AS pow_negative;
```
Result:
| a (DOUBLE) | b (DOUBLE) | c (DOUBLE) | pow_negative (STRING) |
|---|---|---|---|
| 3 | -2 | 1.25992104989487 | NULL |
(1 row)
**Example: Growth per year: 1,00,000 became 1,33,100 in 3 years**
```sql
SELECT round((cbrt(133100.0 / 100000) - 1) * 100, 2) AS growth_pct_per_year;
```
Result:
| growth_pct_per_year (DOUBLE) |
|---|
| 10 |
(1 row)
## pow, power
Syntax:
```sql
pow(x, y)
power(x, y)
```
`x` to the power `y`: `pow(2, 10)` is `1024`. `power` is the same function.
| Argument | Type | Meaning |
|---|---|---|
| `x` | any number | the base |
| `y` | any number | the power; `0.5` is a square root |
- **Returns:** `DOUBLE`.
- **NULL:** `NULL` when an argument is `NULL`, or when the result is not a real number (too big, or a negative base with a fraction power).
**Example**
```sql
SELECT pow(2, 10) AS a, power(2, 0.5) AS b, pow(10, -2) AS c, pow(-8, 1.0 / 3) AS not_real;
```
Result:
| a (DOUBLE) | b (DOUBLE) | c (DOUBLE) | not_real (STRING) |
|---|---|---|---|
| 1024 | 1.4142135623731 | 0.01 | NULL |
(1 row)
**Example: Money after 5 years at 8% a year**
```sql
SELECT round(100000 * pow(1 + 0.08, 5), 2) AS amount;
```
Result:
| amount (DOUBLE) |
|---|
| 146932.81 |
(1 row)
## exp
Syntax:
```sql
exp(x)
```
The number e (2.718...) to the power `x`. It undoes `ln`.
- **Returns:** `DOUBLE`. `NULL` when the result is too big for a `DOUBLE`.
- **NULL:** `NULL` in, `NULL` out.
**Example**
```sql
SELECT exp(0) AS a, exp(1) AS e, exp(ln(5)) AS back_to_5, exp(1000) AS too_big;
```
Result:
| a (DOUBLE) | e (DOUBLE) | back_to_5 (DOUBLE) | too_big (DOUBLE) |
|---|---|---|---|
| 1 | 2.71828182845905 | 5 | NULL |
(1 row)
## ln, log
Syntax:
```sql
ln(x)
log(x)
```
`ln(x)` is the natural logarithm (base e). `log(x)` is base 10, as in PostgreSQL: `log(100)` is `2`, the same as `log10(100)`. `log(b, x)` is base `b`.
- **Returns:** `DOUBLE`.
- **NULL:** `NULL` in, `NULL` out. `x` of 0 or less gives `NULL`.
- **Notes:** before 2026-09-25 `log(x)` was the natural logarithm. A query that meant ln should now say `ln`.
**Example**
```sql
SELECT ln(1) AS a, ln(exp(2)) AS b, log(100) AS base_10, log(2, 8) AS base_2, ln(0) AS zero;
```
Result:
| a (DOUBLE) | b (DOUBLE) | base_10 (DOUBLE) | base_2 (DOUBLE) | zero (STRING) |
|---|---|---|---|---|
| 0 | 2 | 2 | 3 | NULL |
(1 row)
**Example: Log returns of CITY** (sample data: sample)
```sql
SELECT time, close, round(ln(close / lag(close)), 5) AS log_return
FROM ochl_day
WHERE symbol = 'CITY'
LIMIT 4;
```
Result:
| time (TIMESTAMP) | close (DOUBLE) | log_return (DOUBLE) |
|---|---|---|
| 2025-12-28T00:00:00.000Z | 542.2 | NULL |
| 2025-12-29T00:00:00.000Z | 539.7 | -0.00462 |
| 2025-12-30T00:00:00.000Z | 539.3 | -0.00074 |
| 2025-12-31T00:00:00.000Z | 542.4 | 0.00573 |
(4 rows)
## log10
Syntax:
```sql
log10(x)
```
Logarithm base 10: `log10(1000)` is `3`.
- **Returns:** `DOUBLE`.
- **NULL:** `NULL` in, `NULL` out. `x` of 0 or less gives `NULL`.
**Example**
```sql
SELECT log10(1000) AS a, log10(2) AS b, log10(-1) AS negative;
```
Result:
| a (DOUBLE) | b (DOUBLE) | negative (STRING) |
|---|---|---|
| 3 | 0.301029995663981 | NULL |
(1 row)
## log2
Syntax:
```sql
log2(x)
```
Logarithm base 2: how many times you double 1 to get `x`. `log2(8)` is `3`, because 1 → 2 → 4 → 8. For a price, `log2(new / old)` tells how many times the price **doubled**.
| Argument | Type | Meaning |
|---|---|---|
| `x` | any number | the value, more than 0 |
- **Returns:** `DOUBLE`.
- **NULL:** `NULL` in, `NULL` out. `x` of 0 or less gives `NULL`.
**Example**
```sql
SELECT log2(8) AS a, log2(1024) AS b, log2(1000) AS c, log2(0) AS zero;
```
Result:
| a (DOUBLE) | b (DOUBLE) | c (DOUBLE) | zero (STRING) |
|---|---|---|---|
| 3 | 10 | 9.96578428466209 | NULL |
(1 row)
**Example: A share went from 100 to 800 dollars: how many doublings?**
```sql
SELECT log2(800.0 / 100) AS doublings, pow(2, log2(800.0 / 100)) * 100 AS back_to_price;
```
Result:
| doublings (DOUBLE) | back_to_price (DOUBLE) |
|---|---|
| 3 | 800 |
(1 row)
## sign
Syntax:
```sql
sign(x)
```
`-1` for a negative number, `0` for zero, `1` for a positive number.
- **Returns:** `DOUBLE`.
- **NULL:** `NULL` in, `NULL` out.
**Example**
```sql
SELECT sign(-7) AS a, sign(0) AS b, sign(3.5) AS c;
```
Result:
| a (DOUBLE) | b (DOUBLE) | c (DOUBLE) |
|---|---|---|
| -1 | 0 | 1 |
(1 row)
**Example: Up, down or flat days** (sample data: sample)
```sql
SELECT sign(close - open) AS direction, count(*) AS days
FROM ochl_day
GROUP BY sign(close - open)
ORDER BY direction;
```
Result:
| direction (DOUBLE) | days (LONG) |
|---|---|
| -1 | 27 |
| 1 | 30 |
(2 rows)
## mod
Syntax:
```sql
mod(x, y)
x % y
```
The remainder after dividing `x` by `y`: `mod(10, 3)` is `1`. The `%` operator does the same.
- **Returns:** `LONG` when both are whole numbers, else `DOUBLE`. The sign follows `x`.
- **NULL:** `NULL` when an argument is `NULL`. A `y` of `0` is an error, "division by zero", as in PostgreSQL.
**Example**
```sql
SELECT mod(10, 3) AS a, 10 % 3 AS b, mod(-10, 3) AS c, mod(10.5, 3) AS d, mod(5, NULLIF(0, 0)) AS by_zero;
```
Result:
| a (LONG) | b (LONG) | c (LONG) | d (DOUBLE) | by_zero (STRING) |
|---|---|---|---|---|
| 1 | 1 | -1 | 1.5 | NULL |
(1 row)
**Example: Every 5th transaction of the trade log** (sample data: sample)
```sql
SELECT transaction_id, symbol, quantity
FROM trade_log
WHERE transaction_id % 5 = 0
ORDER BY transaction_id
LIMIT 4;
```
Result:
| transaction_id (LONG) | symbol (SYMBOL) | quantity (LONG) |
|---|---|---|
| 100005 | CITY | 500 |
| 100010 | CITY | 50 |
| 100015 | COAST | 1000 |
| 100020 | VOLT | 100 |
(4 rows)
## greatest
Syntax:
```sql
greatest(value, value, ...)
```
The biggest of the values. `NULL` values are skipped. Works for numbers, text and times.
- **Returns:** the biggest value (its own type).
- **NULL:** `NULL` only when every value is `NULL`.
- **Notes:** values that cannot be compared (a number and a text) give an error. At most 64 values.
**Example**
```sql
SELECT greatest(3, 9, 4) AS a, greatest(3, NULL, 9) AS b, greatest('CITY', 'COAST') AS c, greatest(NULL, NULL) AS d;
```
Result:
| a (LONG) | b (LONG) | c (STRING) | d (STRING) |
|---|---|---|---|
| 9 | 9 | COAST | NULL |
(1 row)
**Example: The bigger of two prices** (sample data: sample)
```sql
SELECT time, open, close, greatest(open, close) AS body_top
FROM ochl_day
WHERE symbol = 'VOLT'
LIMIT 3;
```
Result:
| time (TIMESTAMP) | open (DOUBLE) | close (DOUBLE) | body_top (DOUBLE) |
|---|---|---|---|
| 2025-12-28T00:00:00.000Z | 187.7 | 191.7 | 191.7 |
| 2025-12-29T00:00:00.000Z | 192.9 | 196.4 | 196.4 |
| 2025-12-30T00:00:00.000Z | 195 | 192.3 | 195 |
(3 rows)
## least
Syntax:
```sql
least(value, value, ...)
```
The smallest of the values. `NULL` values are skipped. Works for numbers, text and times.
- **Returns:** the smallest value (its own type).
- **NULL:** `NULL` only when every value is `NULL`.
- **Notes:** values that cannot be compared give an error. At most 64 values.
**Example**
```sql
SELECT least(3, 9, 4) AS a, least(3, NULL, 9) AS b, least('2026-09-10', '2026-01-01') AS c;
```
Result:
| a (LONG) | b (LONG) | c (STRING) |
|---|---|---|
| 3 | 3 | 2026-01-01 |
(1 row)
**Example: A number and a text cannot be compared** (shows an error)
```sql
SELECT least(1, 'a');
```
Result:
Error: `line 1, column 8: least: values cannot be compared`
## gcd
Syntax:
```sql
gcd(a, b)
```
The **greatest common divisor**: the biggest whole number that divides both `a` and `b` with no remainder. `gcd(12, 18)` is `6`. Use it to make a ratio as small as possible, like a bonus share ratio of 20:100 that is really 1:5.
| Argument | Type | Meaning |
|---|---|---|
| `a` | whole number | the first value |
| `b` | whole number | the second value |
- **Returns:** `LONG`, 0 or more (the sign is ignored).
- **NULL:** `NULL` when an argument is `NULL`.
- **Notes:** `gcd(0, 5)` is `5`. A number with a fraction (`12.5`) is an error; a `DOUBLE` like `12.0` is fine.
**Example**
```sql
SELECT gcd(12, 18) AS a, gcd(-12, 18) AS b, gcd(0, 5) AS c, gcd(7, 5) AS no_common;
```
Result:
| a (LONG) | b (LONG) | c (LONG) | no_common (LONG) |
|---|---|---|---|
| 6 | 6 | 5 | 1 |
(1 row)
**Example: Write a bonus share ratio in its smallest form**
```sql
SELECT bonus, held,
bonus / gcd(bonus, held) AS bonus_small,
held / gcd(bonus, held) AS held_small
FROM (SELECT 20 AS bonus, 100 AS held UNION ALL SELECT 15, 45 UNION ALL SELECT 7, 10) t;
```
Result:
| bonus (LONG) | held (LONG) | bonus_small (DOUBLE) | held_small (DOUBLE) |
|---|---|---|---|
| 20 | 100 | 1 | 5 |
| 15 | 45 | 1 | 3 |
| 7 | 10 | 7 | 10 |
(3 rows)
**Example: Only whole numbers** (shows an error)
```sql
SELECT gcd(12.5, 5);
```
Result:
Error: `line 1, column 8: gcd: argument 1 must be a whole number, got '12.5'`
## lcm
Syntax:
```sql
lcm(a, b)
```
The **least common multiple**: the smallest whole number that both `a` and `b` divide. `lcm(4, 6)` is `12`. If one thing happens every 4 days and another every 6 days, they happen on the same day every 12 days.
| Argument | Type | Meaning |
|---|---|---|
| `a` | whole number | the first value |
| `b` | whole number | the second value |
- **Returns:** `LONG`, 0 or more.
- **NULL:** `NULL` when an argument is `NULL`.
- **Notes:** `lcm(0, x)` is `0`. A result too big for a `LONG` is an error.
**Example**
```sql
SELECT lcm(4, 6) AS a, lcm(-4, 6) AS b, lcm(0, 5) AS c, lcm(7, 5) AS d;
```
Result:
| a (LONG) | b (LONG) | c (LONG) | d (LONG) |
|---|---|---|---|
| 12 | 12 | 0 | 35 |
(1 row)
**Example: The smallest order that fits a lot size of 10 and a lot size of 25**
```sql
SELECT lcm(10, 25) AS smallest_order;
```
Result:
| smallest_order (LONG) |
|---|
| 50 |
(1 row)
## factorial
Syntax:
```sql
factorial(n)
```
`n!`: the product 1 × 2 × 3 × ... × `n`. `factorial(5)` is `120`. It counts the ways to put `n` things in a row, and it is used to count **combinations** (how many ways to pick `k` things from `n`).
| Argument | Type | Meaning |
|---|---|---|
| `n` | whole number, 0 or more | how many things |
- **Returns:** `LONG` for `n` up to 20. From 21, the result is too big for a `LONG` and is a `DOUBLE` (not exact). From 171 it is too big even for a `DOUBLE`: `NULL`.
- **NULL:** `NULL` in, `NULL` out.
- **Notes:** `factorial(0)` is `1`. A negative `n` or a fraction is an error.
**Example**
```sql
SELECT factorial(0) AS a, factorial(5) AS b, factorial(20) AS c, factorial(21) AS d, factorial(171) AS e;
```
Result:
| a (LONG) | b (LONG) | c (LONG) | d (DOUBLE) | e (STRING) |
|---|---|---|---|---|
| 1 | 120 | 2432902008176640000 | 5.10909421717094e+19 | NULL |
(1 row)
**Example: How many ways to pick 3 shares from the 5 securities?** (sample data: sample)
```sql
SELECT n, factorial(n) / (factorial(3) * factorial(n - 3)) AS ways_to_pick_3
FROM (SELECT count(*) AS n FROM securities) t;
```
Result:
| n (LONG) | ways_to_pick_3 (DOUBLE) |
|---|---|
| 5 | 10 |
(1 row)
**Example: A negative number is an error** (shows an error)
```sql
SELECT factorial(-1);
```
Result:
Error: `line 1, column 8: factorial: needs 0 or more, got -1`
## pi
Syntax:
```sql
pi()
```
The number π (3.14159...): the length around a circle divided by its width. You need it with [sin and cos](functions-math-more.md) and with [radians](#radians).
- **Returns:** `DOUBLE`.
- **NULL:** never `NULL`.
**Example**
```sql
SELECT pi() AS pi, round(pi(), 2) AS short, 2 * pi() AS full_circle_in_radians;
```
Result:
| pi (DOUBLE) | short (DOUBLE) | full_circle_in_radians (DOUBLE) |
|---|---|---|
| 3.14159265358979 | 3.14 | 6.28318530717959 |
(1 row)
## degrees
Syntax:
```sql
degrees(x)
```
Changes an angle in **radians** to **degrees**. A full circle is 360 degrees or 2π radians, so `degrees(pi())` is `180`. The functions `asin`, `acos`, `atan` and `atan2` give radians; use `degrees` to read them.
| Argument | Type | Meaning |
|---|---|---|
| `x` | any number | an angle in radians |
- **Returns:** `DOUBLE`.
- **NULL:** `NULL` in, `NULL` out.
**Example**
```sql
SELECT degrees(pi()) AS half_circle, degrees(pi() / 2) AS right_angle, degrees(1) AS one_radian, degrees(atan(1)) AS atan_1;
```
Result:
| half_circle (DOUBLE) | right_angle (DOUBLE) | one_radian (DOUBLE) | atan_1 (DOUBLE) |
|---|---|---|---|
| 180 | 90 | 57.2957795130823 | 45 |
(1 row)
## radians
Syntax:
```sql
radians(x)
```
Changes an angle in **degrees** to **radians**, the unit that `sin`, `cos` and `tan` need. `radians(180)` is π.
| Argument | Type | Meaning |
|---|---|---|
| `x` | any number | an angle in degrees |
- **Returns:** `DOUBLE`.
- **NULL:** `NULL` in, `NULL` out.
**Example**
```sql
SELECT radians(180) AS pi, radians(90) AS right_angle, round(sin(radians(30)), 4) AS sin_30_degrees;
```
Result:
| pi (DOUBLE) | right_angle (DOUBLE) | sin_30_degrees (DOUBLE) |
|---|---|---|
| 3.14159265358979 | 1.5707963267949 | 0.5 |
(1 row)
## width_bucket
Syntax:
```sql
width_bucket(x, low, high, count)
```
Cuts the range from `low` to `high` into `count` buckets of the same width, and says which bucket `x` falls in: `1` to `count`. A value below `low` is bucket `0`; a value at or above `high` is bucket `count + 1`. Use it to make a **histogram**: a count of values per bucket.
| Argument | Type | Meaning |
|---|---|---|
| `x` | any number | the value to place |
| `low` | any number | where bucket 1 starts |
| `high` | any number | where the last bucket ends (not included) |
| `count` | whole number, 1 or more | how many buckets |
- **Returns:** `LONG`.
- **NULL:** `NULL` when an argument is `NULL`.
- **Notes:** each bucket includes its start and not its end. When `low` is bigger than `high`, the buckets count down. `low` and `high` must be different, else an error. Like PostgreSQL `width_bucket`.
**Example**
```sql
SELECT width_bucket(512, 400, 600, 4) AS a, width_bucket(399, 400, 600, 4) AS below, width_bucket(600, 400, 600, 4) AS at_high, width_bucket(450, 600, 400, 4) AS down;
```
Result:
| a (LONG) | below (LONG) | at_high (LONG) | down (LONG) |
|---|---|---|---|
| 3 | 0 | 5 | 4 |
(1 row)
**Example: Histogram of CITY's one-minute closes on 10 September** (sample data: sample)
```sql
SELECT width_bucket(close, 500, 510, 5) AS bucket,
500 + (width_bucket(close, 500, 510, 5) - 1) * 2 AS from_price,
count(*) AS minutes
FROM ochl_minutes
WHERE symbol = 'CITY' AND time IN '2026-09-10'
GROUP BY width_bucket(close, 500, 510, 5)
ORDER BY bucket;
```
Result:
| bucket (LONG) | from_price (LONG) | minutes (LONG) |
|---|---|---|
| 1 | 500 | 11 |
| 2 | 502 | 87 |
| 3 | 504 | 109 |
| 4 | 506 | 33 |
(4 rows)
**Example: Daily returns in 1% buckets from -3% to +3%** (sample data: sample)
```sql
SELECT width_bucket(chg, -3, 3, 6) AS bucket, count(*) AS days
FROM (SELECT pct_change(close) OVER (PARTITION BY symbol ORDER BY time) AS chg
FROM ochl_day
WHERE time >= '2026-08-23') t
WHERE chg IS NOT NULL
GROUP BY width_bucket(chg, -3, 3, 6)
ORDER BY bucket;
```
Result:
| bucket (LONG) | days (LONG) |
|---|---|
| 1 | 1 |
| 2 | 11 |
| 3 | 11 |
| 4 | 9 |
| 5 | 5 |
| 6 | 4 |
| 7 | 1 |
(7 rows)
## clamp
Syntax:
```sql
clamp(x, low, high)
```
Keeps `x` inside the range from `low` to `high`: below `low` gives `low`, above `high` gives `high`, else `x`. It is the same as `least(greatest(x, low), high)`.
| Argument | Type | Meaning |
|---|---|---|
| `x` | number, text or time | the value |
| `low` | same kind as `x` | the smallest allowed value |
| `high` | same kind as `x` | the biggest allowed value |
- **Returns:** the value that was chosen, with its own type.
- **NULL:** `NULL` when any argument is `NULL`.
- **Notes:** `low` bigger than `high` is an error. Values that cannot be compared (a number and a text) are an error.
**Example**
```sql
SELECT clamp(15, 0, 10) AS too_big, clamp(-3, 0, 10) AS too_small, clamp(4.5, 0, 10) AS inside, clamp('m', 'a', 'k') AS text;
```
Result:
| too_big (LONG) | too_small (LONG) | inside (DOUBLE) | text (STRING) |
|---|---|---|---|
| 10 | 0 | 4.5 | k |
(1 row)
**Example: Circuit limit: an order price must stay within 10% of yesterday's close**
Setup:
```sql
CREATE TABLE orders (id INT, symbol STRING, prev_close DOUBLE, price DOUBLE);
INSERT INTO orders VALUES (1, 'CITY', 504.9, 570.0), (2, 'COAST', 376.1, 390.0), (3, 'VOLT', 191.2, 160.0);
```
```sql
SELECT id, symbol, prev_close, price,
clamp(price, round(prev_close * 0.9, 1), round(prev_close * 1.1, 1)) AS allowed_price
FROM orders
ORDER BY id;
```
Result:
| id (INT) | symbol (STRING) | prev_close (DOUBLE) | price (DOUBLE) | allowed_price (DOUBLE) |
|---|---|---|---|---|
| 1 | CITY | 504.9 | 570 | 555.4 |
| 2 | COAST | 376.1 | 390 | 390 |
| 3 | VOLT | 191.2 | 160 | 172.1 |
(3 rows)
**Example: The lower bound must not be bigger** (shows an error)
```sql
SELECT clamp(5, 10, 0);
```
Result:
Error: `line 1, column 8: clamp: the lower bound is bigger than the upper bound`
## safe_divide
Syntax:
```sql
safe_divide(x, y)
```
`x / y`, but `NULL` when `y` is `0`. The `/` operator stops the query with "division by zero" instead, as PostgreSQL does; `safe_divide` works like `SAFE_DIVIDE` in BigQuery, so copied SQL runs.
| Argument | Type | Meaning |
|---|---|---|
| `x` | any number | the number to divide |
| `y` | any number | the number to divide by |
- **Returns:** `DOUBLE`.
- **NULL:** `NULL` when an argument is `NULL` or `y` is `0`.
- **Notes:** to get `0` instead of `NULL`, use [div0](#div0).
**Example**
```sql
SELECT safe_divide(10, 4) AS a, safe_divide(10, 0) AS by_zero, 10 / NULLIF(0, 0) AS operator, safe_divide(NULL, 2) AS null_value;
```
Result:
| a (DOUBLE) | by_zero (STRING) | operator (STRING) | null_value (STRING) |
|---|---|---|---|
| 2.5 | NULL | NULL | NULL |
(1 row)
**Example: Average trade size, also on a day with no trades**
Setup:
```sql
CREATE TABLE broker_day (broker INT, trades INT, shares INT);
INSERT INTO broker_day VALUES (1, 12, 3400), (34, 0, 0), (45, 5, 1250);
```
```sql
SELECT broker, trades, shares, safe_divide(shares, trades) AS avg_trade_size
FROM broker_day
ORDER BY broker;
```
Result:
| broker (INT) | trades (INT) | shares (INT) | avg_trade_size (DOUBLE) |
|---|---|---|---|
| 1 | 12 | 3400 | 283.333333333333 |
| 34 | 0 | 0 | NULL |
| 45 | 5 | 1250 | 250 |
(3 rows)
## div0
Syntax:
```sql
div0(x, y)
```
`x / y`, but `0` when `y` is `0` (like `DIV0` in Snowflake). Use it when a zero is the right answer, for example so that a total over many rows is not hurt by a `NULL`.
| Argument | Type | Meaning |
|---|---|---|
| `x` | any number | the number to divide |
| `y` | any number | the number to divide by |
- **Returns:** `DOUBLE`.
- **NULL:** `NULL` when an argument is `NULL`. A `y` of `0` gives `0`, not `NULL`.
**Example**
```sql
SELECT div0(10, 4) AS a, div0(10, 0) AS by_zero, div0(5, NULL) AS null_value;
```
Result:
| a (DOUBLE) | by_zero (DOUBLE) | null_value (STRING) |
|---|---|---|
| 2.5 | 0 | NULL |
(1 row)
**Example: safe_divide and div0 side by side**
Setup:
```sql
CREATE TABLE broker_day (broker INT, trades INT, shares INT);
INSERT INTO broker_day VALUES (1, 12, 3400), (34, 0, 0), (45, 5, 1250);
```
```sql
SELECT broker, safe_divide(shares, trades) AS safe_divide, div0(shares, trades) AS div0
FROM broker_day
ORDER BY broker;
```
Result:
| broker (INT) | safe_divide (DOUBLE) | div0 (DOUBLE) |
|---|---|---|
| 1 | 283.333333333333 | 283.333333333333 |
| 34 | NULL | 0 |
| 45 | 250 | 250 |
(3 rows)
## percent
Syntax:
```sql
percent(part, total)
```
`part` as a percent of `total`: `part / total × 100`. `percent(25, 200)` is `12.5`.
| Argument | Type | Meaning |
|---|---|---|
| `part` | any number | the part |
| `total` | any number | the whole |
- **Returns:** `DOUBLE` (`12.5` means 12.5%).
- **NULL:** `NULL` when an argument is `NULL` or `total` is `0`.
**Example**
```sql
SELECT percent(25, 200) AS a, percent(3, 4) AS b, percent(5, 0) AS zero_total;
```
Result:
| a (DOUBLE) | b (DOUBLE) | zero_total (STRING) |
|---|---|---|
| 12.5 | 75 | NULL |
(1 row)
**Example: Share of paying (pro) users in each city** (sample data: query-app)
```sql
SELECT city, count(*) AS users,
sum(CASE WHEN plan = 'pro' THEN 1 ELSE 0 END) AS pro_users,
round(percent(sum(CASE WHEN plan = 'pro' THEN 1 ELSE 0 END), count(*)), 1) AS pro_pct
FROM app_users
WHERE city IS NOT NULL
GROUP BY city
ORDER BY pro_pct DESC, city;
```
Result:
| city (STRING) | users (LONG) | pro_users (LONG) | pro_pct (DOUBLE) |
|---|---|---|---|
| Dharan | 1 | 1 | 100 |
| Kathmandu | 2 | 1 | 50 |
| Pokhara | 2 | 1 | 50 |
| Lalitpur | 1 | 0 | 0 |
(4 rows)
## is_nan, is_inf, is_finite
Syntax:
```sql
is_nan(x)
is_inf(x)
is_finite(x)
```
A `DOUBLE` can hold three special values that are not normal numbers: **NaN** ("not a number", for example from `0 × infinity`), **inf** (infinity) and **-inf**. They can come from other programs or from a calculation that is too big. These functions test for them.
| Function | `true` when `x` is |
|---|---|
| `is_nan(x)` | NaN |
| `is_inf(x)` | inf or -inf |
| `is_finite(x)` | a normal number (not NaN, not inf) |
- **Returns:** `BOOL`.
- **NULL:** `NULL` in, `NULL` out.
- **Notes:** most KDB math functions give `NULL` instead of NaN or inf. The `*` operator does not: `1e308 * 10` is inf. Text is an error.
**Example**
```sql
SELECT is_nan(CAST('NaN' AS DOUBLE)) AS nan, is_inf(CAST('-inf' AS DOUBLE)) AS minus_inf,
is_finite(1e308 * 10) AS too_big_is_finite, is_finite(512.5) AS normal, is_nan(NULL) AS null_value;
```
Result:
| nan (BOOL) | minus_inf (BOOL) | too_big_is_finite (BOOL) | normal (BOOL) | null_value (STRING) |
|---|---|---|---|---|
| true | true | false | true | NULL |
(1 row)
**Example: Which values are normal numbers?**
```sql
SELECT name, is_nan(x) AS is_nan, is_inf(x) AS is_inf, is_finite(x) AS is_finite
FROM (SELECT 'price' AS name, 512.5 AS x
UNION ALL SELECT 'too big', 1e308 * 10
UNION ALL SELECT 'not a number', CAST('NaN' AS DOUBLE)) t;
```
Result:
| name (STRING) | is_nan (BOOL) | is_inf (BOOL) | is_finite (BOOL) |
|---|---|---|---|
| price | false | false | true |
| too big | false | true | false |
| not a number | true | false | false |
(3 rows)
## random, rand
Syntax:
```sql
random()
rand()
```
A random number from `0` up to (but not including) `1`. Every call gives a new number, also on every row. `rand` is the same function.
- **Returns:** `DOUBLE`, at least `0` and less than `1`.
- **NULL:** never `NULL`.
- **Notes:**
- A random whole number from 1 to 6: `floor(random() * 6) + 1`.
- `ORDER BY random() LIMIT n` picks `n` random rows.
- Not safe for passwords or tokens: use [random_token](functions-security.md#random-token) for those.
- Results with `random()` are never taken from a cache.
**Example: Every one-minute candle gets a random number, always from 0 to 1** (sample data: sample)
```sql
SELECT count(*) AS rows, min(r) >= 0 AS at_least_0, max(r) < 1 AS below_1
FROM (SELECT random() AS r FROM ochl_minutes) t;
```
Result:
| rows (LONG) | at_least_0 (BOOL) | below_1 (BOOL) |
|---|---|---|
| 956 | true | true |
(1 row)
**Example: Pick 3 app users for a survey (setseed makes the pick repeatable)** (sample data: query-app)
```sql
SELECT setseed(0.42);
SELECT username, city FROM app_users ORDER BY random() LIMIT 3;
```
Result:
`SELECT setseed(0.42)`
| setseed(0.42) (STRING) |
|---|
| NULL |
(1 row)
`SELECT username, city FROM app_users ORDER BY random() LIMIT 3`
| username (STRING) | city (STRING) |
|---|---|
| suman | Pokhara |
| anita | NULL |
| gita | Dharan |
(3 rows)
## setseed
Syntax:
```sql
setseed(seed)
```
Starts the random numbers from a fixed point, the **seed**. After the same `setseed`, `random()` gives the same numbers again, in the same order. Use it for tests and for a random sample that you can make again.
| Argument | Type | Meaning |
|---|---|---|
| `seed` | number from `-1` to `1` | the starting point |
- **Returns:** always `NULL`.
- **NULL:** `setseed(NULL)` gives `NULL` and changes nothing.
- **Notes:** the seed is shared by the whole kdb program. On a server, another user's `random()` calls move the numbers on, so the order is only repeatable when nobody else uses `random()` at the same time.
**Example: The same seed gives the same numbers**
```sql
SELECT setseed(0.5);
SELECT round(random(), 4) AS first, round(random(), 4) AS second;
SELECT setseed(0.5);
SELECT round(random(), 4) AS first_again, round(random(), 4) AS second_again;
```
Result:
`SELECT setseed(0.5)`
| setseed(0.5) (STRING) |
|---|
| NULL |
(1 row)
`SELECT round(random(), 4) AS first, round(random(), 4) AS second`
| first (DOUBLE) | second (DOUBLE) |
|---|---|
| 0.1391 | 0.9194 |
(1 row)
`SELECT setseed(0.5)`
| setseed(0.5) (STRING) |
|---|
| NULL |
(1 row)
`SELECT round(random(), 4) AS first_again, round(random(), 4) AS second_again`
| first_again (DOUBLE) | second_again (DOUBLE) |
|---|---|
| 0.1391 | 0.9194 |
(1 row)
**Example: The seed must be from -1 to 1** (shows an error)
```sql
SELECT setseed(2);
```
Result:
Error: `line 1, column 8: setseed: the seed must be between -1 and 1`
## Arithmetic operators
Syntax:
```sql
a + b a - b a * b a / b a % b -a
```
| Operator | Result |
|---|---|
| `+` `-` `*` | `LONG` when both sides are whole numbers (an `INT` result past `INT` simply becomes a `LONG`), else `DOUBLE`. A result too big for `LONG` is an error: "bigint out of range" |
| `/` | `DOUBLE`: `7 / 2` is `3.5`. After `SET int_division = integer`, whole number / whole number is a whole number, as in PostgreSQL: `7 / 2` is `3` ([integer division](operators.md#integer-division)) |
| `%` | remainder, like `mod` |
| `TIMESTAMP + number` | the time plus that many milliseconds |
| `TIMESTAMP - TIMESTAMP` | milliseconds between the two times |
| `DATE + number` | the date plus that many days |
- **NULL:** `NULL` on either side gives `NULL`.
- **Division by zero:** `5 / 0`, `5.0 / 0` and `5 % 0` stop the query with "division by zero" (SQLSTATE `22012`), as in PostgreSQL. For `NULL` instead, write `x / NULLIF(y, 0)` or [safe_divide](#safe_divide).
- **Too big:** a whole-number result that does not fit a `LONG` stops the query (SQLSTATE `22003`). Cast one side to `DOUBLE` for an approximate answer.
- `*` and `/` are done before `+` and `-`. Use `( )` to change the order.
**Example**
```sql
SELECT 2 + 3 * 4 AS a, (2 + 3) * 4 AS b, 7 / 2 AS c, 7 % 2 AS d, 5 / NULLIF(0, 0) AS e, -2 * 3 AS f, NULL + 1 AS g;
```
Result:
| a (LONG) | b (LONG) | c (DOUBLE) | d (LONG) | e (STRING) | f (LONG) | g (STRING) |
|---|---|---|---|---|---|---|
| 14 | 20 | 3.5 | 1 | NULL | -6 | NULL |
(1 row)
**Example: Division by zero is an error** (shows an error)
```sql
SELECT 5 / 0;
```
Result:
Error: `line 1, column 10: division by zero`
**Example: Too big for a LONG** (shows an error)
```sql
SELECT 9223372036854775807 + 1;
```
Result:
Error: `line 1, column 28: bigint out of range (numeric value out of range)`
**Example: Times: add one hour, and the minutes between two times**
```sql
SELECT CAST('2026-09-10T05:15:00Z' AS TIMESTAMP) + 3600000 AS plus_one_hour,
(CAST('2026-09-10T11:00:00Z' AS TIMESTAMP) - CAST('2026-09-10T05:15:00Z' AS TIMESTAMP)) / 60000 AS minutes;
```
Result:
| plus_one_hour (TIMESTAMP) | minutes (DOUBLE) |
|---|---|
| 2026-09-10T06:15:00.000Z | 345 |
(1 row)