Welcome to the new DOCS.vastdata.com. We've added new menus and a version selector, plus new persona and resource pages to help customers get started. Looking for a previous article? Use the docs search.

pow

Prev Next

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 for pow(2, 1024), and for pow(0, -1).
  • Zero when it's too small: pow(2, -1075) is 0.0.
  • NaN (not a number) for a negative base with a fractional exponent: pow(-8, 1.0 / 3) and pow(-1, 0.5) are nan. 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 ** 2 is (-2) ** 2, which is 4.0. Write -(2 ** 2) to negate the power.
  • A chain is evaluated from the left. 2 ** 3 ** 2 is (2 ** 3) ** 2, which is 64.0, not 29. Write 2 ** (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 SELECT list, the scan reads the columns and the Query Engine computes pow() for each row it returns.
  • In WHERE, the scan sends the columns for every row, and a FILTER step in the Query Engine computes pow() 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.0025 runs in a FILTER step; 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 no FILTER step:
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 > 100 is still evaluated in VAST DataBase, so pow() runs only on the rows that pass it.

Limitations and errors

  • Numbers only. A boolean, date, interval, BLOB or list fails with No function matches the given name and argument types; the error lists the one form pow accepts, pow(DOUBLE, DOUBLE) -> DOUBLE. A string is converted when the query runs, and one that isn't a number fails with Could not convert string 'abc' to DOUBLE.
  • Always a DOUBLE. There's no integer power: pow(2, 10) is 1024.0, and large results are rounded to the nearest DOUBLE. To multiply an integer by a power of two exactly, shift instead: 3 << 4 is the integer 48, where 3 * pow(2, 4) is 48.0; see shift left.
  • No error for impossible results. A negative base with a fractional exponent returns nan, and an overflow returns inf. Test for them with isnan and isinf.
  • No square function. square(x) fails with Scalar Function with name square does not exist!. Use x * x or pow(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