diff --git a/js/js.libraries/src/core/kotlin.math.kt b/js/js.libraries/src/core/kotlin.math.kt index ab91831809a..4953029e4d5 100644 --- a/js/js.libraries/src/core/kotlin.math.kt +++ b/js/js.libraries/src/core/kotlin.math.kt @@ -27,7 +27,7 @@ public const val PI: Double = 3.141592653589793 /** Base of the natural logarithms, approximately 2.71828. */ public const val E: Double = 2.718281828459045 -// Double +// ================ Double Math ======================================== /** Computes the sine of the angle [a] given in radians. * @@ -431,26 +431,394 @@ public fun Double.roundToLong(): Long = when { -// Float -// also as extension val [absoluteValue] -inline fun abs(a: Float): Float = nativeMath.abs(a.toDouble()).toFloat() -// also as extension val [sign] -inline fun sgn(a: Float): Float = nativeMath.sign(a.toDouble()).toFloat() +// ================ Float Math ======================================== -inline fun max(a: Float, b: Float): Float = nativeMath.max(a, b) -inline fun min(a: Float, b: Float): Float = nativeMath.min(a, b) +/** Computes the sine of the angle [a] given in radians. + * + * Special cases: + * + * - `sin(NaN|+Inf|-Inf)` is `NaN` + */ +@InlineOnly +public inline fun sin(a: Float): Float = nativeMath.sin(a.toDouble()).toFloat() -inline val Float.absoluteValue: Float get() = nativeMath.abs(this.toDouble()).toFloat() -inline val Float.sign: Float get() = nativeMath.sign(this.toDouble()).toFloat() +/** Computes the cosine of the angle [a] given in radians. + * + * Special cases: + * + * - `cos(NaN|+Inf|-Inf)` is `NaN` + */ +@InlineOnly +public inline fun cos(a: Float): Float = nativeMath.cos(a.toDouble()).toFloat() -// 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() +/** Computes the tangent of the angle [a] given in radians. + * + * Special cases: + * + * - `tan(NaN|+Inf|-Inf)` is `NaN` + */ +@InlineOnly +public inline fun tan(a: Float): Float = nativeMath.tan(a.toDouble()).toFloat() + +/** + * 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: Float): Float = nativeMath.asin(a.toDouble()).toFloat() + +/** + * 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: Float): Float = nativeMath.acos(a.toDouble()).toFloat() + +/** + * 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: Float): Float = nativeMath.atan(a.toDouble()).toFloat() + +/** + * 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: Float, x: Float): Float = nativeMath.atan2(y.toDouble(), x.toDouble()).toFloat() + +/** + * 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: Float): Float = nativeMath.sinh(a.toDouble()).toFloat() + +/** + * 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: Float): Float = nativeMath.cosh(a.toDouble()).toFloat() + +/** + * 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: Float): Float = nativeMath.tanh(a.toDouble()).toFloat() + +/** + * 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: Float, y: Float): Float = nativeMath.hypot(x.toDouble(), y.toDouble()).toFloat() + +/** + * 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: Float): Float = nativeMath.sqrt(a.toDouble()).toFloat() + +/** + * 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: Float): Float = nativeMath.exp(a.toDouble()).toFloat() + +/** + * 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: Float): Float = nativeMath.expm1(a.toDouble()).toFloat() + +/** + * 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` + */ +@InlineOnly +public inline fun log(a: Float, base: Float): Float = log(a.toDouble(), base.toDouble()).toFloat() + +/** + * Computes the natural logarithm (base `E`) of the [a] value. + * + * Special cases: + * - `ln(NaN)` is `NaN` + * - `ln(x)` is `NaN` when `x < 0.0` + * - `ln(+Inf)` is `+Inf` + * - `ln(0.0)` is `-Inf` + */ +@InlineOnly +public inline fun ln(a: Float): Float = nativeMath.log(a.toDouble()).toFloat() + +/** + * Computes the decimal logarithm (base 10) of the [a] value. + * + * @see [ln] function for special cases. + */ +@InlineOnly +public inline fun log10(a: Float): Float = nativeMath.log10(a.toDouble()).toFloat() + +/** + * Computes the binary logarithm (base 2) of the [a] value. + * + * @see [ln] function for special cases. + */ +@InlineOnly +public inline fun log2(a: Float): Float = nativeMath.log2(a.toDouble()).toFloat() + +/** + * 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 [ln] function + * @see [expm1] function + */ +@InlineOnly +public inline fun ln1p(a: Float): Float = nativeMath.log1p(a.toDouble()).toFloat() + +/** + * Rounds the given value [a] to an integer towards positive infinity. + + * @return the smallest Float value that is greater than the given value [a] and is a mathematical integer. + * + * Special cases: + * - `ceil(x)` is `x` where `x` is `NaN` or `+Inf` or `-Inf` or already a mathematical integer. + */ +@InlineOnly +public inline fun ceil(a: Float): Float = nativeMath.ceil(a.toDouble()).toFloat() + +/** + * Rounds the given value [a] to an integer towards negative infinity. + + * @return the largest Float value that is smaller than the given value [a] and is a mathematical integer. + * + * Special cases: + * - `floor(x)` is `x` where `x` is `NaN` or `+Inf` or `-Inf` or already a mathematical integer. + */ +@InlineOnly +public inline fun floor(a: Float): Float = nativeMath.floor(a.toDouble()).toFloat() + +/** + * Rounds the given value [a] to an integer towards zero. + * + * @return the value [a] having its fractional part truncated. + * + * Special cases: + * - `truncate(x)` is `x` where `x` is `NaN` or `+Inf` or `-Inf` or already a mathematical integer. + */ +@InlineOnly +public inline fun truncate(a: Float): Float = truncate(a.toDouble()).toFloat() + +/** + * Rounds the given value [a] towards the closest integer with ties rounded towards even integer. + * + * Special cases: + * - `round(x)` is `x` where `x` is `NaN` or `+Inf` or `-Inf` or already a mathematical integer. + */ +@InlineOnly +public inline fun round(a: Float): Float = round(a.toDouble()).toFloat() -fun Float.roundToInt(): Int = toDouble().roundToInt() -fun Float.roundToLong(): Long = toDouble().roundToLong() +/** + * Returns the absolute value of the given value [a]. + * + * Special cases: + * - `abs(NaN)` is `NaN` + * + * @see absoluteValue extension property for [Float] + */ +@InlineOnly +public inline fun abs(a: Float): Float = nativeMath.abs(a.toDouble()).toFloat() + +/** + * Returns the sign of the given value [a]: + * - `-1.0` if the value is negative, + * - zero if the value is zero, + * - `1.0` if the value is positive + * + * Special case: + * - `sign(NaN)` is `NaN` + */ +@InlineOnly +public inline fun sign(a: Float): Float = nativeMath.sign(a.toDouble()).toFloat() + + + +/** + * Returns the smaller of two values. + * + * If either value is `NaN`, then the result is `NaN`. + */ +@InlineOnly +public inline fun min(a: Float, b: Float): Float = 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: Float, b: Float): Float = nativeMath.max(a, b) + +// extensions + + +/** + * Raises this value to the power [other]. + * + * Special cases: + * - `x.pow(0.0)` is `1.0` + * - `x.pow(1.0) == x` + * - `x.pow(NaN)` is `NaN` + * - `NaN.pow(x)` is `NaN` for `x != 0.0` + * - `x.pow(Inf)` is `NaN` for `abs(x) == 1.0` + * - `x.pow(y)` is `NaN` for `x < 0` and `y` is finite and not an integer + */ +@InlineOnly +public inline fun Float.pow(other: Float): Float = nativeMath.pow(this.toDouble(), other.toDouble()).toFloat() + +/** + * Raises this value to the integer power [other]. + * + * See the other overload of [pow] for details. + */ +@InlineOnly +public inline fun Float.pow(other: Int): Float = nativeMath.pow(this.toDouble(), other.toDouble()).toFloat() + +/** + * Returns the absolute value of this value. + * + * Special cases: + * - `NaN.absoluteValue` is `NaN` + * + * @see abs function + */ +@InlineOnly +public inline val Float.absoluteValue: Float get() = nativeMath.abs(this.toDouble()).toFloat() + +/** + * Returns the sign of this value: + * - `-1.0` if the value is negative, + * - zero if the value is zero, + * - `1.0` if the value is positive + * + * Special case: + * - `NaN.sign` is `NaN` + */ +@InlineOnly +public inline val Float.sign: Float get() = nativeMath.sign(this.toDouble()).toFloat() + +/** + * Returns this value with the sign bit same as of the [sign] value. + * + * If [sign] is `NaN` the sign of the result is undefined. + */ +@InlineOnly +public inline fun Float.withSign(sign: Float): Float = this.toDouble().withSign(sign.toDouble()).toFloat() +/** + * Returns this value with the sign bit same as of the [sign] value. + */ +@InlineOnly +public inline fun Float.withSign(sign: Int): Float = this.toDouble().withSign(sign.toDouble()).toFloat() + + +/** + * Rounds this [Float] value to the nearest integer and converts the result to [Int]. + * Ties are rounded towards positive infinity. + * + * Special cases: + * - `x.roundToInt() == Int.MAX_VALUE` when `x > Int.MAX_VALUE` + * - `x.roundToInt() == Int.MIN_VALUE` when `x < Int.MIN_VALUE` + * + * @throws IllegalArgumentException when this value is `NaN` + */ +@InlineOnly +public inline fun Float.roundToInt(): Int = toDouble().roundToInt() + +/** + * Rounds this [Float] value to the nearest integer and converts the result to [Long]. + * Ties are rounded towards positive infinity. + * + * Special cases: + * - `x.roundToLong() == Long.MAX_VALUE` when `x > Long.MAX_VALUE` + * - `x.roundToLong() == Long.MIN_VALUE` when `x < Long.MIN_VALUE` + * + * @throws IllegalArgumentException when this value is `NaN` + */ +@InlineOnly +public inline fun Float.roundToLong(): Long = toDouble().roundToLong() + // Int diff --git a/libraries/stdlib/src/kotlin/util/MathJVM.kt b/libraries/stdlib/src/kotlin/util/MathJVM.kt index f6e7b0df43c..39ddc0fa02c 100644 --- a/libraries/stdlib/src/kotlin/util/MathJVM.kt +++ b/libraries/stdlib/src/kotlin/util/MathJVM.kt @@ -30,7 +30,7 @@ public const val E: Double = nativeMath.E /** Natural logarithm of 2.0, used to compute [log2] function */ private val LN2: Double = ln(2.0) -// Double +// ================ Double Math ======================================== /** Computes the sine of the angle [a] given in radians. * @@ -474,32 +474,447 @@ public fun Double.roundToInt(): Int = when { public fun Double.roundToLong(): Long = if (isNaN()) throw IllegalArgumentException("Cannot round NaN value.") else nativeMath.round(this) -// Float -// also as extension val [absoluteValue] +// ================ Float Math ======================================== + +/** Computes the sine of the angle [a] given in radians. + * + * Special cases: + * + * - `sin(NaN|+Inf|-Inf)` is `NaN` + */ +@InlineOnly +public inline fun sin(a: Float): Float = nativeMath.sin(a.toDouble()).toFloat() + +/** Computes the cosine of the angle [a] given in radians. + * + * Special cases: + * + * - `cos(NaN|+Inf|-Inf)` is `NaN` + */ +@InlineOnly +public inline fun cos(a: Float): Float = nativeMath.cos(a.toDouble()).toFloat() + +/** Computes the tangent of the angle [a] given in radians. + * + * Special cases: + * + * - `tan(NaN|+Inf|-Inf)` is `NaN` + */ +@InlineOnly +public inline fun tan(a: Float): Float = nativeMath.tan(a.toDouble()).toFloat() + +/** + * 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: Float): Float = nativeMath.asin(a.toDouble()).toFloat() + +/** + * 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: Float): Float = nativeMath.acos(a.toDouble()).toFloat() + +/** + * 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: Float): Float = nativeMath.atan(a.toDouble()).toFloat() + +/** + * 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: Float, x: Float): Float = nativeMath.atan2(y.toDouble(), x.toDouble()).toFloat() + +/** + * 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: Float): Float = nativeMath.sinh(a.toDouble()).toFloat() + +/** + * 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: Float): Float = nativeMath.cosh(a.toDouble()).toFloat() + +/** + * 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: Float): Float = nativeMath.tanh(a.toDouble()).toFloat() + +/** + * 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: Float, y: Float): Float = nativeMath.hypot(x.toDouble(), y.toDouble()).toFloat() + +/** + * 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: Float): Float = nativeMath.sqrt(a.toDouble()).toFloat() + +/** + * 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: Float): Float = nativeMath.exp(a.toDouble()).toFloat() + +/** + * 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: Float): Float = nativeMath.expm1(a.toDouble()).toFloat() + +/** + * 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: Float, base: Float): Float { + if (base <= 0.0F || base == 1.0F) return Float.NaN + return (nativeMath.log(a.toDouble()) / nativeMath.log(base.toDouble())).toFloat() +} + +/** + * Computes the natural logarithm (base `E`) of the [a] value. + * + * Special cases: + * - `ln(NaN)` is `NaN` + * - `ln(x)` is `NaN` when `x < 0.0` + * - `ln(+Inf)` is `+Inf` + * - `ln(0.0)` is `-Inf` + */ +@InlineOnly +public inline fun ln(a: Float): Float = nativeMath.log(a.toDouble()).toFloat() + +/** + * Computes the decimal logarithm (base 10) of the [a] value. + * + * @see [ln] function for special cases. + */ +@InlineOnly +public inline fun log10(a: Float): Float = nativeMath.log10(a.toDouble()).toFloat() + +/** + * Computes the binary logarithm (base 2) of the [a] value. + * + * @see [ln] function for special cases. + */ +public fun log2(a: Float): Float = (nativeMath.log(a.toDouble()) / LN2).toFloat() + +/** + * 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 [ln] function + * @see [expm1] function + */ +@InlineOnly +public inline fun ln1p(a: Float): Float = nativeMath.log1p(a.toDouble()).toFloat() + +/** + * Rounds the given value [a] to an integer towards positive infinity. + + * @return the smallest Float value that is greater than the given value [a] and is a mathematical integer. + * + * Special cases: + * - `ceil(x)` is `x` where `x` is `NaN` or `+Inf` or `-Inf` or already a mathematical integer. + */ +@InlineOnly +public inline fun ceil(a: Float): Float = nativeMath.ceil(a.toDouble()).toFloat() + +/** + * Rounds the given value [a] to an integer towards negative infinity. + + * @return the largest Float value that is smaller than the given value [a] and is a mathematical integer. + * + * Special cases: + * - `floor(x)` is `x` where `x` is `NaN` or `+Inf` or `-Inf` or already a mathematical integer. + */ +@InlineOnly +public inline fun floor(a: Float): Float = nativeMath.floor(a.toDouble()).toFloat() + +/** + * Rounds the given value [a] to an integer towards zero. + * + * @return the value [a] having its fractional part truncated. + * + * Special cases: + * - `truncate(x)` is `x` where `x` is `NaN` or `+Inf` or `-Inf` or already a mathematical integer. + */ +public fun truncate(a: Float): Float = when { + a.isNaN() || a.isInfinite() -> a + a > 0 -> floor(a) + else -> ceil(a) +} + +/** + * Rounds the given value [a] towards the closest integer with ties rounded towards even integer. + * + * Special cases: + * - `round(x)` is `x` where `x` is `NaN` or `+Inf` or `-Inf` or already a mathematical integer. + */ +@InlineOnly +public inline fun round(a: Float): Float = nativeMath.rint(a.toDouble()).toFloat() + + +/** + * Returns the absolute value of the given value [a]. + * + * Special cases: + * - `abs(NaN)` is `NaN` + * + * @see absoluteValue extension property for [Float] + */ +@InlineOnly public inline fun abs(a: Float): Float = nativeMath.abs(a) -// also as extension val [sign] -public inline fun sgn(a: Float): Float = nativeMath.signum(a) -public inline fun max(a: Float, b: Float): Float = nativeMath.max(a, b) +/** + * Returns the sign of the given value [a]: + * - `-1.0` if the value is negative, + * - zero if the value is zero, + * - `1.0` if the value is positive + * + * Special case: + * - `sign(NaN)` is `NaN` + */ +@InlineOnly +public inline fun sign(a: Float): Float = nativeMath.signum(a) + + + +/** + * Returns the smaller of two values. + * + * If either value is `NaN`, then the result is `NaN`. + */ +@InlineOnly public inline fun min(a: Float, b: Float): Float = 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: Float, b: Float): Float = nativeMath.max(a, b) +// extensions + + +/** + * Raises this value to the power [other]. + * + * Special cases: + * - `x.pow(0.0)` is `1.0` + * - `x.pow(1.0) == x` + * - `x.pow(NaN)` is `NaN` + * - `NaN.pow(x)` is `NaN` for `x != 0.0` + * - `x.pow(Inf)` is `NaN` for `abs(x) == 1.0` + * - `x.pow(y)` is `NaN` for `x < 0` and `y` is finite and not an integer + */ +@InlineOnly +public inline fun Float.pow(other: Float): Float = nativeMath.pow(this.toDouble(), other.toDouble()).toFloat() + +/** + * Raises this value to the integer power [other]. + * + * See the other overload of [pow] for details. + */ +@InlineOnly +public inline fun Float.pow(other: Int): Float = nativeMath.pow(this.toDouble(), other.toDouble()).toFloat() + +/** + * Computes the remainder of division of this value by the [other] value according to the IEEE 754 standard. + * + * The result is computed as `r = this - (q * other)` where `q` is the quotient of division rounded to the nearest integer, + * `q = round(this / other)`. + * + * Special cases: + * - `x.IEEErem(y)` is `NaN`, when `x` is `NaN` or `y` is `NaN` or `x` is `+Inf|-Inf` or `y` is zero. + * - `x.IEEErem(y) == x` when `x` is finite and `y` is infinite. + * + * @see round + */ +@InlineOnly +public inline fun Float.IEEErem(other: Float): Float = nativeMath.IEEEremainder(this.toDouble(), other.toDouble()).toFloat() + +/** + * Returns the absolute value of this value. + * + * Special cases: + * - `NaN.absoluteValue` is `NaN` + * + * @see abs function + */ +@InlineOnly public inline val Float.absoluteValue: Float get() = nativeMath.abs(this) + +/** + * Returns the sign of this value: + * - `-1.0` if the value is negative, + * - zero if the value is zero, + * - `1.0` if the value is positive + * + * Special case: + * - `NaN.sign` is `NaN` + */ +@InlineOnly public inline val Float.sign: Float get() = nativeMath.signum(this) +/** + * Returns this value with the sign bit same as of the [sign] value. + * + * If [sign] is `NaN` the sign of the result is undefined. + */ +@InlineOnly public inline fun Float.withSign(sign: Float): Float = nativeMath.copySign(this, sign) +/** + * Returns this value with the sign bit same as of the [sign] value. + */ +@InlineOnly public inline fun Float.withSign(sign: Int): Float = nativeMath.copySign(this, sign.toFloat()) - +/** + * Returns the ulp of this value. + * + * An ulp is a positive distance between this value and the next nearest [Float] value larger in magnitude. + * + * Special Cases: + * - `NaN.ulp` is `NaN` + * - `x.ulp` is `+Inf` when `x` is `+Inf` or `-Inf` + * - `0.0.ulp` is `Float.NIN_VALUE` + */ +@InlineOnly 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) +/** + * Returns the [Float] value nearest to this value in direction of positive infinity. + */ +@InlineOnly +public inline fun Float.nextUp(): Float = nativeMath.nextUp(this) +/** + * Returns the [Float] value nearest to this value in direction of negative infinity. + */ +@InlineOnly +public inline fun Float.nextDown(): Float = nativeMath.nextAfter(this, Double.NEGATIVE_INFINITY) + +/** + * Returns the [Float] value nearest to this value in direction from this value towards the value [to]. + * + * Special cases: + * - `x.nextTowards(y)` is `NaN` if either `x` or `y` are `NaN` + * - `x.nextTowards(x) == x` + * + */ +@InlineOnly +public inline fun Float.nextTowards(to: Float): Float = nativeMath.nextAfter(this, to.toDouble()) + +/** + * Rounds this [Float] value to the nearest integer and converts the result to [Int]. + * Ties are rounded towards positive infinity. + * + * Special cases: + * - `x.roundToInt() == Int.MAX_VALUE` when `x > Int.MAX_VALUE` + * - `x.roundToInt() == Int.MIN_VALUE` when `x < Int.MIN_VALUE` + * + * @throws IllegalArgumentException when this value is `NaN` + */ public fun Float.roundToInt(): Int = if (isNaN()) throw IllegalArgumentException("Cannot round NaN value.") else nativeMath.round(this) + +/** + * Rounds this [Float] value to the nearest integer and converts the result to [Long]. + * Ties are rounded towards positive infinity. + * + * Special cases: + * - `x.roundToLong() == Long.MAX_VALUE` when `x > Long.MAX_VALUE` + * - `x.roundToLong() == Long.MIN_VALUE` when `x < Long.MIN_VALUE` + * + * @throws IllegalArgumentException when this value is `NaN` + */ public fun Float.roundToLong(): Long = toDouble().roundToLong() + // Int // also as extension val [absoluteValue] public inline fun abs(a: Int): Int = nativeMath.abs(a) diff --git a/libraries/stdlib/test/numbers/MathTest.kt b/libraries/stdlib/test/numbers/MathTest.kt index dfba448e2e7..c75e4c45160 100644 --- a/libraries/stdlib/test/numbers/MathTest.kt +++ b/libraries/stdlib/test/numbers/MathTest.kt @@ -27,6 +27,13 @@ fun assertAlmostEquals(expected: Double, actual: Double, tolerance: Double? = nu } } +fun assertAlmostEquals(expected: Float, actual: Float, tolerance: Double? = null) { + val tolerance_ = tolerance?.let { abs(it) } ?: 0.0000001 + if (abs(expected - actual) > tolerance_) { + assertEquals(expected, actual) + } +} + class DoubleMathTest { @Test fun trigonometric() { @@ -266,3 +273,247 @@ class DoubleMathTest { } +class FloatMathTest { + + companion object { + const val PI = kotlin.math.PI.toFloat() + const val E = kotlin.math.E.toFloat() + } + + @Test fun trigonometric() { + assertEquals(0.0F, sin(0.0F)) + assertAlmostEquals(0.0F, sin(PI)) + + assertEquals(0.0F, asin(0.0F)) + assertAlmostEquals(PI / 2, asin(1.0F)) + + assertEquals(1.0F, cos(0.0F)) + assertAlmostEquals(-1.0F, cos(PI)) + + assertEquals(0.0F, acos(1.0F)) + assertAlmostEquals(PI / 2, acos(0.0F)) + + assertEquals(0.0F, tan(0.0F)) + assertAlmostEquals(1.0F, tan(PI / 4)) + + assertAlmostEquals(0.0F, atan(0.0F)) + assertAlmostEquals(PI / 4, atan(1.0F)) + + assertAlmostEquals(PI / 4, atan2(10.0F, 10.0F)) + assertAlmostEquals(-PI / 4, atan2(Float.NEGATIVE_INFINITY, Float.POSITIVE_INFINITY)) + assertAlmostEquals(0.0F, atan2(0.0F, 0.0F)) + assertAlmostEquals(0.0F, atan2(0.0F, 10.0F)) + assertAlmostEquals(PI / 2, atan2(2.0F, 0.0F)) + + for (angle in listOf(Float.NaN, Float.POSITIVE_INFINITY, Float.NEGATIVE_INFINITY)) { + assertTrue(sin(angle).isNaN(), "sin($angle)") + assertTrue(cos(angle).isNaN(), "cos($angle)") + assertTrue(tan(angle).isNaN(), "tan($angle)") + } + + for (value in listOf(Float.NaN, 1.2F, -1.1F)) { + assertTrue(asin(value).isNaN()) + assertTrue(acos(value).isNaN()) + } + assertTrue(atan(Float.NaN).isNaN()) + assertTrue(atan2(Float.NaN, 0.0F).isNaN()) + assertTrue(atan2(0.0F, Float.NaN).isNaN()) + } + + @Test fun hyperbolic() { + assertEquals(Float.POSITIVE_INFINITY, sinh(Float.POSITIVE_INFINITY)) + assertEquals(Float.NEGATIVE_INFINITY, sinh(Float.NEGATIVE_INFINITY)) + assertTrue(sinh(Float.NaN).isNaN()) + + assertEquals(Float.POSITIVE_INFINITY, cosh(Float.POSITIVE_INFINITY)) + assertEquals(Float.POSITIVE_INFINITY, cosh(Float.NEGATIVE_INFINITY)) + assertTrue(cosh(Float.NaN).isNaN()) + + assertAlmostEquals(1.0F, tanh(Float.POSITIVE_INFINITY)) + assertAlmostEquals(-1.0F, tanh(Float.NEGATIVE_INFINITY)) + assertTrue(tanh(Float.NaN).isNaN()) + } + + @Test fun powers() { + assertEquals(5.0F, hypot(3.0F, 4.0F)) + assertEquals(Float.POSITIVE_INFINITY, hypot(Float.NEGATIVE_INFINITY, Float.NaN)) + assertEquals(Float.POSITIVE_INFINITY, hypot(Float.NaN, Float.POSITIVE_INFINITY)) + assertTrue(hypot(Float.NaN, 0.0F).isNaN()) + + assertEquals(1.0F, Float.NaN.pow(0.0F)) + assertEquals(1.0F, Float.POSITIVE_INFINITY.pow(0)) + assertEquals(49.0F, 7.0F.pow(2)) + assertEquals(0.25F, 2.0F.pow(-2)) + assertTrue(0.0F.pow(Float.NaN).isNaN()) + assertTrue(Float.NaN.pow(-1).isNaN()) + assertTrue((-7.0F).pow(1/3.0F).isNaN()) + assertTrue(1.0F.pow(Float.POSITIVE_INFINITY).isNaN()) + assertTrue((-1.0F).pow(Float.NEGATIVE_INFINITY).isNaN()) + + assertEquals(5.0F, sqrt(9.0F + 16.0F)) + assertTrue(sqrt(-1.0F).isNaN()) + assertTrue(sqrt(Float.NaN).isNaN()) + + assertTrue(exp(Float.NaN).isNaN()) + assertAlmostEquals(E, exp(1.0F)) + assertEquals(1.0F, exp(0.0F)) + assertEquals(0.0F, exp(Float.NEGATIVE_INFINITY)) + assertEquals(Float.POSITIVE_INFINITY, exp(Float.POSITIVE_INFINITY)) + + assertEquals(0.0F, expm1(0.0F)) + assertEquals(-1.0F, expm1(Float.NEGATIVE_INFINITY)) + assertEquals(Float.POSITIVE_INFINITY, expm1(Float.POSITIVE_INFINITY)) + } + + @Test fun logarithms() { + assertTrue(log(1.0F, Float.NaN).isNaN()) + assertTrue(log(Float.NaN, 1.0F).isNaN()) + assertTrue(log(-1.0F, 2.0F).isNaN()) + assertTrue(log(2.0F, -1.0F).isNaN()) + assertTrue(log(2.0F, 0.0F).isNaN()) + assertTrue(log(2.0F, 1.0F).isNaN()) + assertTrue(log(Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY).isNaN()) + assertEquals(-2.0F, log(0.25F, 2.0F)) + assertEquals(-0.5F, log(2.0F, 0.25F)) + assertEquals(Float.NEGATIVE_INFINITY, log(Float.POSITIVE_INFINITY, 0.25F)) + assertEquals(Float.POSITIVE_INFINITY, log(Float.POSITIVE_INFINITY, 2.0F)) + assertEquals(Float.NEGATIVE_INFINITY, log(0.0F, 2.0F)) + assertEquals(Float.POSITIVE_INFINITY, log(0.0F, 0.25F)) + + assertTrue(ln(Float.NaN).isNaN()) + assertTrue(ln(-1.0F).isNaN()) + assertAlmostEquals(1.0F, ln(E)) + assertEquals(Float.NEGATIVE_INFINITY, ln(0.0F)) + assertEquals(Float.POSITIVE_INFINITY, ln(Float.POSITIVE_INFINITY)) + + assertEquals(1.0F, log10(10.0F)) + assertAlmostEquals(-1.0F, log10(0.1F)) + + assertAlmostEquals(3.0F, log2(8.0F)) + assertEquals(-1.0F, log2(0.5F)) + + assertTrue(ln1p(Float.NaN).isNaN()) + assertTrue(ln1p(-1.1F).isNaN()) + assertEquals(0.0F, ln1p(0.0F)) + assertEquals(Float.NEGATIVE_INFINITY, ln1p(-1.0F)) + } + + @Test fun rounding() { + for (value in listOf(Float.NaN, Float.POSITIVE_INFINITY, Float.NEGATIVE_INFINITY, 0.0F, 1.0F, -10.0F)) { + assertEquals(value, ceil(value)) + assertEquals(value, floor(value)) + assertEquals(value, truncate(value)) + assertEquals(value, round(value)) + } + val data = arrayOf( // v floor trunc round ceil + floatArrayOf( 1.3F, 1.0F, 1.0F, 1.0F, 2.0F), + floatArrayOf(-1.3F, -2.0F, -1.0F, -1.0F, -1.0F), + floatArrayOf( 1.5F, 1.0F, 1.0F, 2.0F, 2.0F), + floatArrayOf(-1.5F, -2.0F, -1.0F, -2.0F, -1.0F), + floatArrayOf( 1.8F, 1.0F, 1.0F, 2.0F, 2.0F), + floatArrayOf(-1.8F, -2.0F, -1.0F, -2.0F, -1.0F), + + floatArrayOf( 2.3F, 2.0F, 2.0F, 2.0F, 3.0F), + floatArrayOf(-2.3F, -3.0F, -2.0F, -2.0F, -2.0F), + floatArrayOf( 2.5F, 2.0F, 2.0F, 2.0F, 3.0F), + floatArrayOf(-2.5F, -3.0F, -2.0F, -2.0F, -2.0F), + floatArrayOf( 2.8F, 2.0F, 2.0F, 3.0F, 3.0F), + floatArrayOf(-2.8F, -3.0F, -2.0F, -3.0F, -2.0F) + ) + for ((v, f, t, r, c) in data) { + assertEquals(f, floor(v), "floor($v)") + assertEquals(t, truncate(v), "truncate($v)") + assertEquals(r, round(v), "round($v)") + assertEquals(c, ceil(v), "ceil($v)") + } + } + + @Test fun roundingConversion() { + assertEquals(1L, 1.0F.roundToLong()) + assertEquals(1L, 1.1F.roundToLong()) + assertEquals(2L, 1.5F.roundToLong()) + assertEquals(3L, 2.5F.roundToLong()) + assertEquals(-2L, (-2.5F).roundToLong()) + assertEquals(-3L, (-2.6F).roundToLong()) + // assertEquals(9223372036854774784, (9223372036854774800.0F).roundToLong()) // platform-specific + assertEquals(Long.MAX_VALUE, Float.MAX_VALUE.roundToLong()) + assertEquals(Long.MIN_VALUE, (-Float.MAX_VALUE).roundToLong()) + assertEquals(Long.MAX_VALUE, Float.POSITIVE_INFINITY.roundToLong()) + assertEquals(Long.MIN_VALUE, Float.NEGATIVE_INFINITY.roundToLong()) + + assertFails { Float.NaN.roundToLong() } + + assertEquals(1, 1.0F.roundToInt()) + assertEquals(1, 1.1F.roundToInt()) + assertEquals(2, 1.5F.roundToInt()) + assertEquals(3, 2.5F.roundToInt()) + assertEquals(-2, (-2.5F).roundToInt()) + assertEquals(-3, (-2.6F).roundToInt()) + assertEquals(16777218, (16777218F).roundToInt()) + assertEquals(Int.MAX_VALUE, Float.MAX_VALUE.roundToInt()) + assertEquals(Int.MIN_VALUE, (-Float.MAX_VALUE).roundToInt()) + assertEquals(Int.MAX_VALUE, Float.POSITIVE_INFINITY.roundToInt()) + assertEquals(Int.MIN_VALUE, Float.NEGATIVE_INFINITY.roundToInt()) + + assertFails { Float.NaN.roundToInt() } + } + + @Test fun absoluteValue() { + assertTrue(abs(Float.NaN).isNaN()) + assertTrue(Float.NaN.absoluteValue.isNaN()) + + for (value in listOf(0.0F, Float.MIN_VALUE, 0.1F, 1.0F, 1000.0F, Float.MAX_VALUE, Float.POSITIVE_INFINITY)) { + assertEquals(value, value.absoluteValue) + assertEquals(value, (-value).absoluteValue) + assertEquals(value, abs(value)) + assertEquals(value, abs(-value)) + } + } + + @Test fun signs() { + assertTrue(sign(Float.NaN).isNaN()) + assertTrue(Float.NaN.sign.isNaN()) + + val negatives = listOf(Float.NEGATIVE_INFINITY, -Float.MAX_VALUE, -1.0F, -Float.MIN_VALUE) + for (value in negatives) { + assertEquals(-1.0F, sign(value)) + assertEquals(-1.0F, value.sign) + } + + val zeroes = listOf(0.0F, -0.0F) + for (value in zeroes) { + assertEquals(value, sign(value)) + assertEquals(value, value.sign) + } + + + val positives = listOf(Float.POSITIVE_INFINITY, Float.MAX_VALUE, 1.0F, Float.MIN_VALUE) + for (value in positives) { + assertEquals(1.0F, sign(value)) + assertEquals(1.0F, value.sign) + } + + val allValues = negatives + positives + for (a in allValues) { + for (b in allValues) { + val r = a.withSign(b) + assertEquals(a.absoluteValue, r.absoluteValue) + assertEquals(b.sign, r.sign) + } + + val rp0 = a.withSign(0.0F) + assertEquals(1.0F, rp0.sign) + assertEquals(a.absoluteValue, rp0.absoluteValue) + + val rm0 = a.withSign(-0.0F) + assertEquals(-1.0F, rm0.sign) + assertEquals(a.absoluteValue, rm0.absoluteValue) + + val ri = a.withSign(-1) + assertEquals(-1.0F, ri.sign) + assertEquals(a.absoluteValue, ri.absoluteValue) + } + } + +} +