From 044ccf1532c3a4e7e8956a4862a0dc5099324af3 Mon Sep 17 00:00:00 2001 From: Ilya Gorbunov Date: Wed, 30 Aug 2017 21:00:30 +0300 Subject: [PATCH] Introduce inverse hyperbolic functions #KT-4900 Improve accuracy of JS polyfills of hyperbolic functions and expm1/log1p --- js/js.libraries/src/core/js.math.kt | 6 + js/js.libraries/src/core/math.kt | 92 +++++++++ js/js.libraries/src/js/polyfills.js | 194 +++++++++++++++--- libraries/stdlib/common/src/kotlin/MathH.kt | 86 ++++++++ libraries/stdlib/src/kotlin/util/MathJVM.kt | 155 ++++++++++++++ libraries/stdlib/test/numbers/MathTest.kt | 91 ++++++++ .../kotlin-stdlib-runtime-merged.txt | 3 + .../reference-public-api/kotlin-stdlib.txt | 3 + license/README.md | 10 +- license/third_party/boost_LICENSE.txt | 23 +++ 10 files changed, 634 insertions(+), 29 deletions(-) create mode 100644 license/third_party/boost_LICENSE.txt diff --git a/js/js.libraries/src/core/js.math.kt b/js/js.libraries/src/core/js.math.kt index 7d0b52e2ebd..1dffb12ee3b 100644 --- a/js/js.libraries/src/core/js.math.kt +++ b/js/js.libraries/src/core/js.math.kt @@ -62,6 +62,12 @@ public external object Math { internal fun cosh(value: Double): Double @PublishedApi internal fun tanh(value: Double): Double + @PublishedApi + internal fun asinh(value: Double): Double + @PublishedApi + internal fun acosh(value: Double): Double + @PublishedApi + internal fun atanh(value: Double): Double @PublishedApi internal fun hypot(x: Double, y: Double): Double diff --git a/js/js.libraries/src/core/math.kt b/js/js.libraries/src/core/math.kt index 656dc41bddb..fdae25b2311 100644 --- a/js/js.libraries/src/core/math.kt +++ b/js/js.libraries/src/core/math.kt @@ -152,6 +152,52 @@ public inline fun cosh(a: Double): Double = nativeMath.cosh(a) @InlineOnly public inline fun tanh(a: Double): Double = nativeMath.tanh(a) +/** + * 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: Double): Double = nativeMath.asinh(a) + +/** + * 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: Double): Double = nativeMath.acosh(a) + +/** + * 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: Double): Double = nativeMath.atanh(a) + /** * Computes `sqrt(x^2 + y^2)` without intermediate overflow or underflow. * @@ -592,6 +638,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 = nativeMath.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 = nativeMath.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 = nativeMath.atanh(a.toDouble()).toFloat() + /** * Computes `sqrt(x^2 + y^2)` without intermediate overflow or underflow. * diff --git a/js/js.libraries/src/js/polyfills.js b/js/js.libraries/src/js/polyfills.js index 42025aea0f1..0cfd7ea42d8 100644 --- a/js/js.libraries/src/js/polyfills.js +++ b/js/js.libraries/src/js/polyfills.js @@ -52,24 +52,171 @@ if (typeof Math.trunc === "undefined") { return Math.ceil(x); }; } -if (typeof Math.sinh === "undefined") { - Math.sinh = function(x) { - var y = Math.exp(x); - return (y - 1 / y) / 2; - }; -} -if (typeof Math.cosh === "undefined") { - Math.cosh = function(x) { - var y = Math.exp(x); - return (y + 1 / y) / 2; - }; -} -if (typeof Math.tanh === "undefined") { - Math.tanh = function(x){ - var a = Math.exp(+x), b = Math.exp(-x); - return a == Infinity ? 1 : b == Infinity ? -1 : (a - b) / (a + b); - }; -} + +(function() { + var epsilon = 2.220446049250313E-16; + var taylor_2_bound = Math.sqrt(epsilon); + var taylor_n_bound = Math.sqrt(taylor_2_bound); + var upper_taylor_2_bound = 1/taylor_2_bound; + var upper_taylor_n_bound = 1/taylor_n_bound; + + if (typeof Math.sinh === "undefined") { + Math.sinh = function(x) { + if (Math.abs(x) < taylor_n_bound) { + var result = x; + if (Math.abs(x) > taylor_2_bound) { + result += (x * x * x) / 6; + } + return result; + } else { + var y = Math.exp(x); + var y1 = 1 / y; + if (!isFinite(y)) return Math.exp(x - Math.LN2); + if (!isFinite(y1)) return -Math.exp(-x - Math.LN2); + return (y - y1) / 2; + } + }; + } + if (typeof Math.cosh === "undefined") { + Math.cosh = function(x) { + var y = Math.exp(x); + var y1 = 1 / y; + if (!isFinite(y) || !isFinite(y1)) return Math.exp(Math.abs(x) - Math.LN2); + return (y + y1) / 2; + }; + } + + if (typeof Math.tanh === "undefined") { + Math.tanh = function(x){ + if (Math.abs(x) < taylor_n_bound) { + var result = x; + if (Math.abs(x) > taylor_2_bound) { + result -= (x * x * x) / 3; + } + return result; + } + else { + var a = Math.exp(+x), b = Math.exp(-x); + return a === Infinity ? 1 : b === Infinity ? -1 : (a - b) / (a + b); + } + }; + } + + // Inverse hyperbolic function implementations derived from boost special math functions, + // Copyright Eric Ford & Hubert Holin 2001. + + if (typeof Math.asinh === "undefined") { + var asinh = function(x) { + if (x >= +taylor_n_bound) + { + if (x > upper_taylor_n_bound) + { + if (x > upper_taylor_2_bound) + { + // approximation by laurent series in 1/x at 0+ order from -1 to 0 + return Math.log(x) + Math.LN2; + } + else + { + // approximation by laurent series in 1/x at 0+ order from -1 to 1 + return Math.log(x * 2 + (1 / (x * 2))); + } + } + else + { + return Math.log(x + Math.sqrt(x * x + 1)); + } + } + else if (x <= -taylor_n_bound) + { + return -asinh(-x); + } + else + { + // approximation by taylor series in x at 0 up to order 2 + var result = x; + if (Math.abs(x) >= taylor_2_bound) + { + var x3 = x * x * x; + // approximation by taylor series in x at 0 up to order 4 + result -= x3 / 6; + } + return result; + } + }; + Math.asinh = asinh; + } + if (typeof Math.acosh === "undefined") { + Math.acosh = function(x) { + if (x < 1) + { + return NaN; + } + else if (x - 1 >= taylor_n_bound) + { + if (x > upper_taylor_2_bound) + { + // approximation by laurent series in 1/x at 0+ order from -1 to 0 + return Math.log(x) + Math.LN2; + } + else + { + return Math.log(x + Math.sqrt(x * x - 1)); + } + } + else + { + var y = Math.sqrt(x - 1); + // approximation by taylor series in y at 0 up to order 2 + var result = y; + if (y >= taylor_2_bound) + { + var y3 = y * y * y; + // approximation by taylor series in y at 0 up to order 4 + result -= y3 / 12; + } + + return Math.sqrt(2) * result; + } + }; + } + if (typeof Math.atanh === "undefined") { + Math.atanh = function(x) { + if (Math.abs(x) < taylor_n_bound) { + var result = x; + if (Math.abs(x) > taylor_2_bound) { + result += (x * x * x) / 3; + } + return result; + } + return Math.log((1 + x) / (1 - x)) / 2; + }; + } + if (typeof Math.log1p === "undefined") { + Math.log1p = function(x) { + if (Math.abs(x) < taylor_n_bound) { + var x2 = x * x; + var x3 = x2 * x; + var x4 = x3 * x; + // approximation by taylor series in x at 0 up to order 4 + return (-x4 / 4 + x3 / 3 - x2 / 2 + x); + } + return Math.log(x + 1); + }; + } + if (typeof Math.expm1 === "undefined") { + Math.expm1 = function(x) { + if (Math.abs(x) < taylor_n_bound) { + var x2 = x * x; + var x3 = x2 * x; + var x4 = x3 * x; + // approximation by taylor series in x at 0 up to order 4 + return (x4 / 24 + x3 / 6 + x2 / 2 + x); + } + return Math.exp(x) - 1; + }; + } +})(); if (typeof Math.hypot === "undefined") { Math.hypot = function() { var y = 0; @@ -94,16 +241,7 @@ if (typeof Math.log2 === "undefined") { return Math.log(x) * Math.LOG2E; }; } -if (typeof Math.log1p === "undefined") { - Math.log1p = function(x) { - return Math.log(x + 1); - }; -} -if (typeof Math.expm1 === "undefined") { - Math.expm1 = function(x) { - return Math.exp(x) - 1; - }; -} + // For HtmlUnit and PhantomJs if (typeof ArrayBuffer.isView === "undefined") { ArrayBuffer.isView = function(a) { diff --git a/libraries/stdlib/common/src/kotlin/MathH.kt b/libraries/stdlib/common/src/kotlin/MathH.kt index c48c086478f..b47642c2cd9 100644 --- a/libraries/stdlib/common/src/kotlin/MathH.kt +++ b/libraries/stdlib/common/src/kotlin/MathH.kt @@ -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. * diff --git a/libraries/stdlib/src/kotlin/util/MathJVM.kt b/libraries/stdlib/src/kotlin/util/MathJVM.kt index 19c047c4109..058630c7e34 100644 --- a/libraries/stdlib/src/kotlin/util/MathJVM.kt +++ b/libraries/stdlib/src/kotlin/util/MathJVM.kt @@ -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. * diff --git a/libraries/stdlib/test/numbers/MathTest.kt b/libraries/stdlib/test/numbers/MathTest.kt index 82fb88eced8..744c1ef89cc 100644 --- a/libraries/stdlib/test/numbers/MathTest.kt +++ b/libraries/stdlib/test/numbers/MathTest.kt @@ -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)) diff --git a/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt b/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt index fde47f04568..1352de47248 100644 --- a/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt +++ b/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt @@ -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 diff --git a/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib.txt b/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib.txt index dbde2a73a23..9863b5d95fd 100644 --- a/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib.txt +++ b/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib.txt @@ -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 diff --git a/license/README.md b/license/README.md index c361b5fd3cb..ada2b418fd1 100644 --- a/license/README.md +++ b/license/README.md @@ -34,6 +34,10 @@ the Kotlin IntelliJ IDEA plugin: - Path: js/js.libraries/src/js/long.js - License: Apache 2 (license/third_party/closure-compiler_LICENSE.txt) - Origin: Google Closure Library, Copyright 2009 The Closure Library Authors + + - Path: js/js.libraries/src/js/polyfills.js + - License: Boost Software License 1.0 (license/third_party/boost_LICENSE.txt) + - Origin: Derived from boost special math functions, Copyright Eric Ford & Hubert Holin 2001. - Path: js/js.parser/src/com/google - License: Netscape Public License 1.1 (license/third_party/rhino_LICENSE.txt) @@ -48,7 +52,11 @@ the Kotlin IntelliJ IDEA plugin: - Path: libraries/stdlib/src/kotlin/collections - License: Apache 2 (license/third_party/gwt_license.txt) - Origin: Derived from GWT, (C) 2007-08 Google Inc. - + + - Path: libraries/stdlib/src/kotlin/util/MathJVM.kt + - License: Boost Software License 1.0 (license/third_party/boost_LICENSE.txt) + - Origin: Derived from boost special math functions, Copyright Eric Ford & Hubert Holin 2001. + - Path: plugins/lint/android-annotations - License: Apache 2 (license/third_party/aosp_license.txt) - Origin: Copyright (C) 2011-15 The Android Open Source Project diff --git a/license/third_party/boost_LICENSE.txt b/license/third_party/boost_LICENSE.txt new file mode 100644 index 00000000000..127a5bc39ba --- /dev/null +++ b/license/third_party/boost_LICENSE.txt @@ -0,0 +1,23 @@ +Boost Software License - Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. \ No newline at end of file