[WASM] Add string to double conversion to Stdlib

This version is not completely correct - there is some IEEE rouding issue that, apparently, require to implement different algorithm with BigIntegers implementation.
This commit is contained in:
Igor Yakovlev
2021-11-18 16:25:27 +01:00
committed by TeamCityServer
parent 9408499da9
commit 73ca478fbd
5 changed files with 401 additions and 2 deletions
+1 -1
View File
@@ -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.
+1 -1
View File
@@ -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.
@@ -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())
}
@@ -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)
}
+4
View File
@@ -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