Applies to VAST Cluster 5.5.
Functions › Numeric and math functions › Inverse trigonometry. Returns the arc cosine of a number: the angle, in radians from 0 to pi, whose cosine is that number. The argument must be from -1 to 1. A common use is turning a cosine similarity into an angle.
Syntax
acos(x)
| Argument | Type | Description |
|---|---|---|
x |
Any number (DOUBLE, REAL, an integer or a DECIMAL) |
The cosine, from -1 to 1. |
Returns: DOUBLE, from 0 to pi.
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_acos. 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 six cosine similarities between pairs of vectors. Row 3 has no value:
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_acos"'
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}.similarities"
run(f"CREATE SCHEMA IF NOT EXISTS {SCHEMA}")
run(f"CREATE TABLE IF NOT EXISTS {TABLE} (id BIGINT, similarity DOUBLE)")
run(f"INSERT INTO {TABLE} VALUES "
"(1, 0.5), (2, -0.5), (3, NULL), (4, 0.0), (5, 1.0), (6, -1.0)")
[]
[]
[(6,)]
What it does
acos(x) is the inverse of cos on the range 0 to pi: it returns the angle a, in radians, for which cos(a) = x. A cosine of 1 is an angle of 0, a cosine of 0 is a right angle (pi/2), and a cosine of -1 is pi. To get degrees, wrap it in degrees.
| Call | Result |
|---|---|
acos(1) |
0.0 |
acos(0.5) |
1.0471975511965979 (pi/3) |
acos(0) |
1.5707963267948966 (pi/2) |
acos(-1) |
3.141592653589793 (pi) |
The result is a DOUBLE for every numeric argument type: an integer, a REAL, a DECIMAL or a DOUBLE.
run("SELECT acos(1), acos(0.5), acos(0), acos(-1)")
run("SELECT typeof(acos(1::INTEGER)), typeof(acos(0.5::FLOAT)), typeof(acos(0.5))")
[(0.0, 1.0471975511965979, 1.5707963267948966, 3.141592653589793)]
[('DOUBLE', 'DOUBLE', 'DOUBLE')]
NULLs and edge cases
| Input | Result |
|---|---|
NULL |
NULL |
'nan'::DOUBLE |
nan |
-0.0::DOUBLE |
1.5707963267948966, the same as for 0 |
The smallest positive DOUBLE (5e-324) |
1.5707963267948966 |
'12' (a string literal holding a number) |
Converted to DOUBLE, then fails: ACOS is undefined outside [-1,1] |
A value above 1 or below -1, such as 1.0000000001 or -2 |
Error: ACOS is undefined outside [-1,1] |
'inf'::DOUBLE, '-inf'::DOUBLE |
Error: input value inf is out of range for numeric function (-inf for minus infinity) |
'abc' (a string that isn't a number) |
Error: Could not convert string 'abc' to DOUBLE |
A BLOB, date, boolean, list or interval |
Error: No function matches the given name and argument types, for example 'acos(DATE)' |
The same cases, as the cluster returns them. They use the helpers from Set up the examples:
run("SELECT acos(NULL), acos('nan'::DOUBLE), acos(-0.0::DOUBLE), acos(5e-324::DOUBLE)")
run("SELECT acos('12')")
run("SELECT acos(1.0000000001)")
run("SELECT acos(-2)")
run("SELECT acos('inf'::DOUBLE)")
run("SELECT acos('-inf'::DOUBLE)")
run("SELECT acos('abc')")
run("SELECT acos(DATE '2026-10-16')")
[(None, nan, 1.5707963267948966, 1.5707963267948966)]
OSError: ACOS is undefined outside [-1,1]
OSError: ACOS is undefined outside [-1,1]
OSError: ACOS is undefined outside [-1,1]
OSError: input value inf is out of range for numeric function
OSError: input value -inf is out of range for numeric function
OSError: Could not convert string 'abc' to DOUBLE
InternalError: Binder Error: No function matches the given name and argument types 'acos(DATE)'. You might need to add explicit type casts.
Examples
The angle between each pair of vectors, in radians and in degrees. A similarity of 1 means the vectors point the same way (0 degrees), 0 means they're at right angles, and -1 means they point in opposite directions:
run(f"SELECT id, similarity, acos(similarity), degrees(acos(similarity)) FROM {TABLE} ORDER BY id LIMIT 10")
[(1, 0.5, 1.0471975511965979, 60.00000000000001), (2, -0.5, 2.0943951023931957, 120.00000000000001), (3, None, None, None), (4, 0.0, 1.5707963267948966, 90.0), (5, 1.0, 0.0, 0.0), (6, -1.0, 3.141592653589793, 180.0)]
Keep only the pairs less than 1 radian apart:
run(f"SELECT id, similarity FROM {TABLE} WHERE acos(similarity) < 1.0 ORDER BY id LIMIT 10")
[(5, 1.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 acos() is always computed by the Query Engine:
- In the
SELECTlist, the scan reads the column and the Query Engine computesacos()for each row it returns. - In
WHERE, the scan sends the column for every row, and aFILTERstep in the Query Engine computesacos()and keeps the rows that match. No rows are skipped in storage.
A value outside -1 to 1 in any row the Query Engine computes acos() for cancels the whole query.
The plan of the WHERE example shows the FILTER step above the scan:
plan(f"SELECT id FROM {TABLE} WHERE acos(similarity) < 1.0")
┌───────────────────────────┐
│ PROJECTION │
│ ──────────────────── │
│ #1 │
│ │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ FILTER │
│ ──────────────────── │
│ (acos(similarity) < 1.0) │
│ │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ VAST_SCAN │
│ ──────────────────── │
│ Function: VAST_SCAN │
│ │
│ Projections: │
│ similarity │
│ id │
│ │
└───────────────────────────┘
Make it faster
- Compare the raw column with the cosine of the angle.
acos()gets smaller as its argument gets larger, soacos(similarity) < 1.0is the same condition assimilarity > cos(1.0). A function on constants is computed once, before the scan, so the second form is a comparison of the column with a constant, which is pushed down to VAST DataBase. Its plan has noFILTERstep:
plan(f"SELECT id FROM {TABLE} WHERE similarity > cos(1.0)")
run(f"SELECT id, similarity FROM {TABLE} WHERE similarity > cos(1.0) ORDER BY id LIMIT 10")
┌───────────────────────────┐
│ VAST_SCAN │
│ ──────────────────── │
│ Function: VAST_SCAN │
│ Projections: id │
│ │
└───────────────────────────┘
[(5, 1.0)]
- Store the angle if you filter on it often. Compute it when you write the rows and keep it in its own column: a condition on that column is pushed down, a condition on
acos()isn't.
Limitations and errors
- The argument must be from -1 to 1. Anything else fails with
ACOS is undefined outside [-1,1], and infinity fails withinput value inf is out of range for numeric function. One such row cancels the query. A cosine similarity computed in floating point can come out a tiny amount above 1 or below -1; clamp it into range first with greatest and least, or keep those rows out with a pushed-down range on the raw column:
run("SELECT acos(1.0000000001)")
run("SELECT acos(least(greatest(1.0000000001, -1.0), 1.0))")
run(f"SELECT id, acos(similarity) FROM {TABLE} WHERE similarity BETWEEN -1 AND 1 ORDER BY id LIMIT 10")
OSError: ACOS is undefined outside [-1,1]
[(0.0,)]
[(1, 1.0471975511965979), (2, 2.0943951023931957), (4, 1.5707963267948966), (5, 0.0), (6, 3.141592653589793)]
- Numbers only. A
BLOB, a date, a boolean, a list or an interval fails withNo function matches the given name and argument types; the error lists the one formacoshas,acos(DOUBLE) -> DOUBLE. A string literal is converted toDOUBLEif it holds a number, and otherwise fails withCould not convert string 'abc' to DOUBLE. - One argument.
acos(1, 2)fails withNo function matches the given name and argument types 'acos(INTEGER_LITERAL, INTEGER_LITERAL)'. - Radians only. There's no degrees variant; use
degrees(acos(x)).
Similar functions
| Function | Returns | Result for 0.5 |
|---|---|---|
acos(x) |
the angle whose cosine is x, from 0 to pi |
1.0471975511965979 |
| asin | the angle whose sine is x, from -pi/2 to pi/2 |
0.5235987755982989 |
| atan | the angle whose tangent is x, from -pi/2 to pi/2 |
0.4636476090008061 |
| cos | the cosine of an angle: the inverse of acos |
cos(acos(0.5)) gives back about 0.5 |
| degrees | radians converted to degrees | degrees(acos(0.5)) is about 60 |
| pi | pi; pi() = acos(-1) is true |
run("SELECT acos(0.5), asin(0.5), atan(0.5), cos(acos(0.5)), degrees(acos(0.5))")
run("SELECT pi() = acos(-1)")
[(1.0471975511965979, 0.5235987755982989, 0.4636476090008061, 0.4999999999999999, 60.00000000000001)]
[(True,)]
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.
- Where filters run and pushdowns.
- EXPLAIN: how to read a plan.