Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(frontend): Acosd function #9876

Merged
merged 20 commits into from
Jul 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions e2e_test/batch/functions/trigonometric_funcs.slt.part
Original file line number Diff line number Diff line change
Expand Up @@ -659,3 +659,48 @@ query R
SELECT abs(abs(asind(-0.5) + 30)) < 1e-14
----
t

query R
SELECT abs(acosd(-1) - 180.0) < 1e-14
----
t

query R
SELECT abs(acosd(-0.75) - 138.59037789072914) < 1e-14
----
t

query R
SELECT abs(acosd(-0.5) - 120.0) < 1e-14
----
t

query R
SELECT abs(acosd(-0.25) - 104.47751218592992) < 1e-14
----
t

query R
SELECT abs(acosd(0) - 90.0) < 1e-14
----
t

query R
SELECT abs(acosd(0.25) - 75.52248781407008) < 1e-14
----
t

query R
SELECT abs(acosd(0.5) - 59.99999999999999) < 1e-14
----
t

query R
SELECT abs(acosd(0.75) - 41.40962210927086) < 1e-14
----
t

query R
SELECT abs(acosd(1) - 0.0) < 1e-14
----
t
1 change: 1 addition & 0 deletions proto/expr.proto
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ message ExprNode {
ACOSH = 265;
ATANH = 266;
SINH = 267;
ACOSD = 268;
// skips 268,269,270 so that acosd, atand, atan2d are close to others
TRUNC = 271;
LN = 272;
Expand Down
146 changes: 128 additions & 18 deletions src/expr/impl/src/scalar/trigonometric.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,30 +95,55 @@ pub fn atanh_f64(input: F64) -> F64 {
f64::atanh(input.0).into()
}

// Radians per degree, a.k.a. PI / 180
static DEGREE_THIRTY: f64 = 30.0;
static DEGREE_FORTY_FIVE: f64 = 45.0;
static DEGREE_SIXTY: f64 = 60.0;
static DEGREE_ONE_HALF: f64 = 0.5;
static DEGREE_ONE: f64 = 1.0;
static RADIANS_PER_DEGREE: f64 = 0.017_453_292_519_943_295;

// Constants we use to get more accurate results.
// Depend on the machine and have to be evaluated at runtime
// See PSQL: https://github.com/postgres/postgres/blob/78ec02d612a9b69039ec2610740f738968fe144d/src/backend/utils/adt/float.c#L2024
static SIND_30: f64 = 0.499_999_999_999_999_94;
static ONE_MINUS_COSD_60: f64 = 0.499_999_999_999_999_9;
static TAND_45: f64 = 1.0;
static COTD_45: f64 = 1.0;
static ASIN_0_5: f64 = 0.523_598_775_598_298_8;
fn sind_30() -> f64 {
f64::sin(DEGREE_THIRTY * RADIANS_PER_DEGREE)
}

fn one_minus_cosd_60() -> f64 {
DEGREE_ONE - f64::cos(DEGREE_SIXTY * RADIANS_PER_DEGREE)
}

fn tand_45() -> f64 {
f64::tan(DEGREE_FORTY_FIVE * RADIANS_PER_DEGREE)
}

fn cotd_45() -> f64 {
f64::cos(DEGREE_FORTY_FIVE * RADIANS_PER_DEGREE)
/ f64::sin(DEGREE_FORTY_FIVE * RADIANS_PER_DEGREE)
}

fn asin_0_5() -> f64 {
f64::asin(DEGREE_ONE_HALF)
}

fn acos_0_5() -> f64 {
f64::acos(DEGREE_ONE_HALF)
}

// returns the cosine of an angle that lies between 0 and 60 degrees. This will return exactly 1
// when xi s 0, and exactly 0.5 when x is 60 degrees.
fn cosd_0_to_60(x: f64) -> f64 {
// https://github.com/postgres/postgres/blob/REL_15_2/src/backend/utils/adt/float.c
let one_minus_cos_x: f64 = 1.0 - f64::cos(x * RADIANS_PER_DEGREE);
1.0 - (one_minus_cos_x / ONE_MINUS_COSD_60) / 2.0
1.0 - (one_minus_cos_x / one_minus_cosd_60()) / 2.0
}

// returns the sine of an angle that lies between 0 and 30 degrees. This will return exactly 0 when
// x is 0, and exactly 0.5 when x is 30 degrees.
fn sind_0_to_30(x: f64) -> f64 {
// https://github.com/postgres/postgres/blob/REL_15_2/src/backend/utils/adt/float.c
let sin_x = f64::sin(x * RADIANS_PER_DEGREE);
(sin_x / SIND_30) / 2.0
(sin_x / sind_30()) / 2.0
}

// returns the cosine of an angle in the first quadrant (0 to 90 degrees).
Expand Down Expand Up @@ -237,6 +262,20 @@ pub fn cotd_f64(input: F64) -> F64 {
let mut arg1 = input.0 % 360.0;
let mut sign = 1.0;

// hardcoding exact results.
if arg1 == 45.0 {
return F64::from(1.0);
}
if arg1 == 135.0 {
return F64::from(-1.0);
}
if arg1 == 225. {
return F64::from(1.0);
}
if arg1 == 315.0 {
return F64::from(-1.0);
}

if arg1 < 0.0 {
// cotd(-x) = -cotd(x)
arg1 = -arg1;
Expand All @@ -256,7 +295,7 @@ pub fn cotd_f64(input: F64) -> F64 {
}

let cot_arg1 = cosd_q1(arg1) / sind_q1(arg1);
let result = sign * (cot_arg1 / COTD_45);
let result = sign * (cot_arg1 / cotd_45());

// On some machines we get cotd(270) = minus zero, but this isn't always
// true. For portability, and because the user constituency for this
Expand All @@ -277,6 +316,20 @@ pub fn tand_f64(input: F64) -> F64 {
let mut arg1 = input.0 % 360.0;
let mut sign = 1.0;

// hardcoding exact results.
if arg1 == 45.0 {
return F64::from(1.0);
}
if arg1 == 135.0 {
return F64::from(-1.0);
}
if arg1 == 225. {
return F64::from(1.0);
}
if arg1 == 315.0 {
return F64::from(-1.0);
}

if arg1 < 0.0 {
// tand(-x) = -tand(x)
arg1 = -arg1;
Expand All @@ -296,7 +349,7 @@ pub fn tand_f64(input: F64) -> F64 {
}

let tan_arg1 = sind_q1(arg1) / cosd_q1(arg1);
let result = sign * (tan_arg1 / TAND_45);
let result = sign * (tan_arg1 / tand_45());

// On some machines we get tand(180) = minus zero, but this isn't always true. For portability,
// and because the user constituency for this function probably doesn't want minus zero, force
Expand All @@ -315,11 +368,11 @@ pub fn asind_q1(x: f64) -> f64 {
// monotonic functionover the full range.
if x <= 0.5 {
let asin_x = f64::asin(x);
return (asin_x / ASIN_0_5) * 30.0;
return (asin_x / asin_0_5()) * 30.0;
}

let acos_x = f64::acos(x);
90.0 - (acos_x / ASIN_0_5) * 60.0
90.0 - (acos_x / acos_0_5()) * 60.0
}

#[function("asind(float8) -> float8")]
Expand All @@ -332,7 +385,7 @@ pub fn asind_f64(input: F64) -> F64 {
}

// Return NaN if input is out of range. Slightly different from PSQL implementation
if input.0 < -1.0 || input.0 > 1.0 {
if !(-1.0..=1.0).contains(&arg1) {
return F64::from(f64::NAN);
}

Expand All @@ -348,6 +401,42 @@ pub fn asind_f64(input: F64) -> F64 {
result.into()
}

// returns the inverse cosine of x in degrees, for x in the range [0, 1]. The result is an angle in
// the first quadrant --- [0, 90] degrees. For the 3 special case inputs (0, 0.5 and 1), this
// function will return exact values (0, 60 and 90 degrees respectively).
fn acosd_q1(x: f64) -> f64 {
// Stitch together inverse sine and cosine functions for the ranges [0, 0.5] and (0.5, 1]. Each
// expression below is guaranteed to return exactly 60 for x=0.5, so the result is a continuous
// monotonic function over the full range.
if x <= 0.5 {
let asin_x = f64::asin(x);
return 90.0 - (asin_x / asin_0_5()) * 30.0;
}
let acos_x = f64::acos(x);
(acos_x / acos_0_5()) * 60.0
}

#[function("acosd(float8) -> float8")]
pub fn acosd_f64(input: F64) -> F64 {
let arg1 = input.0;

// Return NaN if input is NaN or Infinite. Slightly different from PSQL implementation
if input.0.is_nan() || input.0.is_infinite() || !(-1.0..=1.0).contains(&arg1) {
return F64::from(f64::NAN);
}

let result = if arg1 >= 0.0 {
acosd_q1(arg1)
} else {
90.0 + asind_q1(-arg1)
};

if result.is_infinite() {
return F64::from(f64::NAN);
}
result.into()
}

#[function("degrees(float8) -> float8")]
pub fn degrees_f64(input: F64) -> F64 {
input.0.to_degrees().into()
Expand All @@ -360,18 +449,19 @@ pub fn radians_f64(input: F64) -> F64 {

#[cfg(test)]
mod tests {
use std::f64::consts::PI;

use risingwave_common::types::FloatExt;

use crate::scalar::trigonometric::*;

fn precision() -> f64 {
1e-13
1e-12
}

/// numbers are equal within a rounding error
fn assert_similar(lhs: F64, rhs: F64) {
if lhs == F64::from(f64::NAN) && rhs == F64::from(f64::NAN) {
return;
}
let x = (lhs.0 - rhs.0).abs() <= precision();
assert!(
x,
Expand All @@ -385,7 +475,7 @@ mod tests {
#[test]
fn test_degrees() {
let d = F64::from(180);
let pi = F64::from(PI);
let pi = F64::from(core::f64::consts::PI);

// sind
assert_similar(sin_f64(50_f64.to_radians().into()), sind_f64(F64::from(50)));
Expand Down Expand Up @@ -454,7 +544,6 @@ mod tests {
tand_f64(F64::from(-10)),
);
assert_similar(tan_f64(50_f64.to_radians().into()), tand_f64(F64::from(50)));
// we get slightly different result here, which is why I reduce the required accuracy
assert!(
(tan_f64(250_f64.to_radians().into()) - tand_f64(F64::from(250)))
.0
Expand All @@ -471,8 +560,17 @@ mod tests {
assert_similar(asind_f64(F64::from(-0.5)), F64::from(-30));
assert_similar(asind_f64(F64::from(0)), F64::from(0));
assert_similar(asind_f64(F64::from(0.5)), F64::from(30));
assert_similar(asind_f64(F64::from(0.75)), F64::from(48.590377890729));
assert_similar(asind_f64(F64::from(1)), F64::from(90));

// acosd
assert_eq!(acosd_f64(F64::from(-1)), F64::from(180));
assert_similar(acosd_f64(F64::from(-0.75)), F64::from(138.59037789072914));
assert_eq!(acosd_f64(F64::from(-0.5)), F64::from(120));
assert_eq!(acosd_f64(F64::from(0.0)), F64::from(90));
assert_eq!(acosd_f64(F64::from(0.5)), F64::from(60));
assert_eq!(acosd_f64(F64::from(1)), F64::from(0));

// exact matches
assert!(tand_f64(F64::from(-270)).0.is_infinite());
assert_eq!(tand_f64(F64::from(-180)), 0.0);
Expand Down Expand Up @@ -576,4 +674,16 @@ mod tests {
atanh_f64(F64::from(x.powi(2) - 1.0) / F64::from(x.powi(2) + 1.0)),
);
}

#[test]
fn test_exact() {
assert_eq!(cotd_f64(F64::from(135.0)).0, -1.0);
assert_eq!(cotd_f64(F64::from(225.0)).0, 1.0);
assert_eq!(cotd_f64(F64::from(315.0)).0, -1.0);
assert_eq!(cotd_f64(F64::from(45.0)).0, 1.0);
assert_eq!(tand_f64(F64::from(45.0)).0, 1.0);
assert_eq!(tand_f64(F64::from(135.0)).0, -1.0);
assert_eq!(tand_f64(F64::from(225.0)).0, 1.0);
assert_eq!(tand_f64(F64::from(315.0)).0, -1.0);
}
}
1 change: 1 addition & 0 deletions src/frontend/src/binder/expr/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -978,6 +978,7 @@ impl Binder {
("acosh", raw_call(ExprType::Acosh)),
("atanh", raw_call(ExprType::Atanh)),
("asind", raw_call(ExprType::Asind)),
("acosd", raw_call(ExprType::Acosd)),
("degrees", raw_call(ExprType::Degrees)),
("radians", raw_call(ExprType::Radians)),
("sqrt", raw_call(ExprType::Sqrt)),
Expand Down
1 change: 1 addition & 0 deletions src/frontend/src/expr/pure.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ impl ExprVisitor for ImpureAnalyzer {
| Type::Cot
| Type::Asin
| Type::Acos
| Type::Acosd
| Type::Atan
| Type::Atan2
| Type::Sqrt
Expand Down
1 change: 1 addition & 0 deletions src/frontend/src/optimizer/plan_expr_visitor/strong.rs
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ impl Strong {
| ExprType::Cot
| ExprType::Asin
| ExprType::Acos
| ExprType::Acosd
| ExprType::Atan
| ExprType::Atan2
| ExprType::Sind
Expand Down
12 changes: 6 additions & 6 deletions src/tests/regress/data/sql/float8.sql
Original file line number Diff line number Diff line change
Expand Up @@ -284,12 +284,12 @@ SELECT x,
FROM (VALUES (0), (45), (90), (135), (180),
(225), (270), (315), (360)) AS t(x);

--@ SELECT x,
--@ asind(x),
--@ asind(x) IN (-90,-30,0,30,90) AS asind_exact,
--@ acosd(x),
--@ acosd(x) IN (0,60,90,120,180) AS acosd_exact
--@ FROM (VALUES (-1), (-0.5), (0), (0.5), (1)) AS t(x);
SELECT x,
asind(x),
asind(x) IN (-90,-30,0,30,90) AS asind_exact,
acosd(x),
acosd(x) IN (0,60,90,120,180) AS acosd_exact
FROM (VALUES (-1), (-0.5), (0), (0.5), (1)) AS t(x);

--@ SELECT x,
--@ atand(x),
Expand Down
Loading