Applies to VAST Cluster 5.5.
Functions › Numeric and math functions › Powers and logarithms. Returns x raised to the power y, as a DOUBLE. power is the same function, and x ** y and x ^ y are operators for it.
Syntax
pow(x, y)
power(x, y)
x ** y
x ^ y
| Argument | Type | Description |
|---|---|---|
x |
DOUBLE (any number) |
The base. |
y |
DOUBLE (any number) |
The exponent. |
Returns: DOUBLE, whatever the argument types: pow(2, 2) on two integers is 4.0.
Aliases: power. The operators ** and ^ do the same.
Set up the examples
The examples connect with the ADBC driver, as in VAST Query Engine (ADBC), and work in a schema called docs_sql_fn_numeric_pow. Set QE_ENDPOINT, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY and VASTDB_BUCKET (SQL reference), and run the blocks in order in one Python session. Below each block is the output you should see.
This block connects, defines the run() and plan() helpers, and creates a table of three savings accounts: an amount, a yearly rate of return and a number of years. The last row has no values:
import os
import re
import adbc_driver_manager
import adbc_driver_manager.dbapi as dbapi
import adbc_driver_vastdb
BUCKET = os.environ["VASTDB_BUCKET"]
SCHEMA = f'"{BUCKET}/docs_sql_fn_numeric_pow"'
conn = dbapi.connect(
driver=adbc_driver_vastdb.get_driver_path(),
db_kwargs={
"vast.db.endpoint": os.environ["QE_ENDPOINT"],
"vast.db.access_key": os.environ["AWS_ACCESS_KEY_ID"],
"vast.db.secret_key": os.environ["AWS_SECRET_ACCESS_KEY"],
},
autocommit=True,
)
def server_message(e):
"""The server's message in a driver error, without the request ID or the echoed statement."""
text = re.sub(r"\[req=0x[0-9a-f]+\]: ", "", str(e))
found = (re.search(r'message: "(.*?)", request_id', text, re.S)
or re.search(r"description: (.*)\)", text, re.S))
message = found.group(1) if found else text.strip().splitlines()[-1]
message = re.sub(r"^Plan query: '.*?' failed with: ", "", message, flags=re.S)
message = re.split(r"\\n|\n", message)[0].replace('\\"', '"')
return message.replace(BUCKET, "<bucket>")
def run(sql):
"""Run one statement. Print the rows it returns, or the error."""
try:
with conn.cursor() as cur:
cur.execute(sql)
rows = cur.fetchall() if cur.description else []
except (adbc_driver_manager.Error, OSError) as e:
print(f"{type(e).__name__}: {server_message(e)}")
else:
print(rows)
def plan(sql):
"""Print the plan without its ~N Rows lines: the table's row count, which can still be 0 just after an insert."""
with conn.cursor() as cur:
cur.execute("EXPLAIN " + sql)
steps = cur.fetchall()[0][1]
print("\n".join(line for line in steps.splitlines() if "Rows" not in line))
TABLE = f"{SCHEMA}.savings"
run(f"CREATE SCHEMA IF NOT EXISTS {SCHEMA}")
run(f"CREATE TABLE IF NOT EXISTS {TABLE} (id BIGINT, amount DOUBLE, rate DOUBLE, years INTEGER)")
run(f"INSERT INTO {TABLE} VALUES "
"(1, 1000.0, 0.05, 10), "
"(2, 2500.0, 0.03, 5), "
"(3, 800.0, -0.1, 3), "
"(4, NULL, NULL, NULL)")
[]
[]
[(4,)]
What it does
pow(x, y) computes xy in double-precision floating point: pow(2, 10) is 1024.0, pow(2, 0.5) is the square root of 2, and pow(2, -1) is 0.5. Integer, DECIMAL and FLOAT arguments are converted to DOUBLE, and so is a string that holds a number: pow('12', 2) is 144.0.
A result with no real value, or one too big or too small for a DOUBLE, isn't an error. You get a special value instead:
- Infinity (
inf) when the result is too big, as forpow(2, 1024), and forpow(0, -1). - Zero when it's too small:
pow(2, -1075)is0.0. - NaN (not a number) for a negative base with a fractional exponent:
pow(-8, 1.0 / 3)andpow(-1, 0.5)arenan. A negative base with a whole exponent works:pow(-8, 3)is-512.0. For the cube root of a negative number, use cbrt.
Because the result is a DOUBLE, a large power isn't exact: pow(3, 40) is 1.2157665459056929e+19, not the exact integer, and pow(9007199254740993, 1) is 9007199254740992.0.
The ** and ^ operators
x ** y and x ^ y return the same as pow(x, y). Two rules differ from ordinary maths notation, so use parentheses:
- A minus sign binds first.
-2 ** 2is(-2) ** 2, which is4.0. Write-(2 ** 2)to negate the power. - A chain is evaluated from the left.
2 ** 3 ** 2is(2 ** 3) ** 2, which is64.0, not 29. Write2 ** (3 ** 2)for that.
Multiplication binds less tightly than a power: 2 * 3 ** 2 is 18.0. ^ is the power operator, not exclusive or; for that, use xor.
NULLs and edge cases
| Input | Result |
|---|---|
pow(NULL, 2), pow(2, NULL) |
NULL |
pow(2, 10) |
1024.0 |
pow(0, 0) |
1.0 |
pow(0, -1) |
inf |
pow(-8, 3) |
-512.0 |
pow(-8, 1.0 / 3), pow(-1, 0.5) |
nan |
pow(2, 1024) |
inf |
pow(2, -1075) |
0.0 |
pow('nan'::DOUBLE, 0), pow(1, 'nan'::DOUBLE) |
1.0 |
pow('inf'::DOUBLE, -1), pow(0.5, 'inf'::DOUBLE) |
0.0 |
pow(3, 40) |
1.2157665459056929e+19 (not exact) |
-2 ** 2 |
4.0 |
2 ** 3 ** 2 |
64.0 |
2 * 3 ** 2 |
18.0 |
pow('12', 2) |
144.0 |
pow('abc', 2) |
Error: Could not convert string 'abc' to DOUBLE |
pow(2) |
Error: No function matches the given name and argument types 'pow(INTEGER_LITERAL)' |
pow(true, 2) (a boolean, date, interval, BLOB or list) |
Error: No function matches the given name and argument types 'pow(BOOLEAN, INTEGER_LITERAL)' |
The same cases, as the cluster returns them. They use the table and helpers from Set up the examples:
run("SELECT pow(NULL, 2), pow(2, NULL), pow(2, 10), pow(0, 0), pow(0, -1), pow(-8, 3), pow(-8, 1.0 / 3), pow(-1, 0.5)")
run("SELECT pow(2, 1024), pow(2, -1075), pow('nan'::DOUBLE, 0), pow(1, 'nan'::DOUBLE), "
"pow('inf'::DOUBLE, -1), pow(0.5, 'inf'::DOUBLE), pow(3, 40)")
run("SELECT -2 ** 2, 2 ** 3 ** 2, 2 * 3 ** 2, -2 ^ 2, 2 ^ 3 ^ 2")
run("SELECT -(2 ** 2), 2 ** (3 ** 2)")
run("SELECT typeof(pow(2, 2)), typeof(pow(2::INTEGER, 2::INTEGER)), typeof(pow(2.5, 2)), typeof(2 ** 10)")
run(f"SELECT pow('12', 2) FROM {TABLE} LIMIT 1")
run(f"SELECT pow('abc', 2) FROM {TABLE} LIMIT 1")
run("SELECT pow(2)")
run("SELECT pow(true, 2)")
[(None, None, 1024.0, 1.0, inf, -512.0, nan, nan)]
[(inf, 0.0, 1.0, 1.0, 0.0, 0.0, 1.2157665459056929e+19)]
[(4.0, 64.0, 18.0, 4.0, 64.0)]
[(-4.0, 512.0)]
[('DOUBLE', 'DOUBLE', 'DOUBLE', 'DOUBLE')]
[(144.0,)]
OSError: Could not convert string 'abc' to DOUBLE
InternalError: Binder Error: No function matches the given name and argument types 'pow(INTEGER_LITERAL)'. You might need to add explicit type casts.
InternalError: Binder Error: No function matches the given name and argument types 'pow(BOOLEAN, INTEGER_LITERAL)'. You might need to add explicit type casts.
Examples
The value of each account after its number of years, with the rate compounded once a year:
run(f"SELECT id, round(amount * pow(1 + rate, years), 2) FROM {TABLE} ORDER BY id LIMIT 10")
[(1, 1628.89), (2, 2898.19), (3, 583.2), (4, None)]
The growth factor four ways: the two function names and the two operators:
run(f"SELECT id, pow(1 + rate, years), power(1 + rate, years), (1 + rate) ** years, (1 + rate) ^ years "
f"FROM {TABLE} ORDER BY id LIMIT 10")
[(1, 1.628894626777442, 1.628894626777442, 1.628894626777442, 1.628894626777442), (2, 1.1592740743, 1.1592740743, 1.1592740743, 1.1592740743), (3, 0.7290000000000001, 0.7290000000000001, 0.7290000000000001, 0.7290000000000001), (4, None, None, None, None)]
Keep only the accounts that end above 2,000:
run(f"SELECT id, amount FROM {TABLE} WHERE amount * pow(1 + rate, years) > 2000 ORDER BY id LIMIT 10")
[(2, 2500.0)]
How it runs in VAST
The Query Engine runs on the cluster's CNodes, and a query on a table starts with a scan in VAST DataBase (VAST_SCAN). VAST DataBase can evaluate a comparison of a column with a constant, but not a function of a column, so pow() and the ** and ^ operators are always computed by the Query Engine:
- In the
SELECTlist, the scan reads the columns and the Query Engine computespow()for each row it returns. - In
WHERE, the scan sends the columns for every row, and aFILTERstep in the Query Engine computespow()and keeps the rows that match. No rows are skipped in storage.
The plan of the WHERE example shows the FILTER step above the scan:
plan(f"SELECT id FROM {TABLE} WHERE amount * pow(1 + rate, years) > 2000")
┌───────────────────────────┐
│ PROJECTION │
│ ──────────────────── │
│ #3 │
│ │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ FILTER │
│ ──────────────────── │
│ ((amount * pow((1.0 + rate│
│ ), CAST(years AS DOUBLE)))│
│ > 2000.0) │
│ │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ VAST_SCAN │
│ ──────────────────── │
│ Function: VAST_SCAN │
│ │
│ Projections: │
│ amount │
│ rate │
│ years │
│ id │
│ │
└───────────────────────────┘
Make it faster
- Compare the raw column instead of its power.
rate ** 2 > 0.0025runs in aFILTERstep; the same condition as a range on the column,rate > 0.05 OR rate < -0.05, is pushed down to VAST DataBase, so rows that don't match never leave storage. Its plan has noFILTERstep:
plan(f"SELECT id FROM {TABLE} WHERE rate ** 2 > 0.0025")
plan(f"SELECT id FROM {TABLE} WHERE rate > 0.05 OR rate < -0.05")
┌───────────────────────────┐
│ PROJECTION │
│ ──────────────────── │
│ #1 │
│ │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ FILTER │
│ ──────────────────── │
│ ((rate ** 2.0) > 0.0025) │
│ │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ VAST_SCAN │
│ ──────────────────── │
│ Function: VAST_SCAN │
│ │
│ Projections: │
│ rate │
│ id │
│ │
└───────────────────────────┘
┌───────────────────────────┐
│ VAST_SCAN │
│ ──────────────────── │
│ Function: VAST_SCAN │
│ Projections: id │
│ │
└───────────────────────────┘
- Store the result if you filter on it often. Compute it when you write the rows and keep it in its own column, so the condition is a comparison of a column with a constant.
- Combine it with a pushed-down condition. In
id > 100 AND amount * pow(1 + rate, years) > 2000,id > 100is still evaluated in VAST DataBase, sopow()runs only on the rows that pass it.
Limitations and errors
- Numbers only. A boolean, date, interval,
BLOBor list fails withNo function matches the given name and argument types; the error lists the one formpowaccepts,pow(DOUBLE, DOUBLE) -> DOUBLE. A string is converted when the query runs, and one that isn't a number fails withCould not convert string 'abc' to DOUBLE. - Always a
DOUBLE. There's no integer power:pow(2, 10)is1024.0, and large results are rounded to the nearestDOUBLE. To multiply an integer by a power of two exactly, shift instead:3 << 4is the integer48, where3 * pow(2, 4)is48.0; see shift left. - No error for impossible results. A negative base with a fractional exponent returns
nan, and an overflow returnsinf. Test for them with isnan and isinf. - No
squarefunction.square(x)fails withScalar Function with name square does not exist!. Usex * xorpow(x, 2).
run("SELECT isnan(pow(-8, 1.0 / 3)), isinf(pow(2, 1024))")
run("SELECT square(3)")
run("SELECT 3 * 3, pow(3, 2)")
[(True, True)]
InternalError: Catalog Error: Scalar Function with name square does not exist!
[(9, 9.0)]
Similar functions
| Function | Computes | Result for 2 and 10 (pow(2, 10) gives 1024.0) |
|---|---|---|
x ** y, x ^ y |
the same as pow |
1024.0 |
exp (y * ln(x)) |
the same power, through e | 1024.0 |
| sqrt | pow(x, 0.5) for x ≥ 0 |
sqrt(1024) is 32.0 |
| cbrt | the cube root, also of negative numbers | cbrt(1024) is 10.079368399158987 |
shift left << |
a * pow(2, n) as an integer |
3 << 4 is 48; 3 * pow(2, 4) is 48.0 |
run("SELECT pow(2, 10), power(2, 10), 2 ** 10, 2 ^ 10, exp(10 * ln(2)), sqrt(1024), cbrt(1024)")
run("SELECT 3 << 4, 3 * pow(2, 4)")
[(1024.0, 1024.0, 1024.0, 1024.0, 1024.0, 32.0, 10.079368399158987)]
[(48, 48.0)]
Clean up:
run(f"DROP TABLE IF EXISTS {TABLE}")
run(f"DROP SCHEMA IF EXISTS {SCHEMA}")
conn.close()
[]
[]
See also
- Numeric and math functions: all the numeric functions.
- sqrt, cbrt, exp and ln.
- Where filters run and pushdowns.
- EXPLAIN: how to read a plan.