[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:
committed by
TeamCityServer
parent
9408499da9
commit
73ca478fbd
@@ -907,7 +907,7 @@ public actual fun Float.roundToLong(): Long = TODO("Wasm stdlib: Math")
|
|||||||
* @see absoluteValue extension property for [Int]
|
* @see absoluteValue extension property for [Int]
|
||||||
*/
|
*/
|
||||||
@SinceKotlin("1.2")
|
@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.
|
* Returns the smaller of two values.
|
||||||
|
|||||||
@@ -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.
|
* Parses the string as a [Double] number and returns the result.
|
||||||
* @throws NumberFormatException if the string is not a valid representation of a number.
|
* @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.
|
* 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)
|
||||||
|
}
|
||||||
@@ -121,6 +121,10 @@ the Kotlin IntelliJ IDEA plugin:
|
|||||||
- License: Apache 2 ([license/third_party/aosp_license.txt][aosp])
|
- 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
|
- 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
|
## Kotlin Test Data
|
||||||
|
|
||||||
The following source code is used for testing the Kotlin compiler and/or plugin and is not incorporated into
|
The following source code is used for testing the Kotlin compiler and/or plugin and is not incorporated into
|
||||||
|
|||||||
Reference in New Issue
Block a user