From 5e9e6d595112e4efb3ae3b5bb4adcd6201ee2995 Mon Sep 17 00:00:00 2001 From: Ilya Gorbunov Date: Thu, 20 Jul 2017 21:21:56 +0300 Subject: [PATCH] Common math tests and document for trigonometric functions and powers. #KT-4900 --- js/js.libraries/src/core/kotlin.math.kt | 275 +++++++++++++-- libraries/stdlib/src/kotlin/util/MathJVM.kt | 369 ++++++++++++++++---- libraries/stdlib/test/numbers/MathTest.kt | 156 +++++++++ 3 files changed, 700 insertions(+), 100 deletions(-) create mode 100644 libraries/stdlib/test/numbers/MathTest.kt diff --git a/js/js.libraries/src/core/kotlin.math.kt b/js/js.libraries/src/core/kotlin.math.kt index ac4386c1851..2a8aed2821e 100644 --- a/js/js.libraries/src/core/kotlin.math.kt +++ b/js/js.libraries/src/core/kotlin.math.kt @@ -17,6 +17,7 @@ package kotlin.math +import kotlin.internal.InlineOnly import kotlin.js.Math as nativeMath // constants, can't use them from nativeMath as they are not constants there @@ -28,36 +29,243 @@ public const val E: Double = 2.718281828459045 // Double -inline fun sin(a: Double): Double = nativeMath.sin(a) -inline fun cos(a: Double): Double = nativeMath.cos(a) -inline fun tan(a: Double): Double = nativeMath.tan(a) +/** Computes the sine of the angle [a] given in radians. + * + * Special cases: + * + * - `sin(NaN|+Inf|-Inf)` is `NaN` + */ +@InlineOnly +public inline fun sin(a: Double): Double = nativeMath.sin(a) -inline fun asin(a: Double): Double = nativeMath.asin(a) -inline fun acos(a: Double): Double = nativeMath.acos(a) -inline fun atan(a: Double): Double = nativeMath.atan(a) -inline fun atan2(y: Double, x: Double): Double = nativeMath.atan2(y, x) +/** Computes the cosine of the angle [a] given in radians. + * + * Special cases: + * + * - `cos(NaN|+Inf|-Inf)` is `NaN` + */ +@InlineOnly +public inline fun cos(a: Double): Double = nativeMath.cos(a) -// TODO: Polyfill -/* -inline fun sinh(a: Double): Double = nativeMath.sinh(a) -inline fun cosh(a: Double): Double = nativeMath.cosh(a) -inline fun tanh(a: Double): Double = nativeMath.tanh(a) +/** Computes the tangent of the angle [a] given in radians. + * + * Special cases: + * + * - `tan(NaN|+Inf|-Inf)` is `NaN` + */ +@InlineOnly +public inline fun tan(a: Double): Double = nativeMath.tan(a) -inline fun hypot(x: Double, y: Double): Double = nativeMath.hypot(x, y) -*/ +/** + * Computes the arc sine of the value [a]; + * the returned value is an angle in the range from `-PI/2` to `PI/2` radians. + * + * Special cases: + * - `asin(v)` is `NaN`, when `abs(v) > 1` or v is `NaN` + */ +@InlineOnly +public inline fun asin(a: Double): Double = nativeMath.asin(a) -inline fun pow(a: Double, b: Double): Double = nativeMath.pow(a, b) -inline fun pow(a: Double, b: Int): Double = nativeMath.pow(a, b.toDouble()) +/** + * Computes the arc cosine of the value [a]; + * the returned value is an angle in the range from `0.0` to `PI` radians. + * + * Special cases: + * - `acos(v)` is `NaN`, when `abs(v) > 1` or v is `NaN` + */ +@InlineOnly +public inline fun acos(a: Double): Double = nativeMath.acos(a) -inline fun sqrt(a: Double): Double = nativeMath.sqrt(a) +/** + * Computes the arc tangent of the value [a]; + * the returned value is an angle in the range from `-PI/2` to `PI/2` radians. + * + * Special cases: + * - `atan(NaN)` is `NaN` + */ +@InlineOnly +public inline fun atan(a: Double): Double = nativeMath.atan(a) -inline fun exp(a: Double): Double = nativeMath.exp(a) -// inline fun expm1(a: Double): Double = nativeMath.expm1(a) // polyfill +/** + * Returns the angle `theta` of the polar coordinates `(r, theta)` that correspond + * to the rectangular coordinates `(x, y)` by computing the arc tangent of the value [y] / [x]; + * the returned value is an angle in the range from `-PI` to `PI` radians. + * + * Special cases: + * - `atan2(0.0, 0.0)` is `0.0` + * - `atan2(0.0, x)` is `0.0` for `x > 0` and `PI` for `x < 0` + * - `atan2(-0.0, x)` is `-0.0` for 'x > 0` and `-PI` for `x < 0` + * - `atan2(y, +Inf)` is `0.0` for `0 < y < +Inf` and `-0.0` for '-Inf < y < 0` + * - `atan2(y, -Inf)` is `PI` for `0 < y < +Inf` and `-PI` for `-Inf < y < 0` + * - `atan2(y, 0.0)` is `PI/2` for `y > 0` and `-PI/2` for `y < 0` + * - `atan2(+Inf, x)` is `PI/2` for finite `x`y + * - `atan2(-Inf, x)` is `-PI/2` for finite `x` + * - `atan2(NaN, x)` and `atan2(y, NaN)` is `NaN` + */ +@InlineOnly +public inline fun atan2(y: Double, x: Double): Double = nativeMath.atan2(y, x) + +/** + * Computes the hyperbolic sine of the value [a]. + * + * Special cases: + * + * - `sinh(NaN)` is `NaN` + * - `sinh(+Inf)` is `+Inf` + * - `sinh(-Inf)` is `-Inf` + */ +@InlineOnly +public inline fun sinh(a: Double): Double = nativeMath.sinh(a) + +/** + * Computes the hyperbolic cosine of the value [a]. + * + * Special cases: + * + * - `cosh(NaN)` is `NaN` + * - `cosh(+Inf|-Inf)` is `+Inf` + */ +@InlineOnly +public inline fun cosh(a: Double): Double = nativeMath.cosh(a) + +/** + * Computes the hyperbolic tangent of the value [a]. + * + * Special cases: + * + * - `tanh(NaN)` is `NaN` + * - `tanh(+Inf)` is `1.0` + * - `tanh(-Inf)` is `-1.0` + */ +@InlineOnly +public inline fun tanh(a: Double): Double = nativeMath.tanh(a) + +/** + * Computes `sqrt(x^2 + y^2)` without intermediate overflow or underflow. + * + * Special cases: + * - returns `+Inf` if any of arguments is infinite + * - returns `NaN` if any of arguments is `NaN` and the other is not infinite + */ +@InlineOnly +public inline fun hypot(x: Double, y: Double): Double = nativeMath.hypot(x, y) + +/** + * Raises the first argument [a] to the power of the second argument [b]. + * + * Special cases: + * - `pow(x, 0.0)` is `1.0` + * - `pow(x, 1.0) == x` + * - `pow(x, NaN)` is `NaN` + * - `pow(NaN, x)` is `NaN` for `x != 0.0` + * - `pow(x, Inf)` is `NaN` for `abs(x) == 1.0` + * - `pow(x, y)` is `NaN` for `x < 0` and `y` is finite and not an integer + */ +@InlineOnly +public inline fun pow(a: Double, b: Double): Double = nativeMath.pow(a, b) + +/** + * Raises the first argument [a] to the integer power of the second argument [b]. + * + * See the other overload of [pow] for details. + */ +@InlineOnly +public inline fun pow(a: Double, b: Int): Double = nativeMath.pow(a, b.toDouble()) + +/** + * Computes the positive square root of the value [a]. + * + * Special cases: + * - `sqrt(x)` is `NaN` when `x < 0` or `x` is `NaN` + */ +@InlineOnly +public inline fun sqrt(a: Double): Double = nativeMath.sqrt(a) + +/** + * Computes Euler's number `e` raised to the power of the value [a]. + * + * Special cases: + * - `exp(NaN)` is `NaN` + * - `exp(+Inf)` is `+Inf` + * - `exp(-Inf)` is `0.0` + */ +@InlineOnly +public inline fun exp(a: Double): Double = nativeMath.exp(a) + +/** + * Computes `exp(a) - 1`. + * + * This function can be implemented to produce more precise result for [a] near zero. + * + * Special cases: + * - `expm1(NaN)` is `NaN` + * - `expm1(+Inf)` is `+Inf` + * - `expm1(-Inf)` is `-1.0` + * + * @see [exp] function. + */ +@InlineOnly +public inline fun expm1(a: Double): Double = nativeMath.expm1(a) + +/** + * Computes the logarithm in the given [base] of the [a] value. + * + * Special cases: + * - `log(a, b)` is `NaN` if either `a` or `b` are `NaN` + * - `log(a, b)` is `NaN` when `a < 0` or `b <= 0` or `b == 1.0` + * - `log(+Inf, +Inf)` is `NaN` + * - `log(+Inf, b)` is `+Inf` for `b > 1` and `-Inf` for `b < 1` + * - `log(0.0, b)` is `-Inf` for `b > 1` and `+Inf` for `b > 1` + */ +public fun log(a: Double, base: Double): Double { + if (base <= 0.0 || base == 1.0) return Double.NaN + return nativeMath.log(a) / nativeMath.log(base) +} + +/** + * Computes the natural logarithm (base `E`) of the [a] value. + * + * Special cases: + * - `log(NaN)` is `NaN` + * - `log(x)` is `NaN` when `x < 0.0` + * - `log(+Inf)` is `+Inf` + * - `log(0.0)` is `-Inf` + */ +@InlineOnly +public inline fun log(a: Double): Double = nativeMath.log(a) + +/** + * Computes the decimal logarithm (base 10) of the [a] value. + * + * @see [log] function for special cases. + */ +@InlineOnly +public inline fun log10(a: Double): Double = nativeMath.log10(a) + +/** + * Computes the binary logarithm (base 2) of the [a] value. + * + * @see [log] function for special cases. + */ +@InlineOnly +public inline fun log2(a: Double): Double = nativeMath.log2(a) + +/** + * Computes `log(a + 1)`. + * + * This function can be implemented to produce more precise result for [a] near zero. + * + * Special cases: + * - `log1p(NaN)` is `NaN` + * - `log1p(x)` is `NaN` where `x < -1.0` + * - `log1p(-1.0)` is `-Inf` + * - `log1p(+Inf)` is `+Inf` + * + * @see [log] function. + */ +@InlineOnly +public inline fun log1p(a: Double): Double = nativeMath.log1p(a) -inline fun log(a: Double): Double = nativeMath.log(a) -fun log(a: Double, base: Double): Double = nativeMath.log(a) / nativeMath.log(base) -//inline fun log10(a: Double): Double = nativeMath.log10(a) // polyfill -//inline fun log1p(a: Double): Double = nativeMath.log1p(a) // polyfill inline fun ceil(a: Double): Double = nativeMath.ceil(a).unsafeCast() // TODO: Remove unsafe cast after removing public js.math inline fun floor(a: Double): Double = nativeMath.floor(a).unsafeCast() @@ -67,9 +275,20 @@ inline fun truncate(a: Double): Double = nativeMath.trunc(a) // polyfill inline fun abs(a: Double): Double = nativeMath.abs(a) // also as extension val [sign] inline fun sgn(a: Double): Double = nativeMath.sign(a) - -inline fun min(a: Double, b: Double): Double = nativeMath.min(a, b) -inline fun max(a: Double, b: Double): Double = nativeMath.max(a, b) +/** + * Returns the smaller of two values. + * + * If either value is `NaN`, then the result is `NaN`. + */ +@InlineOnly +public inline fun min(a: Double, b: Double): Double = nativeMath.min(a, b) +/** + * Returns the greater of two values. + * + * If either value is `NaN`, then the result is `NaN`. + */ +@InlineOnly +public inline fun max(a: Double, b: Double): Double = nativeMath.max(a, b) // extensions @@ -82,7 +301,6 @@ inline val Double.sign: Double get() = nativeMath.sign(this) // TODO: Reimplement here fun Double.withSign(sign: Double): Double = this.absoluteValue * sign.sign inline fun Double.withSign(sign: Int): Double = this.withSign(sign.toDouble()) -//inline fun Double.adjustExponent(scaleFactor: Int): Double = nativeMath.scalb(this, scaleFactor) fun Double.roundToLong(): Long = if (isNaN()) throw IllegalArgumentException("Cannot round NaN value.") else nativeMath.round(this).unsafeCast().toLong() @@ -101,13 +319,10 @@ inline fun min(a: Float, b: Float): Float = nativeMath.min(a, b) inline val Float.absoluteValue: Float get() = nativeMath.abs(this.toDouble()).toFloat() inline val Float.sign: Float get() = nativeMath.sign(this.toDouble()).toFloat() -//inline val Float.exponent: Int get() = nativeMath.getExponent(this) // TODO: Reimplement inline fun Float.withSign(sign: Float): Float = this.toDouble().withSign(sign.toDouble()).toFloat() inline fun Float.withSign(sign: Int): Float = this.toDouble().withSign(sign.toDouble()).toFloat() -//inline fun Float.adjustExponent(scaleFactor: Int): Float = nativeMath.scalb(this, scaleFactor) -//fun Float.withExponent(exponent: Int): Float = nativeMath.scalb(this, exponent - this.exponent) fun Float.roundToInt(): Int = if (isNaN()) throw IllegalArgumentException("Cannot round NaN value.") else nativeMath.round(this) diff --git a/libraries/stdlib/src/kotlin/util/MathJVM.kt b/libraries/stdlib/src/kotlin/util/MathJVM.kt index 3089ac33e1a..b91db6d6323 100644 --- a/libraries/stdlib/src/kotlin/util/MathJVM.kt +++ b/libraries/stdlib/src/kotlin/util/MathJVM.kt @@ -19,129 +19,358 @@ package kotlin.math +import kotlin.internal.InlineOnly import java.lang.Math as nativeMath // constants /** Ratio of the circumference of a circle to its diameter, approximately 3.14159. */ -const val PI: Double = nativeMath.PI +public const val PI: Double = nativeMath.PI /** Base of the natural logarithms, approximately 2.71828. */ -const val E: Double = nativeMath.E +public const val E: Double = nativeMath.E +/** Natural logarithm of 2.0, used to compute [log2] function */ +private val LN2: Double = log(2.0) // Double -inline fun sin(a: Double): Double = nativeMath.sin(a) -inline fun cos(a: Double): Double = nativeMath.cos(a) -inline fun tan(a: Double): Double = nativeMath.tan(a) +/** Computes the sine of the angle [a] given in radians. + * + * Special cases: + * + * - `sin(NaN|+Inf|-Inf)` is `NaN` + */ +@InlineOnly +public inline fun sin(a: Double): Double = nativeMath.sin(a) -inline fun asin(a: Double): Double = nativeMath.asin(a) -inline fun acos(a: Double): Double = nativeMath.acos(a) -inline fun atan(a: Double): Double = nativeMath.atan(a) -inline fun atan2(y: Double, x: Double): Double = nativeMath.atan2(y, x) +/** Computes the cosine of the angle [a] given in radians. + * + * Special cases: + * + * - `cos(NaN|+Inf|-Inf)` is `NaN` + */ +@InlineOnly +public inline fun cos(a: Double): Double = nativeMath.cos(a) -inline fun sinh(a: Double): Double = nativeMath.sinh(a) -inline fun cosh(a: Double): Double = nativeMath.cosh(a) -inline fun tanh(a: Double): Double = nativeMath.tanh(a) +/** Computes the tangent of the angle [a] given in radians. + * + * Special cases: + * + * - `tan(NaN|+Inf|-Inf)` is `NaN` + */ +@InlineOnly +public inline fun tan(a: Double): Double = nativeMath.tan(a) -inline fun hypot(x: Double, y: Double): Double = nativeMath.hypot(x, y) +/** + * Computes the arc sine of the value [a]; + * the returned value is an angle in the range from `-PI/2` to `PI/2` radians. + * + * Special cases: + * - `asin(v)` is `NaN`, when `abs(v) > 1` or v is `NaN` + */ +@InlineOnly +public inline fun asin(a: Double): Double = nativeMath.asin(a) -inline fun pow(a: Double, b: Double): Double = nativeMath.pow(a, b) -inline fun pow(a: Double, b: Int): Double = nativeMath.pow(a, b.toDouble()) +/** + * Computes the arc cosine of the value [a]; + * the returned value is an angle in the range from `0.0` to `PI` radians. + * + * Special cases: + * - `acos(v)` is `NaN`, when `abs(v) > 1` or v is `NaN` + */ +@InlineOnly +public inline fun acos(a: Double): Double = nativeMath.acos(a) -inline fun sqrt(a: Double): Double = nativeMath.sqrt(a) +/** + * Computes the arc tangent of the value [a]; + * the returned value is an angle in the range from `-PI/2` to `PI/2` radians. + * + * Special cases: + * - `atan(NaN)` is `NaN` + */ +@InlineOnly +public inline fun atan(a: Double): Double = nativeMath.atan(a) -inline fun exp(a: Double): Double = nativeMath.exp(a) -inline fun expm1(a: Double): Double = nativeMath.expm1(a) +/** + * Returns the angle `theta` of the polar coordinates `(r, theta)` that correspond + * to the rectangular coordinates `(x, y)` by computing the arc tangent of the value [y] / [x]; + * the returned value is an angle in the range from `-PI` to `PI` radians. + * + * Special cases: + * - `atan2(0.0, 0.0)` is `0.0` + * - `atan2(0.0, x)` is `0.0` for `x > 0` and `PI` for `x < 0` + * - `atan2(-0.0, x)` is `-0.0` for 'x > 0` and `-PI` for `x < 0` + * - `atan2(y, +Inf)` is `0.0` for `0 < y < +Inf` and `-0.0` for '-Inf < y < 0` + * - `atan2(y, -Inf)` is `PI` for `0 < y < +Inf` and `-PI` for `-Inf < y < 0` + * - `atan2(y, 0.0)` is `PI/2` for `y > 0` and `-PI/2` for `y < 0` + * - `atan2(+Inf, x)` is `PI/2` for finite `x`y + * - `atan2(-Inf, x)` is `-PI/2` for finite `x` + * - `atan2(NaN, x)` and `atan2(y, NaN)` is `NaN` + */ +@InlineOnly +public inline fun atan2(y: Double, x: Double): Double = nativeMath.atan2(y, x) -inline fun log(a: Double): Double = nativeMath.log(a) -fun log(a: Double, base: Double): Double = nativeMath.log(a) / nativeMath.log(base) -inline fun log10(a: Double): Double = nativeMath.log10(a) -inline fun log1p(a: Double): Double = nativeMath.log1p(a) +/** + * Computes the hyperbolic sine of the value [a]. + * + * Special cases: + * + * - `sinh(NaN)` is `NaN` + * - `sinh(+Inf)` is `+Inf` + * - `sinh(-Inf)` is `-Inf` + */ +@InlineOnly +public inline fun sinh(a: Double): Double = nativeMath.sinh(a) -inline fun ceil(a: Double): Double = nativeMath.ceil(a) -inline fun floor(a: Double): Double = nativeMath.floor(a) -inline fun truncate(a: Double): Double = nativeMath.rint(a) +/** + * Computes the hyperbolic cosine of the value [a]. + * + * Special cases: + * + * - `cosh(NaN)` is `NaN` + * - `cosh(+Inf|-Inf)` is `+Inf` + */ +@InlineOnly +public inline fun cosh(a: Double): Double = nativeMath.cosh(a) + +/** + * Computes the hyperbolic tangent of the value [a]. + * + * Special cases: + * + * - `tanh(NaN)` is `NaN` + * - `tanh(+Inf)` is `1.0` + * - `tanh(-Inf)` is `-1.0` + */ +@InlineOnly +public inline fun tanh(a: Double): Double = nativeMath.tanh(a) + +/** + * Computes `sqrt(x^2 + y^2)` without intermediate overflow or underflow. + * + * Special cases: + * - returns `+Inf` if any of arguments is infinite + * - returns `NaN` if any of arguments is `NaN` and the other is not infinite + */ +@InlineOnly +public inline fun hypot(x: Double, y: Double): Double = nativeMath.hypot(x, y) + +/** + * Raises the first argument [a] to the power of the second argument [b]. + * + * Special cases: + * - `pow(x, 0.0)` is `1.0` + * - `pow(x, 1.0) == x` + * - `pow(x, NaN)` is `NaN` + * - `pow(NaN, x)` is `NaN` for `x != 0.0` + * - `pow(x, Inf)` is `NaN` for `abs(x) == 1.0` + * - `pow(x, y)` is `NaN` for `x < 0` and `y` is finite and not an integer + */ +@InlineOnly +public inline fun pow(a: Double, b: Double): Double = nativeMath.pow(a, b) + +/** + * Raises the first argument [a] to the integer power of the second argument [b]. + * + * See the other overload of [pow] for details. + */ +@InlineOnly +public inline fun pow(a: Double, b: Int): Double = nativeMath.pow(a, b.toDouble()) + +/** + * Computes the positive square root of the value [a]. + * + * Special cases: + * - `sqrt(x)` is `NaN` when `x < 0` or `x` is `NaN` + */ +@InlineOnly +public inline fun sqrt(a: Double): Double = nativeMath.sqrt(a) + +/** + * Computes Euler's number `e` raised to the power of the value [a]. + * + * Special cases: + * - `exp(NaN)` is `NaN` + * - `exp(+Inf)` is `+Inf` + * - `exp(-Inf)` is `0.0` + */ +@InlineOnly +public inline fun exp(a: Double): Double = nativeMath.exp(a) + +/** + * Computes `exp(a) - 1`. + * + * This function can be implemented to produce more precise result for [a] near zero. + * + * Special cases: + * - `expm1(NaN)` is `NaN` + * - `expm1(+Inf)` is `+Inf` + * - `expm1(-Inf)` is `-1.0` + * + * @see [exp] function. + */ +@InlineOnly +public inline fun expm1(a: Double): Double = nativeMath.expm1(a) + +/** + * Computes the logarithm in the given [base] of the [a] value. + * + * Special cases: + * - `log(a, b)` is `NaN` if either `a` or `b` are `NaN` + * - `log(a, b)` is `NaN` when `a < 0` or `b <= 0` or `b == 1.0` + * - `log(+Inf, +Inf)` is `NaN` + * - `log(+Inf, b)` is `+Inf` for `b > 1` and `-Inf` for `b < 1` + * - `log(0.0, b)` is `-Inf` for `b > 1` and `+Inf` for `b > 1` + */ +public fun log(a: Double, base: Double): Double { + if (base <= 0.0 || base == 1.0) return Double.NaN + return nativeMath.log(a) / nativeMath.log(base) +} + +/** + * Computes the natural logarithm (base `E`) of the [a] value. + * + * Special cases: + * - `log(NaN)` is `NaN` + * - `log(x)` is `NaN` when `x < 0.0` + * - `log(+Inf)` is `+Inf` + * - `log(0.0)` is `-Inf` + */ +@InlineOnly +public inline fun log(a: Double): Double = nativeMath.log(a) + +/** + * Computes the decimal logarithm (base 10) of the [a] value. + * + * @see [log] function for special cases. + */ +@InlineOnly +public inline fun log10(a: Double): Double = nativeMath.log10(a) + +/** + * Computes the binary logarithm (base 2) of the [a] value. + * + * @see [log] function for special cases. + */ +public fun log2(a: Double): Double = nativeMath.log(a) / LN2 + +/** + * Computes `log(a + 1)`. + * + * This function can be implemented to produce more precise result for [a] near zero. + * + * Special cases: + * - `log1p(NaN)` is `NaN` + * - `log1p(x)` is `NaN` where `x < -1.0` + * - `log1p(-1.0)` is `-Inf` + * - `log1p(+Inf)` is `+Inf` + * + * @see [log] function. + */ +@InlineOnly +public inline fun log1p(a: Double): Double = nativeMath.log1p(a) + +public inline fun ceil(a: Double): Double = nativeMath.ceil(a) +public inline fun floor(a: Double): Double = nativeMath.floor(a) +public inline fun truncate(a: Double): Double = nativeMath.rint(a) // also as extension val [absoluteValue] -inline fun abs(a: Double): Double = nativeMath.abs(a) +public inline fun abs(a: Double): Double = nativeMath.abs(a) // also as extension val [sign] -inline fun sgn(a: Double): Double = nativeMath.signum(a) +public inline fun sgn(a: Double): Double = nativeMath.signum(a) - -inline fun min(a: Double, b: Double): Double = nativeMath.min(a, b) -inline fun max(a: Double, b: Double): Double = nativeMath.max(a, b) +/** + * Returns the smaller of two values. + * + * If either value is `NaN`, then the result is `NaN`. + */ +@InlineOnly +public inline fun min(a: Double, b: Double): Double = nativeMath.min(a, b) +/** + * Returns the greater of two values. + * + * If either value is `NaN`, then the result is `NaN`. + */ +@InlineOnly +public inline fun max(a: Double, b: Double): Double = nativeMath.max(a, b) // extensions +/** + * Raises this value to the power [other]. + * + * See the [pow] top-level function for details. + */ +@InlineOnly @JvmName("power") -inline fun Double.pow(other: Double): Double = nativeMath.pow(this, other) +public inline fun Double.pow(other: Double): Double = nativeMath.pow(this, other) + +/** + * Raises this value to the integer power [other]. + * + * See the [pow] top-level function for details. + */ +@InlineOnly @JvmName("power") -inline fun Double.pow(other: Int): Double = nativeMath.pow(this, other.toDouble()) +public inline fun Double.pow(other: Int): Double = nativeMath.pow(this, other.toDouble()) -inline fun Double.IEEErem(other: Double): Double = nativeMath.IEEEremainder(this, other) -inline val Double.absoluteValue: Double get() = nativeMath.abs(this) -inline val Double.sign: Double get() = nativeMath.signum(this) -inline val Double.exponent: Int get() = nativeMath.getExponent(this) +public inline fun Double.IEEErem(other: Double): Double = nativeMath.IEEEremainder(this, other) +public inline val Double.absoluteValue: Double get() = nativeMath.abs(this) +public inline val Double.sign: Double get() = nativeMath.signum(this) -inline fun Double.withSign(sign: Double): Double = nativeMath.copySign(this, sign) -inline fun Double.withSign(sign: Int): Double = nativeMath.copySign(this, sign.toDouble()) -inline fun Double.adjustExponent(scaleFactor: Int): Double = nativeMath.scalb(this, scaleFactor) -fun Double.withExponent(exponent: Int): Double = nativeMath.scalb(this, exponent - this.exponent) +public inline fun Double.withSign(sign: Double): Double = nativeMath.copySign(this, sign) +public inline fun Double.withSign(sign: Int): Double = nativeMath.copySign(this, sign.toDouble()) -inline val Double.ulp: Double get() = nativeMath.ulp(this) -inline fun Double.nextUp(): Double = nativeMath.nextUp(this) -inline fun Double.nextDown(): Double = nativeMath.nextAfter(this, Double.NEGATIVE_INFINITY) -inline fun Double.nextTowards(to: Double): Double = nativeMath.nextAfter(this, to) +public inline val Double.ulp: Double get() = nativeMath.ulp(this) +public inline fun Double.nextUp(): Double = nativeMath.nextUp(this) +public inline fun Double.nextDown(): Double = nativeMath.nextAfter(this, Double.NEGATIVE_INFINITY) +public inline fun Double.nextTowards(to: Double): Double = nativeMath.nextAfter(this, to) -fun Double.roundToLong(): Long = if (isNaN()) throw IllegalArgumentException("Cannot round NaN value.") else nativeMath.round(this) +public fun Double.roundToLong(): Long = if (isNaN()) throw IllegalArgumentException("Cannot round NaN value.") else nativeMath.round(this) // Float // also as extension val [absoluteValue] -inline fun abs(a: Float): Float = nativeMath.abs(a) +public inline fun abs(a: Float): Float = nativeMath.abs(a) // also as extension val [sign] -inline fun sgn(a: Float): Float = nativeMath.signum(a) +public inline fun sgn(a: Float): Float = nativeMath.signum(a) -inline fun max(a: Float, b: Float): Float = nativeMath.max(a, b) -inline fun min(a: Float, b: Float): Float = nativeMath.min(a, b) +public inline fun max(a: Float, b: Float): Float = nativeMath.max(a, b) +public inline fun min(a: Float, b: Float): Float = nativeMath.min(a, b) -inline val Float.absoluteValue: Float get() = nativeMath.abs(this) -inline val Float.sign: Float get() = nativeMath.signum(this) -inline val Float.exponent: Int get() = nativeMath.getExponent(this) +public inline val Float.absoluteValue: Float get() = nativeMath.abs(this) +public inline val Float.sign: Float get() = nativeMath.signum(this) -inline fun Float.withSign(sign: Float): Float = nativeMath.copySign(this, sign) -inline fun Float.withSign(sign: Int): Float = nativeMath.copySign(this, sign.toFloat()) -inline fun Float.adjustExponent(scaleFactor: Int): Float = nativeMath.scalb(this, scaleFactor) -fun Float.withExponent(exponent: Int): Float = nativeMath.scalb(this, exponent - this.exponent) +public inline fun Float.withSign(sign: Float): Float = nativeMath.copySign(this, sign) +public inline fun Float.withSign(sign: Int): Float = nativeMath.copySign(this, sign.toFloat()) -inline val Float.ulp: Float get() = nativeMath.ulp(this) -inline fun Float.nextUp(): Float = nativeMath.nextUp(this) -inline fun Float.nextDown(): Float = nativeMath.nextAfter(this, Double.NEGATIVE_INFINITY) -inline fun Float.nextTowards(to: Double): Float = nativeMath.nextAfter(this, to) +public inline val Float.ulp: Float get() = nativeMath.ulp(this) +public inline fun Float.nextUp(): Float = nativeMath.nextUp(this) +public inline fun Float.nextDown(): Float = nativeMath.nextAfter(this, Double.NEGATIVE_INFINITY) +public inline fun Float.nextTowards(to: Double): Float = nativeMath.nextAfter(this, to) -fun Float.roundToInt(): Int = if (isNaN()) throw IllegalArgumentException("Cannot round NaN value.") else nativeMath.round(this) -fun Float.roundToLong(): Long = toDouble().roundToLong() +public fun Float.roundToInt(): Int = if (isNaN()) throw IllegalArgumentException("Cannot round NaN value.") else nativeMath.round(this) +public fun Float.roundToLong(): Long = toDouble().roundToLong() // Int // also as extension val [absoluteValue] -inline fun abs(a: Int): Int = nativeMath.abs(a) +public inline fun abs(a: Int): Int = nativeMath.abs(a) -inline fun min(a: Int, b: Int): Int = nativeMath.min(a, b) -inline fun max(a: Int, b: Int): Int = nativeMath.max(a, b) +public inline fun min(a: Int, b: Int): Int = nativeMath.min(a, b) +public inline fun max(a: Int, b: Int): Int = nativeMath.max(a, b) -inline val Int.absoluteValue: Int get() = nativeMath.abs(this) +public inline val Int.absoluteValue: Int get() = nativeMath.abs(this) // Long // also as extension val [absoluteValue] -inline fun abs(a: Long): Long = nativeMath.abs(a) +public inline fun abs(a: Long): Long = nativeMath.abs(a) -inline fun min(a: Long, b: Long): Long = nativeMath.min(a, b) -inline fun max(a: Long, b: Long): Long = nativeMath.max(a, b) +public inline fun min(a: Long, b: Long): Long = nativeMath.min(a, b) +public inline fun max(a: Long, b: Long): Long = nativeMath.max(a, b) -inline val Long.absoluteValue: Long get() = nativeMath.abs(this) +public inline val Long.absoluteValue: Long get() = nativeMath.abs(this) diff --git a/libraries/stdlib/test/numbers/MathTest.kt b/libraries/stdlib/test/numbers/MathTest.kt new file mode 100644 index 00000000000..29ee187ecbf --- /dev/null +++ b/libraries/stdlib/test/numbers/MathTest.kt @@ -0,0 +1,156 @@ +/* + * Copyright 2010-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package test.numbers + +import org.junit.Test +import kotlin.math.* +import kotlin.test.* + +fun assertAlmostEquals(expected: Double, actual: Double, tolerance: Double? = null) { + val tolerance_ = tolerance?.let { abs(it) } ?: 0.000000000001 + if (abs(expected - actual) > tolerance_) { + assertEquals(expected, actual) + } +} + +class DoubleMathTest { + + @Test fun trigonometric() { + assertEquals(0.0, sin(0.0)) + assertAlmostEquals(0.0, sin(PI)) + + assertEquals(0.0, asin(0.0)) + assertAlmostEquals(PI / 2, asin(1.0)) + + assertEquals(1.0, cos(0.0)) + assertAlmostEquals(-1.0, cos(PI)) + + assertEquals(0.0, acos(1.0)) + assertAlmostEquals(PI / 2, acos(0.0)) + + assertEquals(0.0, tan(0.0)) + assertAlmostEquals(1.0, tan(PI / 4)) + + assertAlmostEquals(0.0, atan(0.0)) + assertAlmostEquals(PI / 4, atan(1.0)) + + assertAlmostEquals(PI / 4, atan2(10.0, 10.0)) + assertAlmostEquals(-PI / 4, atan2(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY)) + assertAlmostEquals(0.0, atan2(0.0, 0.0)) + assertAlmostEquals(0.0, atan2(0.0, 10.0)) + assertAlmostEquals(PI / 2, atan2(2.0, 0.0)) + + for (angle in listOf(Double.NaN, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY)) { + assertTrue(sin(angle).isNaN(), "sin($angle)") + assertTrue(cos(angle).isNaN(), "cos($angle)") + assertTrue(tan(angle).isNaN(), "tan($angle)") + } + + for (value in listOf(Double.NaN, 1.2, -1.1)) { + assertTrue(asin(value).isNaN()) + assertTrue(acos(value).isNaN()) + } + assertTrue(atan(Double.NaN).isNaN()) + assertTrue(atan2(Double.NaN, 0.0).isNaN()) + assertTrue(atan2(0.0, Double.NaN).isNaN()) + } + + @Test fun hyperbolic() { + assertEquals(Double.POSITIVE_INFINITY, sinh(Double.POSITIVE_INFINITY)) + assertEquals(Double.NEGATIVE_INFINITY, sinh(Double.NEGATIVE_INFINITY)) + assertTrue(sinh(Double.NaN).isNaN()) + + assertEquals(Double.POSITIVE_INFINITY, cosh(Double.POSITIVE_INFINITY)) + assertEquals(Double.POSITIVE_INFINITY, cosh(Double.NEGATIVE_INFINITY)) + assertTrue(cosh(Double.NaN).isNaN()) + + assertAlmostEquals(1.0, tanh(Double.POSITIVE_INFINITY)) + assertAlmostEquals(-1.0, tanh(Double.NEGATIVE_INFINITY)) + assertTrue(tanh(Double.NaN).isNaN()) + } + + @Test fun powers() { + assertEquals(5.0, hypot(3.0, 4.0)) + assertEquals(Double.POSITIVE_INFINITY, hypot(Double.NEGATIVE_INFINITY, Double.NaN)) + assertEquals(Double.POSITIVE_INFINITY, hypot(Double.NaN, Double.POSITIVE_INFINITY)) + assertTrue(hypot(Double.NaN, 0.0).isNaN()) + + assertEquals(1.0, pow(Double.NaN, 0.0)) + assertEquals(1.0, Double.POSITIVE_INFINITY.pow(0)) + assertEquals(49.0, pow(7.0, 2)) + assertEquals(0.25, pow(2.0, -2)) + assertTrue(pow(0.0, Double.NaN).isNaN()) + assertTrue(pow(Double.NaN, -1).isNaN()) + assertTrue(pow(-7.0, 1/3.0).isNaN()) + assertTrue(pow(1.0, Double.POSITIVE_INFINITY).isNaN()) + assertTrue(pow(-1.0, Double.NEGATIVE_INFINITY).isNaN()) + + assertEquals(5.0, sqrt(9.0 + 16.0)) + assertTrue(sqrt(-1.0).isNaN()) + assertTrue(sqrt(Double.NaN).isNaN()) + + assertTrue(exp(Double.NaN).isNaN()) + assertAlmostEquals(E, exp(1.0)) + assertEquals(1.0, exp(0.0)) + assertEquals(0.0, exp(Double.NEGATIVE_INFINITY)) + assertEquals(Double.POSITIVE_INFINITY, exp(Double.POSITIVE_INFINITY)) + + assertEquals(0.0, expm1(0.0)) + assertEquals(-1.0, expm1(Double.NEGATIVE_INFINITY)) + assertEquals(Double.POSITIVE_INFINITY, expm1(Double.POSITIVE_INFINITY)) + } + + @Test fun logarithms() { + assertTrue(log(1.0, Double.NaN).isNaN()) + assertTrue(log(Double.NaN, 1.0).isNaN()) + assertTrue(log(-1.0, 2.0).isNaN()) + assertTrue(log(2.0, -1.0).isNaN()) + assertTrue(log(2.0, 0.0).isNaN()) + assertTrue(log(2.0, 1.0).isNaN()) + assertTrue(log(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY).isNaN()) + assertEquals(-2.0, log(0.25, 2.0)) + assertEquals(-0.5, log(2.0, 0.25)) + assertEquals(Double.NEGATIVE_INFINITY, log(Double.POSITIVE_INFINITY, 0.25)) + assertEquals(Double.POSITIVE_INFINITY, log(Double.POSITIVE_INFINITY, 2.0)) + assertEquals(Double.NEGATIVE_INFINITY, log(0.0, 2.0)) + assertEquals(Double.POSITIVE_INFINITY, log(0.0, 0.25)) + + assertTrue(log(Double.NaN).isNaN()) + assertTrue(log(-1.0).isNaN()) + assertEquals(1.0, log(E)) + assertEquals(Double.NEGATIVE_INFINITY, log(0.0)) + assertEquals(Double.POSITIVE_INFINITY, log(Double.POSITIVE_INFINITY)) + + assertEquals(1.0, log10(10.0)) + assertAlmostEquals(-1.0, log10(0.1)) + + assertAlmostEquals(3.0, log2(8.0)) + assertEquals(-1.0, log2(0.5)) + + assertTrue(log1p(Double.NaN).isNaN()) + assertTrue(log1p(-1.1).isNaN()) + assertEquals(0.0, log1p(0.0)) + assertEquals(Double.NEGATIVE_INFINITY, log1p(-1.0)) + } + + @Test fun rounding() { + TODO() + } + + +} +