Skip to content

Commit

Permalink
feat(frontend): Acosd function (#9876)
Browse files Browse the repository at this point in the history
  • Loading branch information
CAJan93 authored Jul 26, 2024
1 parent f389a77 commit 849bed0
Show file tree
Hide file tree
Showing 7 changed files with 183 additions and 24 deletions.
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 @@ -1011,6 +1011,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

0 comments on commit 849bed0

Please sign in to comment.