Introduce inverse hyperbolic functions

#KT-4900

Improve accuracy of JS polyfills of hyperbolic functions and expm1/log1p
This commit is contained in:
Ilya Gorbunov
2017-08-30 21:00:30 +03:00
parent 232d1bd9ef
commit 044ccf1532
10 changed files with 634 additions and 29 deletions
@@ -140,6 +140,49 @@ public expect fun cosh(a: Double): Double
@SinceKotlin("1.2")
public expect fun tanh(a: Double): Double
/**
* Computes the inverse hyperbolic sine of the value [a].
*
* The returned value is `x` such that `sinh(x) == a`.
*
* Special cases:
*
* - `asinh(NaN)` is `NaN`
* - `asinh(+Inf)` is `+Inf`
* - `asinh(-Inf)` is `-Inf`
*/
@SinceKotlin("1.2")
public expect fun asinh(a: Double): Double
/**
* Computes the inverse hyperbolic cosine of the value [a].
*
* The returned value is positive `x` such that `cosh(x) == a`.
*
* Special cases:
*
* - `acosh(NaN)` is `NaN`
* - `acosh(x)` is `NaN` when `x < 1`
* - `acosh(+Inf)` is `+Inf`
*/
@SinceKotlin("1.2")
public expect fun acosh(a: Double): Double
/**
* Computes the inverse hyperbolic tangent of the value [a].
*
* The returned value is `x` such that `tanh(x) == a`.
*
* Special cases:
*
* - `tanh(NaN)` is `NaN`
* - `tanh(x)` is `NaN` when `x > 1` or `x < -1`
* - `tanh(1.0)` is `+Inf`
* - `tanh(-1.0)` is `-Inf`
*/
@SinceKotlin("1.2")
public expect fun atanh(a: Double): Double
/**
* Computes `sqrt(x^2 + y^2)` without intermediate overflow or underflow.
*
@@ -527,6 +570,49 @@ public expect fun cosh(a: Float): Float
@SinceKotlin("1.2")
public expect fun tanh(a: Float): Float
/**
* Computes the inverse hyperbolic sine of the value [a].
*
* The returned value is `x` such that `sinh(x) == a`.
*
* Special cases:
*
* - `asinh(NaN)` is `NaN`
* - `asinh(+Inf)` is `+Inf`
* - `asinh(-Inf)` is `-Inf`
*/
@SinceKotlin("1.2")
public expect fun asinh(a: Float): Float
/**
* Computes the inverse hyperbolic cosine of the value [a].
*
* The returned value is positive `x` such that `cosh(x) == a`.
*
* Special cases:
*
* - `acosh(NaN)` is `NaN`
* - `acosh(x)` is `NaN` when `x < 1`
* - `acosh(+Inf)` is `+Inf`
*/
@SinceKotlin("1.2")
public expect fun acosh(a: Float): Float
/**
* Computes the inverse hyperbolic tangent of the value [a].
*
* The returned value is `x` such that `tanh(x) == a`.
*
* Special cases:
*
* - `tanh(NaN)` is `NaN`
* - `tanh(x)` is `NaN` when `x > 1` or `x < -1`
* - `tanh(1.0)` is `+Inf`
* - `tanh(-1.0)` is `-Inf`
*/
@SinceKotlin("1.2")
public expect fun atanh(a: Float): Float
/**
* Computes `sqrt(x^2 + y^2)` without intermediate overflow or underflow.
*
+155
View File
@@ -32,6 +32,12 @@ public const val E: Double = nativeMath.E
/** Natural logarithm of 2.0, used to compute [log2] function */
private val LN2: Double = ln(2.0)
private val epsilon: Double = nativeMath.ulp(1.0)
private val taylor_2_bound = nativeMath.sqrt(epsilon)
private val taylor_n_bound = nativeMath.sqrt(taylor_2_bound)
private val upper_taylor_2_bound = 1 / taylor_2_bound
private val upper_taylor_n_bound = 1 / taylor_n_bound
// ================ Double Math ========================================
/** Computes the sine of the angle [a] given in radians.
@@ -155,6 +161,109 @@ public inline fun cosh(a: Double): Double = nativeMath.cosh(a)
@InlineOnly
public inline fun tanh(a: Double): Double = nativeMath.tanh(a)
// Inverse hyperbolic function implementations derived from boost special math functions,
// Copyright Eric Ford & Hubert Holin 2001.
/**
* Computes the inverse hyperbolic sine of the value [a].
*
* The returned value is `x` such that `sinh(x) == a`.
*
* Special cases:
*
* - `asinh(NaN)` is `NaN`
* - `asinh(+Inf)` is `+Inf`
* - `asinh(-Inf)` is `-Inf`
*/
@SinceKotlin("1.2")
public fun asinh(a: Double): Double =
when {
a >= +taylor_n_bound ->
if (a > upper_taylor_n_bound) {
if (a > upper_taylor_2_bound) {
// approximation by laurent series in 1/x at 0+ order from -1 to 0
nativeMath.log(a) + LN2
} else {
// approximation by laurent series in 1/x at 0+ order from -1 to 1
nativeMath.log(a * 2 + (1 / (a * 2)))
}
} else {
nativeMath.log(a + nativeMath.sqrt(a * a + 1))
}
a <= -taylor_n_bound -> -asinh(-a)
else -> {
// approximation by taylor series in x at 0 up to order 2
var result = a;
if (nativeMath.abs(a) >= taylor_2_bound) {
// approximation by taylor series in x at 0 up to order 4
result -= (a * a * a) / 6
}
result
}
}
/**
* Computes the inverse hyperbolic cosine of the value [a].
*
* The returned value is positive `x` such that `cosh(x) == a`.
*
* Special cases:
*
* - `acosh(NaN)` is `NaN`
* - `acosh(x)` is `NaN` when `x < 1`
* - `acosh(+Inf)` is `+Inf`
*/
@SinceKotlin("1.2")
public fun acosh(a: Double): Double =
when {
a < 1 -> Double.NaN
a > upper_taylor_2_bound ->
// approximation by laurent series in 1/x at 0+ order from -1 to 0
nativeMath.log(a) + LN2
a - 1 >= taylor_n_bound ->
nativeMath.log(a + nativeMath.sqrt(a * a - 1))
else -> {
val y = nativeMath.sqrt(a - 1)
// approximation by taylor series in y at 0 up to order 2
var result = y
if (y >= taylor_2_bound) {
// approximation by taylor series in y at 0 up to order 4
result -= (y * y * y) / 12
}
nativeMath.sqrt(2.0) * result
}
}
/**
* Computes the inverse hyperbolic tangent of the value [a].
*
* The returned value is `x` such that `tanh(x) == a`.
*
* Special cases:
*
* - `tanh(NaN)` is `NaN`
* - `tanh(x)` is `NaN` when `x > 1` or `x < -1`
* - `tanh(1.0)` is `+Inf`
* - `tanh(-1.0)` is `-Inf`
*/
@SinceKotlin("1.2")
public fun atanh(x: Double): Double {
if (nativeMath.abs(x) < taylor_n_bound) {
var result = x
if (nativeMath.abs(x) > taylor_2_bound) {
result += (x * x * x) / 3
}
return result
}
return nativeMath.log((1 + x) / (1 - x)) / 2
}
/**
* Computes `sqrt(x^2 + y^2)` without intermediate overflow or underflow.
*
@@ -640,6 +749,52 @@ public inline fun cosh(a: Float): Float = nativeMath.cosh(a.toDouble()).toFloat(
@InlineOnly
public inline fun tanh(a: Float): Float = nativeMath.tanh(a.toDouble()).toFloat()
/**
* Computes the inverse hyperbolic sine of the value [a].
*
* The returned value is `x` such that `sinh(x) == a`.
*
* Special cases:
*
* - `asinh(NaN)` is `NaN`
* - `asinh(+Inf)` is `+Inf`
* - `asinh(-Inf)` is `-Inf`
*/
@SinceKotlin("1.2")
@InlineOnly
public inline fun asinh(a: Float): Float = asinh(a.toDouble()).toFloat()
/**
* Computes the inverse hyperbolic cosine of the value [a].
*
* The returned value is positive `x` such that `cosh(x) == a`.
*
* Special cases:
*
* - `acosh(NaN)` is `NaN`
* - `acosh(x)` is `NaN` when `x < 1`
* - `acosh(+Inf)` is `+Inf`
*/
@SinceKotlin("1.2")
@InlineOnly
public inline fun acosh(a: Float): Float = acosh(a.toDouble()).toFloat()
/**
* Computes the inverse hyperbolic tangent of the value [a].
*
* The returned value is `x` such that `tanh(x) == a`.
*
* Special cases:
*
* - `tanh(NaN)` is `NaN`
* - `tanh(x)` is `NaN` when `x > 1` or `x < -1`
* - `tanh(1.0)` is `+Inf`
* - `tanh(-1.0)` is `-Inf`
*/
@SinceKotlin("1.2")
@InlineOnly
public inline fun atanh(a: Float): Float = atanh(a.toDouble()).toFloat()
/**
* Computes `sqrt(x^2 + y^2)` without intermediate overflow or underflow.
*
+91
View File
@@ -79,17 +79,60 @@ class DoubleMathTest {
@Test fun hyperbolic() {
assertEquals(Double.POSITIVE_INFINITY, sinh(Double.POSITIVE_INFINITY))
assertEquals(Double.NEGATIVE_INFINITY, sinh(Double.NEGATIVE_INFINITY))
assertTrue(sinh(Double.MIN_VALUE) != 0.0)
assertTrue(sinh(710.0).isFinite())
assertTrue(sinh(-710.0).isFinite())
assertTrue(sinh(Double.NaN).isNaN())
assertEquals(Double.POSITIVE_INFINITY, cosh(Double.POSITIVE_INFINITY))
assertEquals(Double.POSITIVE_INFINITY, cosh(Double.NEGATIVE_INFINITY))
assertTrue(cosh(710.0).isFinite())
assertTrue(cosh(-710.0).isFinite())
assertTrue(cosh(Double.NaN).isNaN())
assertAlmostEquals(1.0, tanh(Double.POSITIVE_INFINITY))
assertAlmostEquals(-1.0, tanh(Double.NEGATIVE_INFINITY))
assertTrue(tanh(Double.MIN_VALUE) != 0.0)
assertTrue(tanh(Double.NaN).isNaN())
}
@Test fun inverseHyperbolicSin() {
for (exact in listOf(Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, 0.0, Double.MIN_VALUE, -Double.MIN_VALUE, 0.00001)) {
assertEquals(exact, asinh(sinh(exact)))
}
for (approx in listOf(Double.MIN_VALUE, 0.1, 1.0, 100.0, 710.0)) {
assertAlmostEquals(approx, asinh(sinh(approx)))
assertAlmostEquals(-approx, asinh(sinh(-approx)))
}
assertTrue(asinh(Double.NaN).isNaN())
}
@Test fun inverseHyperbolicCos() {
for (exact in listOf(Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, 0.0)) {
assertEquals(abs(exact), acosh(cosh(exact)))
}
for (approx in listOf(Double.MIN_VALUE, 0.00001, 1.0, 100.0, 710.0)) {
assertAlmostEquals(approx, acosh(cosh(approx)))
assertAlmostEquals(approx, acosh(cosh(-approx)))
}
for (invalid in listOf(-1.0, 0.0, 0.99999, Double.NaN)) {
assertTrue(acosh(invalid).isNaN())
}
}
@Test fun inverseHyperbolicTan() {
for (exact in listOf(Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, 0.0, Double.MIN_VALUE, -Double.MIN_VALUE)) {
assertEquals(exact, atanh(tanh(exact)))
}
for (approx in listOf(0.00001)) {
assertAlmostEquals(approx, atanh(tanh(approx)))
}
for (invalid in listOf(-1.00001, 1.00001, Double.NaN, Double.MAX_VALUE, -Double.MAX_VALUE, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY)) {
assertTrue(atanh(invalid).isNaN())
}
}
@Test fun powers() {
assertEquals(5.0, hypot(3.0, 4.0))
assertEquals(Double.POSITIVE_INFINITY, hypot(Double.NEGATIVE_INFINITY, Double.NaN))
@@ -117,6 +160,8 @@ class DoubleMathTest {
assertEquals(Double.POSITIVE_INFINITY, exp(Double.POSITIVE_INFINITY))
assertEquals(0.0, expm1(0.0))
assertEquals(Double.MIN_VALUE, expm1(Double.MIN_VALUE))
assertEquals(0.00010000500016667084, expm1(1e-4))
assertEquals(-1.0, expm1(Double.NEGATIVE_INFINITY))
assertEquals(Double.POSITIVE_INFINITY, expm1(Double.POSITIVE_INFINITY))
}
@@ -151,6 +196,8 @@ class DoubleMathTest {
assertTrue(ln1p(Double.NaN).isNaN())
assertTrue(ln1p(-1.1).isNaN())
assertEquals(0.0, ln1p(0.0))
assertEquals(9.999995000003334e-7, ln1p(1e-6))
assertEquals(Double.MIN_VALUE, ln1p(Double.MIN_VALUE))
assertEquals(Double.NEGATIVE_INFINITY, ln1p(-1.0))
}
@@ -323,17 +370,61 @@ class FloatMathTest {
@Test fun hyperbolic() {
assertEquals(Float.POSITIVE_INFINITY, sinh(Float.POSITIVE_INFINITY))
assertEquals(Float.NEGATIVE_INFINITY, sinh(Float.NEGATIVE_INFINITY))
assertTrue(sinh(Float.MIN_VALUE) != 0.0F)
assertTrue(sinh(89.0F).isFinite())
assertTrue(sinh(-89.0F).isFinite())
assertTrue(sinh(Float.NaN).isNaN())
assertEquals(Float.POSITIVE_INFINITY, cosh(Float.POSITIVE_INFINITY))
assertEquals(Float.POSITIVE_INFINITY, cosh(Float.NEGATIVE_INFINITY))
assertTrue(cosh(89.0F).isFinite())
assertTrue(cosh(-89.0F).isFinite())
assertTrue(cosh(Float.NaN).isNaN())
assertAlmostEquals(1.0F, tanh(Float.POSITIVE_INFINITY))
assertAlmostEquals(-1.0F, tanh(Float.NEGATIVE_INFINITY))
assertTrue(tanh(Float.MIN_VALUE) != 0.0F)
assertTrue(tanh(Float.NaN).isNaN())
}
@Test fun inverseHyperbolicSin() {
for (exact in listOf(Float.POSITIVE_INFINITY, Float.NEGATIVE_INFINITY, 0.0F, Float.MIN_VALUE, -Float.MIN_VALUE, 0.00001F)) {
assertEquals(exact, asinh(sinh(exact)))
}
for (approx in listOf(Float.MIN_VALUE, 0.1F, 1.0F, 89.0F)) {
assertAlmostEquals(approx, asinh(sinh(approx)))
assertAlmostEquals(-approx, asinh(sinh(-approx)))
}
assertTrue(asinh(Float.NaN).isNaN())
}
@Test fun inverseHyperbolicCos() {
for (exact in listOf(Float.POSITIVE_INFINITY, Float.NEGATIVE_INFINITY, 0.0F)) {
assertEquals(abs(exact), acosh(cosh(exact)))
}
for (approx in listOf(Float.MIN_VALUE, 0.1F, 1.0F, 89.0F)) {
assertAlmostEquals(approx, acosh(cosh(approx)))
assertAlmostEquals(approx, acosh(cosh(-approx)))
}
for (invalid in listOf(-1.0F, 0.0F, 0.99999F, Float.NaN)) {
assertTrue(acosh(invalid).isNaN())
}
}
@Test fun inverseHyperbolicTan() {
for (exact in listOf(Float.POSITIVE_INFINITY, Float.NEGATIVE_INFINITY, 0.0F, Float.MIN_VALUE, -Float.MIN_VALUE)) {
assertEquals(exact, atanh(tanh(exact)))
}
for (approx in listOf(0.00001F)) {
assertAlmostEquals(approx, atanh(tanh(approx)))
}
for (invalid in listOf(-1.00001F, 1.00001F, Float.NaN, Float.MAX_VALUE, -Float.MAX_VALUE, Float.NEGATIVE_INFINITY, Float.POSITIVE_INFINITY)) {
assertTrue(atanh(invalid).isNaN())
}
}
@Test fun powers() {
assertEquals(5.0F, hypot(3.0F, 4.0F))
assertEquals(Float.POSITIVE_INFINITY, hypot(Float.NEGATIVE_INFINITY, Float.NaN))
@@ -3016,6 +3016,9 @@ public abstract interface class kotlin/jvm/internal/markers/KMutableSet : kotlin
public final class kotlin/math/MathKt {
public static final field E D
public static final field PI D
public static final fun acosh (D)D
public static final fun asinh (D)D
public static final fun atanh (D)D
public static final fun getSign (I)I
public static final fun getSign (J)I
public static final fun log (DD)D
@@ -2024,6 +2024,9 @@ public final class kotlin/io/TextStreamsKt {
public final class kotlin/math/MathKt {
public static final field E D
public static final field PI D
public static final fun acosh (D)D
public static final fun asinh (D)D
public static final fun atanh (D)D
public static final fun getSign (I)I
public static final fun getSign (J)I
public static final fun log (DD)D