diff --git a/kotlin-native/runtime/src/main/cpp/KotlinMath.cpp b/kotlin-native/runtime/src/main/cpp/KotlinMath.cpp index 17e19c8e123..6cb7452255e 100644 --- a/kotlin-native/runtime/src/main/cpp/KotlinMath.cpp +++ b/kotlin-native/runtime/src/main/cpp/KotlinMath.cpp @@ -153,6 +153,8 @@ KDouble Kotlin_math_round(KDouble x) { return rint(x); } KDouble Kotlin_math_abs(KDouble x) { return fabs(x); } +KDouble Kotlin_math_cbrt(KDouble x) { return cbrt(x); } + // extensions KDouble Kotlin_math_Double_pow(KDouble thiz, KDouble x) { @@ -228,6 +230,8 @@ KFloat Kotlin_math_roundf(KFloat x) { return rintf(x); } KFloat Kotlin_math_absf(KFloat x) { return fabsf(x); } +KFloat Kotlin_math_cbrtf(KFloat x) { return cbrtf(x); } + // extensions KFloat Kotlin_math_Float_pow(KFloat thiz, KFloat x) { @@ -311,6 +315,8 @@ KDouble Kotlin_math_abs(KDouble x) { RETURN_RESULT_OF_JS_CALL(knjs__Math_abs, KDouble Kotlin_math_atan2(KDouble y, KDouble x) { RETURN_RESULT_OF_JS_CALL2(knjs__Math_atan2, y, x); } KDouble Kotlin_math_hypot(KDouble x, KDouble y) { RETURN_RESULT_OF_JS_CALL2(knjs__Math_hypot, x, y); } +KDouble Kotlin_math_cbrt(KDouble x) { RETURN_RESULT_OF_JS_CALL(knjs__Math_cbrt, x); } + // extensions KDouble Kotlin_math_Double_pow(KDouble thiz, KDouble x) { RETURN_RESULT_OF_JS_CALL2(knjs__Math_pow, thiz, x); } @@ -406,6 +412,8 @@ KFloat Kotlin_math_absf(KFloat x) { return (KFloat)Kotlin_math_abs (x); } KFloat Kotlin_math_atan2f(KFloat y, KFloat x) { return (KFloat)Kotlin_math_atan2(y, x); } KFloat Kotlin_math_hypotf(KFloat x, KFloat y) { return (KFloat)Kotlin_math_hypot(x, y); } +KDouble Kotlin_math_cbrtf(KFloat x) { return (KFloat)Kotlin_math_cbrt(x); } + // extensions KFloat Kotlin_math_Float_pow(KFloat thiz, KFloat x) { return (KFloat)Kotlin_math_Double_pow(thiz, x); } @@ -504,6 +512,8 @@ KDouble Kotlin_math_round(KDouble x) { NotImplemented(); } KDouble Kotlin_math_abs(KDouble x) { NotImplemented(); } +KDouble Kotlin_math_cbrt(KDouble x) { NotImplemented(); } + // extensions KDouble Kotlin_math_Double_pow(KDouble thiz, KDouble x) { NotImplemented(); } @@ -551,6 +561,8 @@ KFloat Kotlin_math_roundf(KFloat x) { NotImplemented(); } KFloat Kotlin_math_absf(KFloat x) { NotImplemented(); } +KFloat Kotlin_math_cbrtf(KFloat x) { NotImplemented(); } + // extensions KFloat Kotlin_math_Float_pow(KFloat thiz, KFloat x) { NotImplemented(); } diff --git a/kotlin-native/runtime/src/main/kotlin/kotlin/math/math.kt b/kotlin-native/runtime/src/main/kotlin/kotlin/math/math.kt index 66ab5dd5f78..25c5a848195 100644 --- a/kotlin-native/runtime/src/main/kotlin/kotlin/math/math.kt +++ b/kotlin-native/runtime/src/main/kotlin/kotlin/math/math.kt @@ -384,6 +384,23 @@ public actual fun max(a: Double, b: Double): Double = when { else -> if (a > b) a else b } + +/** + * Returns the cube root of [x]. For any `x`, `cbrt(-x) == -cbrt(x)`; + * that is, the cube root of a negative value is the negative of the cube root + * of that value's magnitude. Special cases: + * + * Special cases: + * - If the argument is `NaN`, then the result is `NaN`. + * - If the argument is infinite, then the result is an infinity with the same sign as the argument. + * - If the argument is zero, then the result is a zero with the same sign as the argument. + */ +@SinceKotlin("1.7") +@ExperimentalStdlibApi +@GCUnsafeCall("Kotlin_math_cbrt") +public external actual fun cbrt(x: Double): Double + + // extensions /** @@ -934,6 +951,23 @@ public actual fun max(a: Float, b: Float): Float = when { else -> if (a > b) a else b } + +/** + * Returns the cube root of [x]. For any `x`, `cbrt(-x) == -cbrt(x)`; + * that is, the cube root of a negative value is the negative of the cube root + * of that value's magnitude. Special cases: + * + * Special cases: + * - If the argument is `NaN`, then the result is `NaN`. + * - If the argument is infinite, then the result is an infinity with the same sign as the argument. + * - If the argument is zero, then the result is a zero with the same sign as the argument. + */ +@SinceKotlin("1.7") +@ExperimentalStdlibApi +@GCUnsafeCall("Kotlin_math_cbrtf") +public external actual fun cbrt(x: Float): Float + + // extensions /** diff --git a/libraries/stdlib/api/js-v1/kotlin.math.kt b/libraries/stdlib/api/js-v1/kotlin.math.kt index c979940d701..d85b4b28f8b 100644 --- a/libraries/stdlib/api/js-v1/kotlin.math.kt +++ b/libraries/stdlib/api/js-v1/kotlin.math.kt @@ -107,6 +107,16 @@ public inline fun atanh(x: kotlin.Double): kotlin.Double @kotlin.internal.InlineOnly public inline fun atanh(x: kotlin.Float): kotlin.Float +@kotlin.SinceKotlin(version = "1.7") +@kotlin.ExperimentalStdlibApi +@kotlin.internal.InlineOnly +public inline fun cbrt(x: kotlin.Double): kotlin.Double + +@kotlin.SinceKotlin(version = "1.7") +@kotlin.ExperimentalStdlibApi +@kotlin.internal.InlineOnly +public inline fun cbrt(x: kotlin.Float): kotlin.Float + @kotlin.SinceKotlin(version = "1.2") @kotlin.internal.InlineOnly public inline fun ceil(x: kotlin.Double): kotlin.Double diff --git a/libraries/stdlib/api/js/kotlin.math.kt b/libraries/stdlib/api/js/kotlin.math.kt index c979940d701..d85b4b28f8b 100644 --- a/libraries/stdlib/api/js/kotlin.math.kt +++ b/libraries/stdlib/api/js/kotlin.math.kt @@ -107,6 +107,16 @@ public inline fun atanh(x: kotlin.Double): kotlin.Double @kotlin.internal.InlineOnly public inline fun atanh(x: kotlin.Float): kotlin.Float +@kotlin.SinceKotlin(version = "1.7") +@kotlin.ExperimentalStdlibApi +@kotlin.internal.InlineOnly +public inline fun cbrt(x: kotlin.Double): kotlin.Double + +@kotlin.SinceKotlin(version = "1.7") +@kotlin.ExperimentalStdlibApi +@kotlin.internal.InlineOnly +public inline fun cbrt(x: kotlin.Float): kotlin.Float + @kotlin.SinceKotlin(version = "1.2") @kotlin.internal.InlineOnly public inline fun ceil(x: kotlin.Double): kotlin.Double diff --git a/libraries/stdlib/common/src/kotlin/MathH.kt b/libraries/stdlib/common/src/kotlin/MathH.kt index aeb6c0a8fbd..221aa3244f9 100644 --- a/libraries/stdlib/common/src/kotlin/MathH.kt +++ b/libraries/stdlib/common/src/kotlin/MathH.kt @@ -354,6 +354,22 @@ public expect fun min(a: Double, b: Double): Double @SinceKotlin("1.2") public expect fun max(a: Double, b: Double): Double + +/** + * Returns the cube root of [x]. For any `x`, `cbrt(-x) == -cbrt(x)`; + * that is, the cube root of a negative value is the negative of the cube root + * of that value's magnitude. Special cases: + * + * Special cases: + * - If the argument is `NaN`, then the result is `NaN`. + * - If the argument is infinite, then the result is an infinity with the same sign as the argument. + * - If the argument is zero, then the result is a zero with the same sign as the argument. + */ +@SinceKotlin("1.7") +@ExperimentalStdlibApi +public expect fun cbrt(x: Double): Double + + // extensions /** @@ -817,6 +833,22 @@ public expect fun min(a: Float, b: Float): Float @SinceKotlin("1.2") public expect fun max(a: Float, b: Float): Float + +/** + * Returns the cube root of [x]. For any `x`, `cbrt(-x) == -cbrt(x)`; + * that is, the cube root of a negative value is the negative of the cube root + * of that value's magnitude. Special cases: + * + * Special cases: + * - If the argument is `NaN`, then the result is `NaN`. + * - If the argument is infinite, then the result is an infinity with the same sign as the argument. + * - If the argument is zero, then the result is a zero with the same sign as the argument. + */ +@SinceKotlin("1.7") +@ExperimentalStdlibApi +public expect fun cbrt(x: Float): Float + + // extensions diff --git a/libraries/stdlib/js/src/kotlin/js/js.math.kt b/libraries/stdlib/js/src/kotlin/js/js.math.kt index de2d771e5e5..b02430c985b 100644 --- a/libraries/stdlib/js/src/kotlin/js/js.math.kt +++ b/libraries/stdlib/js/src/kotlin/js/js.math.kt @@ -29,6 +29,7 @@ internal external object JsMath { fun sqrt(value: Double): Double fun tan(value: Double): Double fun log(value: Double): Double + fun cbrt(value: Double): Double fun pow(base: Double, exp: Double): Double fun round(value: Number): Double fun floor(value: Number): Double diff --git a/libraries/stdlib/js/src/kotlin/math.kt b/libraries/stdlib/js/src/kotlin/math.kt index 9421a51ab1b..b0081b26b2f 100644 --- a/libraries/stdlib/js/src/kotlin/math.kt +++ b/libraries/stdlib/js/src/kotlin/math.kt @@ -380,6 +380,23 @@ public actual inline fun min(a: Double, b: Double): Double = nativeMath.min(a, b @InlineOnly public actual inline fun max(a: Double, b: Double): Double = nativeMath.max(a, b) + +/** + * Returns the cube root of [x]. For any `x`, `cbrt(-x) == -cbrt(x)`; + * that is, the cube root of a negative value is the negative of the cube root + * of that value's magnitude. Special cases: + * + * Special cases: + * - If the argument is `NaN`, then the result is `NaN`. + * - If the argument is infinite, then the result is an infinity with the same sign as the argument. + * - If the argument is zero, then the result is a zero with the same sign as the argument. + */ +@SinceKotlin("1.7") +@ExperimentalStdlibApi +@InlineOnly +public actual inline fun cbrt(x: Double): Double = nativeMath.cbrt(x) + + // extensions /** @@ -900,6 +917,23 @@ public actual inline fun min(a: Float, b: Float): Float = nativeMath.min(a, b) @InlineOnly public actual inline fun max(a: Float, b: Float): Float = nativeMath.max(a, b) + +/** + * Returns the cube root of [x]. For any `x`, `cbrt(-x) == -cbrt(x)`; + * that is, the cube root of a negative value is the negative of the cube root + * of that value's magnitude. Special cases: + * + * Special cases: + * - If the argument is `NaN`, then the result is `NaN`. + * - If the argument is infinite, then the result is an infinity with the same sign as the argument. + * - If the argument is zero, then the result is a zero with the same sign as the argument. + */ +@SinceKotlin("1.7") +@ExperimentalStdlibApi +@InlineOnly +public actual inline fun cbrt(x: Float): Float = nativeMath.cbrt(x.toDouble()).toFloat() + + // extensions diff --git a/libraries/stdlib/jvm/src/kotlin/util/MathJVM.kt b/libraries/stdlib/jvm/src/kotlin/util/MathJVM.kt index f2d15497d41..225d716d789 100644 --- a/libraries/stdlib/jvm/src/kotlin/util/MathJVM.kt +++ b/libraries/stdlib/jvm/src/kotlin/util/MathJVM.kt @@ -463,6 +463,23 @@ public actual inline fun min(a: Double, b: Double): Double = nativeMath.min(a, b @InlineOnly public actual inline fun max(a: Double, b: Double): Double = nativeMath.max(a, b) + +/** + * Returns the cube root of [x]. For any `x`, `cbrt(-x) == -cbrt(x)`; + * that is, the cube root of a negative value is the negative of the cube root + * of that value's magnitude. Special cases: + * + * Special cases: + * - If the argument is `NaN`, then the result is `NaN`. + * - If the argument is infinite, then the result is an infinity with the same sign as the argument. + * - If the argument is zero, then the result is a zero with the same sign as the argument. + */ +@SinceKotlin("1.7") +@ExperimentalStdlibApi +@InlineOnly +public actual inline fun cbrt(x: Double): Double = nativeMath.cbrt(x) + + // extensions @@ -993,8 +1010,24 @@ public actual inline fun min(a: Float, b: Float): Float = nativeMath.min(a, b) @InlineOnly public actual inline fun max(a: Float, b: Float): Float = nativeMath.max(a, b) -// extensions +/** + * Returns the cube root of [x]. For any `x`, `cbrt(-x) == -cbrt(x)`; + * that is, the cube root of a negative value is the negative of the cube root + * of that value's magnitude. Special cases: + * + * Special cases: + * - If the argument is `NaN`, then the result is `NaN`. + * - If the argument is infinite, then the result is an infinity with the same sign as the argument. + * - If the argument is zero, then the result is a zero with the same sign as the argument. + */ +@SinceKotlin("1.7") +@ExperimentalStdlibApi +@InlineOnly +public actual inline fun cbrt(x: Float): Float = nativeMath.cbrt(x.toDouble()).toFloat() + + +// extensions /** * Raises this value to the power [x]. diff --git a/libraries/stdlib/test/numbers/MathTest.kt b/libraries/stdlib/test/numbers/MathTest.kt index 1ceb0a1680e..584543a4449 100644 --- a/libraries/stdlib/test/numbers/MathTest.kt +++ b/libraries/stdlib/test/numbers/MathTest.kt @@ -22,6 +22,20 @@ fun assertAlmostEquals(expected: Float, actual: Float, tolerance: Double? = null } } +// For Kotlin JS tests +private val Float.ulpCommon: Float + get() = when { + isNaN() -> Float.NaN + isInfinite() -> Float.POSITIVE_INFINITY + this == Float.MAX_VALUE || this == -Float.MAX_VALUE -> 2.0f.pow(104) + else -> { + val d = absoluteValue + // Ensure we never have -0.0f + val valueOrPositiveZero = (this + 0.0f).toBits(); + Float.fromBits(valueOrPositiveZero + (if (valueOrPositiveZero >= 0) 1 else -1)) - d + } + } + class DoubleMathTest { @Test fun trigonometric() { @@ -121,6 +135,30 @@ class DoubleMathTest { } } + @Test fun cubeRoots() { + val testingPairs = mapOf( + Double.NaN to Double.NaN, + Double.POSITIVE_INFINITY to Double.POSITIVE_INFINITY, + Double.NEGATIVE_INFINITY to Double.NEGATIVE_INFINITY, + Double.fromBits(0x0010000000000000) to 2.812644285236262E-103, // smallest normal value + Double.fromBits(0x1L) to 1.7031839360032603E-108, // smallest value (denormal) + Double.MAX_VALUE to 5.643803094122362E102, + 0.0 to 0.0, + 0.9 to 0.9654893846056297, + 9.9 to 2.1472291690189413, + 27.0 to 3.0, + 399.1289 to 7.362710510208026, + 8123.452 to 20.102351976782558, + 21717.639 to 27.9, + 392890.22 to 73.24147345684439 + ) + + for ((x, result) in testingPairs) { + assertEquals(result, cbrt(x), if (result.isFinite()) 2.0 * result.ulp else 0.0) + assertEquals(cbrt(-x), -cbrt(x)) + } + } + @Test fun powers() { assertEquals(5.0, hypot(3.0, 4.0)) assertEquals(Double.POSITIVE_INFINITY, hypot(Double.NEGATIVE_INFINITY, Double.NaN)) @@ -468,6 +506,30 @@ class FloatMathTest { } } + @Test fun cubeRoots() { + val testingPairs = mapOf( + Float.NaN to Float.NaN, + Float.POSITIVE_INFINITY to Float.POSITIVE_INFINITY, + Float.NEGATIVE_INFINITY to Float.NEGATIVE_INFINITY, + Float.fromBits(0x00800000) to 2.2737368E-13f, // smallest normal value + Float.fromBits(0x1) to 1.1190347E-15f, // smallest value (denormal) + Float.MAX_VALUE to 6.9814636E12f, + 0.0f to 0.0f, + 0.9f to 0.9654894f, + 9.9f to 2.1472292f, + 27.0f to 3.0f, + 399.1289f to 7.3627105f, + 8123.452f to 20.102352f, + 21717.639f to 27.9f, + 392890.22f to 73.24147f + ) + + for ((x, result) in testingPairs) { + assertEquals(result, cbrt(x), if (result.isFinite()) 2.0f * result.ulpCommon else 0.0f) + assertEquals(cbrt(-x), -cbrt(x)) + } + } + @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/stdlib/wasm/src/kotlin/math/fdlibm/s_cbrt.kt b/libraries/stdlib/wasm/src/kotlin/math/fdlibm/s_cbrt.kt new file mode 100644 index 00000000000..486358c9ec2 --- /dev/null +++ b/libraries/stdlib/wasm/src/kotlin/math/fdlibm/s_cbrt.kt @@ -0,0 +1,76 @@ +/* @(#)s_cbrt.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + * + */ + +/* ieee_cbrt(x) + * Return cube root of x + */ + +package kotlin.math.fdlibm + +private const val B1 = 715094163 /* B1 = (682-0.03306235651)*2**20 */ +private const val B2 = 696219795 /* B2 = (664-0.03306235651)*2**20 */ +private const val C = 5.42857142857142815906e-01 /* 19/35 = 0x3FE15F15, 0xF15F15F1 */ +private const val D = -7.05306122448979611050e-01 /* -864/1225 = 0xBFE691DE, 0x2532C834 */ +private const val E = 1.41428571428571436819e+00 /* 99/70 = 0x3FF6A0EA, 0x0EA0EA0F */ +private const val F = 1.60714285714285720630e+00 /* 45/28 = 0x3FF9B6DB, 0x6DB6DB6E */ +private const val G = 3.57142857142857150787e-01 /* 5/14 = 0x3FD6DB6D, 0xB6DB6DB7 */ + +internal fun __ieee754_cbrt(v: Double): Double { + var hx: Int = 0 + var r: Double = 0.0 + var s: Double = 0.0 + var t: Double = 0.0 + var w: Double = 0.0 + var sign: Int = 0 + + var x = v + + hx = __HI(x) /* high word of x */ + sign = hx and 0x80000000.toInt() /* sign= sign(x) */ + hx = hx xor sign + if (hx >= 0x7ff00000) { /* ieee_cbrt(NaN,INF) is itself */ + return (x + x) + } + if ((hx or __LO(x)) == 0) { + return (x) /* ieee_cbrt(0) is itself */ + } + + x = doubleSetWord(d = x, hi = hx) /* x <- |x| */ + /* rough cbrt to 5 bits */ + if (hx < 0x00100000) { /* subnormal number */ + t = doubleSetWord(d = t, hi = 0x43500000) /* set t= 2**54 */ + t *= x + t = doubleSetWord(d = t, hi = __HI(t) / 3 + B2) + } else { + t = doubleSetWord(d = t, hi = hx / 3 + B1) + } + + /* new cbrt to 23 bits, may be implemented in single precision */ + r = t * t / x + s = C + r * t + t *= G + F / (s + E + D / s) + + /* chopped to 20 bits and make it larger than ieee_cbrt(x) */ + t = doubleSetWord(d = t, hi = __HI(t) + 0x00000001, lo = 0) + + /* one step newton iteration to 53 bits with error less than 0.667 ulps */ + s = t * t /* t*t is exact */ + r = x / s + w = t + t + r = (r - t) / (w + r) /* r-s is exact */ + t = t + t * r + + /* retore the sign bit */ + t = doubleSetWord(d = t, hi = __HI(t) or sign) + return (t) +} diff --git a/libraries/stdlib/wasm/src/kotlin/math/math.kt b/libraries/stdlib/wasm/src/kotlin/math/math.kt index 9de2fc101a7..7265d7e0f6c 100644 --- a/libraries/stdlib/wasm/src/kotlin/math/math.kt +++ b/libraries/stdlib/wasm/src/kotlin/math/math.kt @@ -349,6 +349,21 @@ public actual fun min(a: Double, b: Double): Double = kotlin.wasm.internal.wasm_ @SinceKotlin("1.2") public actual fun max(a: Double, b: Double): Double = kotlin.wasm.internal.wasm_f64_max(a, b) +/** + * Returns the cube root of [x]. For any `x`, `cbrt(-x) == -cbrt(x)`; + * that is, the cube root of a negative value is the negative of the cube root + * of that value's magnitude. Special cases: + * + * Special cases: + * - If the argument is `NaN`, then the result is `NaN`. + * - If the argument is infinite, then the result is an infinity with the same sign as the argument. + * - If the argument is zero, then the result is a zero with the same sign as the argument. + */ +@SinceKotlin("1.7") +@ExperimentalStdlibApi +public actual fun cbrt(x: Double): Double = kotlin.math.fdlibm.__ieee754_cbrt(x) + + // extensions /** @@ -841,6 +856,22 @@ public actual fun min(a: Float, b: Float): Float = kotlin.wasm.internal.wasm_f32 @SinceKotlin("1.2") public actual fun max(a: Float, b: Float): Float = kotlin.wasm.internal.wasm_f32_max(a ,b) + +/** + * Returns the cube root of [x]. For any `x`, `cbrt(-x) == -cbrt(x)`; + * that is, the cube root of a negative value is the negative of the cube root + * of that value's magnitude. Special cases: + * + * Special cases: + * - If the argument is `NaN`, then the result is `NaN`. + * - If the argument is infinite, then the result is an infinity with the same sign as the argument. + * - If the argument is zero, then the result is a zero with the same sign as the argument. + */ +@SinceKotlin("1.7") +@ExperimentalStdlibApi +public actual fun cbrt(x: Float): Float = kotlin.math.fdlibm.__ieee754_cbrt(x.toDouble()).toFloat() + + // extensions