diff --git a/libraries/stdlib/wasm/src/kotlin/Math.kt b/libraries/stdlib/wasm/src/kotlin/Math.kt index 48d6272c640..7a6e665663f 100644 --- a/libraries/stdlib/wasm/src/kotlin/Math.kt +++ b/libraries/stdlib/wasm/src/kotlin/Math.kt @@ -907,7 +907,7 @@ public actual fun Float.roundToLong(): Long = TODO("Wasm stdlib: Math") * @see absoluteValue extension property for [Int] */ @SinceKotlin("1.2") -public actual fun abs(n: Int): Int = TODO("Wasm stdlib: Math") +public actual fun abs(n: Int): Int = if (n < 0) -n else n /** * Returns the smaller of two values. diff --git a/libraries/stdlib/wasm/src/kotlin/Text.kt b/libraries/stdlib/wasm/src/kotlin/Text.kt index da36ae33dcf..48eb7a0703e 100644 --- a/libraries/stdlib/wasm/src/kotlin/Text.kt +++ b/libraries/stdlib/wasm/src/kotlin/Text.kt @@ -868,7 +868,7 @@ public actual fun String.toLong(radix: Int): Long = toLongOrNull(radix) ?: numbe * Parses the string as a [Double] number and returns the result. * @throws NumberFormatException if the string is not a valid representation of a number. */ -public actual fun String.toDouble(): Double = TODO("Wasm stdlib: Text") +public actual fun String.toDouble(): Double = kotlin.text.parseDouble(this) /** * Parses the string as a [Float] number and returns the result. diff --git a/libraries/stdlib/wasm/src/kotlin/text/FloatingPointConverter.kt b/libraries/stdlib/wasm/src/kotlin/text/FloatingPointConverter.kt new file mode 100644 index 00000000000..fea4c60a12f --- /dev/null +++ b/libraries/stdlib/wasm/src/kotlin/text/FloatingPointConverter.kt @@ -0,0 +1,285 @@ +/* + * Licensed to the .NET Foundation under one or more agreements. + * The .NET Foundation licenses this file to you under the MIT license. + */ + +package kotlin.text + +import kotlin.math.abs + +private val s_Pow10MantissaTable: ULongArray = ulongArrayOf( + // powers of 10 + 0xA0000000_00000000UL, // 1 + 0xC8000000_00000000UL, // 2 + 0xFA000000_00000000UL, // 3 + 0x9C400000_00000000UL, // 4 + 0xC3500000_00000000UL, // 5 + 0xF4240000_00000000UL, // 6 + 0x98968000_00000000UL, // 7 + 0xBEBC2000_00000000UL, // 8 + 0xEE6B2800_00000000UL, // 9 + 0x9502F900_00000000UL, // 10 + 0xBA43B740_00000000UL, // 11 + 0xE8D4A510_00000000UL, // 12 + 0x9184E72A_00000000UL, // 13 + 0xB5E620F4_80000000UL, // 14 + 0xE35FA931_A0000000UL, // 15 + + // powers of 0.1 + 0xCCCCCCCC_CCCCCCCDUL, // 1 + 0xA3D70A3D_70A3D70BUL, // 2 + 0x83126E97_8D4FDF3CUL, // 3 + 0xD1B71758_E219652EUL, // 4 + 0xA7C5AC47_1B478425UL, // 5 + 0x8637BD05_AF6C69B7UL, // 6 + 0xD6BF94D5_E57A42BEUL, // 7 + 0xABCC7711_8461CEFFUL, // 8 + 0x89705F41_36B4A599UL, // 9 + 0xDBE6FECE_BDEDD5C2UL, // 10 + 0xAFEBFF0B_CB24AB02UL, // 11 + 0x8CBCCC09_6F5088CFUL, // 12 + 0xE12E1342_4BB40E18UL, // 13 + 0xB424DC35_095CD813UL, // 14 + 0x901D7CF7_3AB0ACDCUL, // 15 +) + +private val s_Pow10ExponentTable: ShortArray = shortArrayOf( + // exponents for both powers of 10 and 0.1 + 4, // 1 + 7, // 2 + 10, // 3 + 14, // 4 + 17, // 5 + 20, // 6 + 24, // 7 + 27, // 8 + 30, // 9 + 34, // 10 + 37, // 11 + 40, // 12 + 44, // 13 + 47, // 14 + 50, // 15 +) + +private val s_Pow10By16MantissaTable: ULongArray = ulongArrayOf( + // powers of 10^16 + 0x8E1BC9BF_04000000UL, // 1 + 0x9DC5ADA8_2B70B59EUL, // 2 + 0xAF298D05_0E4395D6UL, // 3 + 0xC2781F49_FFCFA6D4UL, // 4 + 0xD7E77A8F_87DAF7FAUL, // 5 + 0xEFB3AB16_C59B14A0UL, // 6 + 0x850FADC0_9923329CUL, // 7 + 0x93BA47C9_80E98CDEUL, // 8 + 0xA402B9C5_A8D3A6E6UL, // 9 + 0xB616A12B_7FE617A8UL, // 10 + 0xCA28A291_859BBF90UL, // 11 + 0xE070F78D_39275566UL, // 12 + 0xF92E0C35_37826140UL, // 13 + 0x8A5296FF_E33CC92CUL, // 14 + 0x9991A6F3_D6BF1762UL, // 15 + 0xAA7EEBFB_9DF9DE8AUL, // 16 + 0xBD49D14A_A79DBC7EUL, // 17 + 0xD226FC19_5C6A2F88UL, // 18 + 0xE950DF20_247C83F8UL, // 19 + 0x81842F29_F2CCE373UL, // 20 + 0x8FCAC257_558EE4E2UL, // 21 + + // powers of 0.1^16 + 0xE69594BE_C44DE160UL, // 1 + 0xCFB11EAD_453994C3UL, // 2 + 0xBB127C53_B17EC165UL, // 3 + 0xA87FEA27_A539E9B3UL, // 4 + 0x97C560BA_6B0919B5UL, // 5 + 0x88B402F7_FD7553ABUL, // 6 + 0xF64335BC_F065D3A0UL, // 7 + 0xDDD0467C_64BCE4C4UL, // 8 + 0xC7CABA6E_7C5382EDUL, // 9 + 0xB3F4E093_DB73A0B7UL, // 10 + 0xA21727DB_38CB0053UL, // 11 + 0x91FF8377_5423CC29UL, // 12 + 0x8380DEA9_3DA4BC82UL, // 13 + 0xECE53CEC_4A314F00UL, // 14 + 0xD5605FCD_CF32E217UL, // 15 + 0xC0314325_637A1978UL, // 16 + 0xAD1C8EAB_5EE43BA2UL, // 17 + 0x9BECCE62_836AC5B0UL, // 18 + 0x8C71DCD9_BA0B495CUL, // 19 + 0xFD00B897_47823938UL, // 20 + 0xE3E27A44_4D8D991AUL, // 21 +) + +private val s_Pow10By16ExponentTable: ShortArray = shortArrayOf( + // exponents for both powers of 10^16 and 0.1^16 + 54, // 1 + 107, // 2 + 160, // 3 + 213, // 4 + 266, // 5 + 319, // 6 + 373, // 7 + 426, // 8 + 479, // 9 + 532, // 10 + 585, // 11 + 638, // 12 + 691, // 13 + 745, // 14 + 798, // 15 + 851, // 16 + 904, // 17 + 957, // 18 + 1010, // 19 + 1064, // 20 + 1117, // 21 +) + + +// get 32-bit integer from at most 9 digits +private fun digitsToInt(p: String, pIndex: Int, count: Int): UInt { + var res: UInt = (p[pIndex] - '0').toUInt() + for (index in 1 until count) { + res = (10U * res) + (p[pIndex + index] - '0').toUInt() + } + return res +} + +private fun mul32x32To64(a: UInt, b: UInt): ULong = a.toULong() * b.toULong() + +private fun mul64Lossy(a: ULong, b: ULong): ULong { + // it's ok to lose some precision here - Mul64 will be called + // at most twice during the conversion, so the error won't propagate + // to any of the 53 significant bits of the result + return mul32x32To64((a shr 32).toUInt(), (b shr 32).toUInt()) + + (mul32x32To64((a shr 32).toUInt(), b.toUInt()) shr 32) + + (mul32x32To64((a).toUInt(), (b shr 32).toUInt()) shr 32) +} + +//TODO: +//This is partially implementation of parser. The issue with it is incorrect even rounding so the result number +//could slightly different from other backends. True implementation needs Big Integer +//Original implementation https://github.com/mono/corert/blob/master/src/System.Private.CoreLib/src/System/Number.CoreRT.cs +internal fun numberToDouble(signed: Boolean, numberScale: Int, number: String): Double { + var srcIndex = 0 + val total = number.length + var remaining = total + + // skip the leading zeros + while (remaining != 0 && number[srcIndex] == '0') { + remaining-- + srcIndex++ + } + + if (remaining == 0) { + return if(signed) -0.0 else 0.0 + } + + var count: Int = minOf(remaining, 9) + remaining -= count + var resultValue = digitsToInt(number, srcIndex, count).toULong() + srcIndex += count + + if (remaining > 0) { + count = minOf(remaining, 9) + remaining -= count + + // get the denormalized power of 10 + val mult: UInt = (s_Pow10MantissaTable[count - 1] shr (64 - s_Pow10ExponentTable[count - 1])).toUInt() + resultValue = mul32x32To64((resultValue).toUInt(), mult) + digitsToInt(number, srcIndex, count) + srcIndex += count + } + + val scale = numberScale - (total - remaining) + val absScale: Int = abs(scale) + if (absScale >= 22 * 16) { + // overflow / underflow + return if (scale > 0) { + if(signed) Double.NEGATIVE_INFINITY else Double.POSITIVE_INFINITY + } else { + if(signed) -0.0 else 0.0 + } + } + + var exp = 64 + + // normalize the mantissa + if ((resultValue and 0xFFFFFFFF_00000000UL) == 0UL) { resultValue = resultValue shl 32; exp -= 32 } + if ((resultValue and 0xFFFF0000_00000000UL) == 0UL) { resultValue = resultValue shl 16; exp -= 16 } + if ((resultValue and 0xFF000000_00000000UL) == 0UL) { resultValue = resultValue shl 8; exp -= 8 } + if ((resultValue and 0xF0000000_00000000UL) == 0UL) { resultValue = resultValue shl 4; exp -= 4 } + if ((resultValue and 0xC0000000_00000000UL) == 0UL) { resultValue = resultValue shl 2; exp -= 2 } + if ((resultValue and 0x80000000_00000000UL) == 0UL) { resultValue = resultValue shl 1; exp -= 1 } + + var index: Int = absScale and 15 + if (index != 0) { + val multexp: Int = s_Pow10ExponentTable[index - 1].toInt() + // the exponents are shared between the inverted and regular table + exp += if(scale < 0) (-multexp + 1) else multexp + + val multVal: ULong = s_Pow10MantissaTable[index + (if (scale < 0) 15 else 0) - 1] + + resultValue = mul64Lossy(resultValue, multVal) + // normalize + if ((resultValue and 0x80000000_00000000UL) == 0UL) { + resultValue = resultValue shl 1 + exp-- + } + } + + index = absScale ushr 4 + if (index != 0) { + val multexp: Int = s_Pow10By16ExponentTable[index - 1].toInt() + // the exponents are shared between the inverted and regular table + exp += if (scale < 0) (-multexp + 1) else multexp + + val multVal: ULong = s_Pow10By16MantissaTable[index + (if (scale < 0) 21 else 0) - 1] + resultValue = mul64Lossy(resultValue, multVal) + // normalize + if ((resultValue and 0x80000000_00000000UL) == 0UL) { + resultValue = resultValue shl 1 + exp-- + } + } + + // round & scale down + if ((resultValue and (1UL shl 10)) != 0UL) { + var tmp: ULong = resultValue + ((1UL shl 10) - 1UL) + ((resultValue shr 11) and 1UL) + if (tmp < resultValue) { + // overflow + tmp = (tmp shr 1) or 0x8000000000000000UL + exp += 1 + } + resultValue = tmp + } + + // return the exponent to a biased state + exp += 0x3FE + + // handle overflow, underflow, "Epsilon - 1/2 Epsilon", denormalized, and the normal case + if (exp <= 0) { + if (exp == -52 && (resultValue >= 0x8000000000000058UL)) { + // round X where {Epsilon > X >= 2.470328229206232730000000E-324} up to Epsilon (instead of down to zero) + resultValue = 0x0000000000000001UL + } else if (exp <= -52) { + // underflow + resultValue = 0UL + } else { + // denormalized + resultValue = resultValue shr (-exp + 11 + 1) + } + } + else if (exp >= 0x7FF) { + // overflow + resultValue = 0x7FF0000000000000UL + } else { + // normal postive exponent case + resultValue = (exp.toULong() shl 52) + ((resultValue shr 11) and 0x000FFFFFFFFFFFFFUL) + } + + if (signed) { + resultValue = resultValue or 0x8000000000000000UL + } + + return Double.fromBits(resultValue.toLong()) +} \ No newline at end of file diff --git a/libraries/stdlib/wasm/src/kotlin/text/FloatingPointParser.kt b/libraries/stdlib/wasm/src/kotlin/text/FloatingPointParser.kt new file mode 100644 index 00000000000..fd7d9f2f811 --- /dev/null +++ b/libraries/stdlib/wasm/src/kotlin/text/FloatingPointParser.kt @@ -0,0 +1,110 @@ +/* + * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package kotlin.text + +internal fun parseDouble(string: String): Double { + if (string.isEmpty()) numberFormatError(string) + + var index = 0 + + fun parseUnsignificants() { + while (index <= string.lastIndex) { + if (string[index] != ' ' && string[index] != '\n' && string[index] != '\t') break + index++ + } + } + + fun parseNumber(sb: StringBuilder) { + while (index <= string.lastIndex) { + val ch = string[index] + if (ch !in '0'..'9') break + sb.append(string[index]) + index++ + } + } + + fun parseSign(): Boolean { + if (index > string.lastIndex) return false + val ch = string[index] + if (ch == '+' || ch == '-') { + index++ + return ch == '-' + } + return false + } + + fun parseE(): Boolean { + if (index > string.lastIndex) return false + val ch = string[index] + if (ch == 'e' || ch == 'E') { + index++ + return true + } + return false + } + + fun parseDot(): Boolean { + if (index > string.lastIndex) return false + val ch = string[index] + if (ch == '.') { + index++ + return true + } + return false + } + + fun tryParseWord(word: String): Boolean { + if (string.length - index < word.length) return false + val originalIndex = index + var wordIndex = 0 + while (wordIndex < word.length) { + if (string[index] != word[wordIndex]) { + index = originalIndex + return false + } + wordIndex++ + index++ + } + return true + } + + parseUnsignificants() + val isNegative = parseSign() + + if (tryParseWord("NaN")) { + parseUnsignificants() + if (index != string.length) numberFormatError(string) + return if (!isNegative) Double.NaN else -Double.NaN + } + if (tryParseWord("Infinity")) { + parseUnsignificants() + if (index != string.length) numberFormatError(string) + return if (!isNegative) Double.POSITIVE_INFINITY else -Double.NEGATIVE_INFINITY + } + + val numberBuilder = StringBuilder() + parseNumber(numberBuilder) + var scale = numberBuilder.length + if (parseDot()) parseNumber(numberBuilder) + if (numberBuilder.isEmpty()) numberFormatError(string) + + if (parseE()) { + val exponentBuilder = StringBuilder() + if (parseSign()) { + exponentBuilder.append('-') + } + parseNumber(exponentBuilder) + if (numberBuilder.isEmpty()) numberFormatError(string) + + scale += exponentBuilder.toString().toInt() + } + + parseUnsignificants() + if (index != string.length) numberFormatError(string) + + val number = numberBuilder.toString() + return numberToDouble(isNegative, scale, number) +} \ No newline at end of file diff --git a/license/README.md b/license/README.md index 6e47282e37d..fd69c77c1fa 100644 --- a/license/README.md +++ b/license/README.md @@ -121,6 +121,10 @@ the Kotlin IntelliJ IDEA plugin: - License: Apache 2 ([license/third_party/aosp_license.txt][aosp]) - Origin: Derived from JetPack Compose compiler plugin code, Copyright 2019 The Android Open Source Project + - Path: libraries/stdlib/wasm/src/kotlin/text/FloatingPointConverter.kt + - License: MIT ([license/third_party/asmble_license.txt][asmble]) + - Origin: Copyright (C) 2018 Chad Retz + ## Kotlin Test Data The following source code is used for testing the Kotlin compiler and/or plugin and is not incorporated into