Applies to VAST Cluster 5.5.
Functions › Numeric and math functions › Hyperbolic functions. Returns the inverse hyperbolic tangent of x: the value whose hyperbolic tangent is x. x must be from -1 to 1.
Syntax
atanh(x)
| Argument | Type | Description |
|---|---|---|
x |
DOUBLE (any number) |
A value from -1 to 1. Integers, DECIMAL and FLOAT values are converted to DOUBLE. |
Returns: DOUBLE.
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_atanh. 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 seven correlation coefficients, each from -1 to 1. 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_atanh"'
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}.correlations"
run(f"CREATE SCHEMA IF NOT EXISTS {SCHEMA}")
run(f"CREATE TABLE IF NOT EXISTS {TABLE} (id BIGINT, r DOUBLE)")
run(f"INSERT INTO {TABLE} VALUES "
"(1, 0.5), (2, -0.5), (3, NULL), (4, 0.0), (5, 1.0), (6, -1.0), (7, 0.9)")
[]
[]
[(7,)]
What it does
atanh(x) is the inverse of tanh: for x between -1 and 1 it returns the value y with tanh(y) = x. It's an odd function, so atanh(-0.5) is -atanh(0.5), -0.5493061443340548. Results are rounded to the nearest DOUBLE, so a round trip can be off in the last digit: atanh(tanh(1)) is 0.9999999999999999.
A common use is the Fisher z-transform of a correlation coefficient r: atanh(r) turns correlations into values you can average, and tanh() turns the average back into a correlation.
NULLs and edge cases
| Input | Result |
|---|---|
NULL |
NULL |
0 |
0.0 |
0.5, -0.5 |
0.5493061443340548, -0.5493061443340548 |
-0.0::DOUBLE |
-0.0 |
5e-324::DOUBLE (the smallest DOUBLE above 0) |
5e-324 |
1 |
inf |
-1 |
inf (positive infinity, not -inf) |
'nan'::DOUBLE |
nan |
2, -2, 1.0000000001, 'inf'::DOUBLE or '-inf'::DOUBLE |
Error: ATANH is undefined outside [-1,1] |
'12' (a string) |
Converted to DOUBLE, then the same error |
'abc' |
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 'atanh(DATE)' |
An INTEGER, FLOAT or DECIMAL value |
Converted; the result is DOUBLE |
The same cases, as the cluster returns them. They use the table and helpers from Set up the examples:
run("SELECT atanh(NULL), atanh(0), atanh(0.5), atanh(-0.5), atanh(-0.0::DOUBLE), atanh(5e-324::DOUBLE)")
run("SELECT atanh(1), atanh(-1), atanh('nan'::DOUBLE)")
run("SELECT typeof(atanh(1::INTEGER)), typeof(atanh(0.5::FLOAT)), typeof(atanh(0.5))")
run("SELECT atanh(2)")
run("SELECT atanh(1.0000000001)")
run("SELECT atanh('inf'::DOUBLE)")
run("SELECT atanh('12')")
run("SELECT atanh('abc')")
run("SELECT atanh(DATE '2026-10-16')")
[(None, 0.0, 0.5493061443340548, -0.5493061443340548, -0.0, 5e-324)]
[(inf, inf, nan)]
[('DOUBLE', 'DOUBLE', 'DOUBLE')]
OSError: ATANH is undefined outside [-1,1]
OSError: ATANH is undefined outside [-1,1]
OSError: ATANH is undefined outside [-1,1]
OSError: ATANH is undefined outside [-1,1]
OSError: Could not convert string 'abc' to DOUBLE
InternalError: Binder Error: No function matches the given name and argument types 'atanh(DATE)'. You might need to add explicit type casts.
Examples
The transform of each correlation. Row 5 (1.0) and row 6 (-1.0) both give inf:
run(f"SELECT id, r, atanh(r) FROM {TABLE} ORDER BY id LIMIT 10")
[(1, 0.5, 0.5493061443340548), (2, -0.5, -0.5493061443340548), (3, None, None), (4, 0.0, 0.0), (5, 1.0, inf), (6, -1.0, inf), (7, 0.9, 1.4722194895832204)]
The average of the correlations strictly between -1 and 1, by the Fisher z-transform: transform, average, transform back:
run(f"SELECT tanh(avg(atanh(r))) FROM {TABLE} WHERE r > -1 AND r < 1")
[(0.3522891589092123,)]
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 atanh() of a column is always computed by the Query Engine:
- In the
SELECTlist, the scan reads the column and the Query Engine computesatanh()for each row it returns. - In
WHERE, the scan sends the column for every row, and aFILTERstep in the Query Engine computesatanh()and keeps the rows that match.
The plan of a WHERE condition on atanh(r) shows the FILTER step above the scan:
plan(f"SELECT id FROM {TABLE} WHERE atanh(r) > 0.5")
┌───────────────────────────┐
│ PROJECTION │
│ ──────────────────── │
│ #1 │
│ │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ FILTER │
│ ──────────────────── │
│ (atanh(r) > 0.5) │
│ │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ VAST_SCAN │
│ ──────────────────── │
│ Function: VAST_SCAN │
│ │
│ Projections: │
│ r │
│ id │
│ │
└───────────────────────────┘
Make it faster
- Compare the raw column instead.
atanh()only grows, soatanh(r) > 0.5keeps the same rows asr > tanh(0.5). A function of constants is computed once, before the scan, so that condition can be pushed down to VAST DataBase:
plan(f"SELECT id FROM {TABLE} WHERE r > tanh(0.5)")
run(f"SELECT id FROM {TABLE} WHERE r > tanh(0.5) ORDER BY id LIMIT 10")
┌───────────────────────────┐
│ VAST_SCAN │
│ ──────────────────── │
│ Function: VAST_SCAN │
│ Projections: id │
│ │
└───────────────────────────┘
[(1,), (5,), (7,)]
Limitations and errors
- One value outside -1 to 1 cancels the whole query. The error is
ATANH is undefined outside [-1,1], and it happens on any row, here row 7, wherer * 2is1.8. Return NULL for those rows withCASE:
run(f"SELECT id, atanh(r * 2) FROM {TABLE} ORDER BY id LIMIT 10")
run(f"SELECT id, CASE WHEN abs(r * 2) <= 1 THEN atanh(r * 2) END FROM {TABLE} ORDER BY id LIMIT 10")
OSError: ATANH is undefined outside [-1,1]
[(1, inf), (2, inf), (3, None), (4, 0.0), (5, None), (6, None), (7, None)]
atanh(-1)isinf, not-inf. If the sign matters, test for -1 yourself:
run(f"SELECT id, CASE WHEN r = -1 THEN '-inf'::DOUBLE ELSE atanh(r) END FROM {TABLE} ORDER BY id LIMIT 10")
[(1, 0.5493061443340548), (2, -0.5493061443340548), (3, None), (4, 0.0), (5, inf), (6, -inf), (7, 1.4722194895832204)]
- Infinity isn't accepted.
atanh('inf'::DOUBLE)andatanh('-inf'::DOUBLE)fail with the same error; NaN gives NaN. - One argument, numbers only. A second argument, a date, a boolean, a
BLOB, a list or an interval fails withNo function matches the given name and argument types. A string is converted toDOUBLEfirst, and fails withCould not convert string ... to DOUBLEif it isn't a number.
Similar functions
| Function | Computes | Relation to atanh |
|---|---|---|
| tanh | hyperbolic tangent | its inverse: tanh(atanh(x)) gives back about x |
| asinh | inverse hyperbolic sine | takes any number |
| acosh | inverse hyperbolic cosine | takes values from 1 up |
| ln | natural logarithm | atanh(x) is, up to rounding, 0.5 * ln((1 + x) / (1 - x)) |
run("SELECT atanh(0.5), tanh(atanh(0.5)), 0.5 * ln((1 + 0.5) / (1 - 0.5)), asinh(0.5), acosh(1.5)")
[(0.5493061443340548, 0.49999999999999994, 0.5493061443340549, 0.48121182505960347, 0.9624236501192069)]
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 hyperbolic functions.
- Where filters run and pushdowns.
- EXPLAIN: how to read a plan.