WASM: Impelment float to string conversion operations

This commit is contained in:
Igor Laevsky
2021-07-29 19:55:10 +03:00
committed by TeamCityServer
parent f34a079699
commit 0eba74a9d2
5 changed files with 351 additions and 24 deletions
@@ -1758,7 +1758,7 @@ public class Float private constructor(public val value: Float) : Number(), Comp
other is Float && this.equals(other)
public override fun toString(): String =
TODO("Wasm: string coercion")
dtoa(this.toDouble())
public override inline fun hashCode(): Int =
bits()
@@ -2071,7 +2071,7 @@ public class Double private constructor(public val value: Double) : Number(), Co
other is Double && this.bits() == other.bits()
public override fun toString(): String =
TODO("Wasm: string coercion")
dtoa(this)
public override inline fun hashCode(): Int = bits().hashCode()
@@ -6,10 +6,10 @@ package kotlin.wasm.internal
private enum class CharCodes(val code: Int) {
// PERCENT(0x25),
// PLUS(0x2B),
MINUS(0x2D),
// DOT(0x2E),
_0(0x30),
PLUS(0x2B),
MINUS(0x2D),
DOT(0x2E),
_0(0x30),
// _1(0x31),
// _2(0x32),
// _3(0x33),
@@ -29,7 +29,7 @@ private enum class CharCodes(val code: Int) {
// Z(0x5A),
// a(0x61),
// b(0x62),
// e(0x65),
e(0x65),
// n(0x6E),
// o(0x6F),
// u(0x75),
@@ -101,6 +101,7 @@ private fun utoaDecSimple64(buffer: CharArray, numInput: Long, offsetInput: Int)
private fun Boolean.toInt() = if (this) 1 else 0
private fun Boolean.toLong() = if (this) 1L else 0L
private fun decimalCount32(value: Int): Int {
if (value < 100000) {
@@ -163,3 +164,311 @@ private fun decimalCount64High(value: Long): Int {
}
}
}
private const val MAX_DOUBLE_LENGTH = 28
internal fun dtoa(value: Double): String {
if (value == 0.0) return "0.0"
if (!value.isFinite()) {
if (value.isNaN()) return "NaN"
return if (value < 0) "-Infinity" else "Infinity"
}
val buf = CharArray(MAX_DOUBLE_LENGTH)
val size = dtoaCore(buf, value)
val ret = CharArray(size)
buf.copyInto(ret, 0, 0, size)
return kotlin.String(ret)
}
private fun dtoaCore(buffer: CharArray, valueInp: Double): Int {
var value = valueInp
val sign = (value < 0).toInt()
if (sign == 1) {
value = -value
buffer[0] = CharCodes.MINUS.code.toChar()
}
var len = grisu2(value, buffer, sign)
len = prettify(BufferWithOffset(buffer, sign), len - sign, _K)
return len + sign
}
// These are needed for grisu2 implementation
// TODO: What we are going to do with multiple threads?
private var _K: Int = 0
private var _exp: Int = 0
private var _frc_minus: Long = 0
private var _frc_plus: Long = 0
private var _frc_pow: Long = 0
private var _exp_pow: Int = 0
private val EXP_POWERS = shortArrayOf(
-1220, -1193, -1166, -1140, -1113, -1087, -1060, -1034, -1007, -980,
-954, -927, -901, -874, -847, -821, -794, -768, -741, -715,
-688, -661, -635, -608, -582, -555, -529, -502, -475, -449,
-422, -396, -369, -343, -316, -289, -263, -236, -210, -183,
-157, -130, -103, -77, -50, -24, 3, 30, 56, 83,
109, 136, 162, 189, 216, 242, 269, 295, 322, 348,
375, 402, 428, 455, 481, 508, 534, 561, 588, 614,
641, 667, 694, 720, 747, 774, 800, 827, 853, 880,
907, 933, 960, 986, 1013, 1039, 1066
)
// 1e-348, 1e-340, ..., 1e340
private val FRC_POWERS = longArrayOf(
0xFA8FD5A0081C0288UL.toLong(), 0xBAAEE17FA23EBF76UL.toLong(), 0x8B16FB203055AC76UL.toLong(), 0xCF42894A5DCE35EAUL.toLong(),
0x9A6BB0AA55653B2DUL.toLong(), 0xE61ACF033D1A45DFUL.toLong(), 0xAB70FE17C79AC6CAUL.toLong(), 0xFF77B1FCBEBCDC4FUL.toLong(),
0xBE5691EF416BD60CUL.toLong(), 0x8DD01FAD907FFC3CUL.toLong(), 0xD3515C2831559A83UL.toLong(), 0x9D71AC8FADA6C9B5UL.toLong(),
0xEA9C227723EE8BCBUL.toLong(), 0xAECC49914078536DUL.toLong(), 0x823C12795DB6CE57UL.toLong(), 0xC21094364DFB5637UL.toLong(),
0x9096EA6F3848984FUL.toLong(), 0xD77485CB25823AC7UL.toLong(), 0xA086CFCD97BF97F4UL.toLong(), 0xEF340A98172AACE5UL.toLong(),
0xB23867FB2A35B28EUL.toLong(), 0x84C8D4DFD2C63F3BUL.toLong(), 0xC5DD44271AD3CDBAUL.toLong(), 0x936B9FCEBB25C996UL.toLong(),
0xDBAC6C247D62A584UL.toLong(), 0xA3AB66580D5FDAF6UL.toLong(), 0xF3E2F893DEC3F126UL.toLong(), 0xB5B5ADA8AAFF80B8UL.toLong(),
0x87625F056C7C4A8BUL.toLong(), 0xC9BCFF6034C13053UL.toLong(), 0x964E858C91BA2655UL.toLong(), 0xDFF9772470297EBDUL.toLong(),
0xA6DFBD9FB8E5B88FUL.toLong(), 0xF8A95FCF88747D94UL.toLong(), 0xB94470938FA89BCFUL.toLong(), 0x8A08F0F8BF0F156BUL.toLong(),
0xCDB02555653131B6UL.toLong(), 0x993FE2C6D07B7FACUL.toLong(), 0xE45C10C42A2B3B06UL.toLong(), 0xAA242499697392D3UL.toLong(),
0xFD87B5F28300CA0EUL.toLong(), 0xBCE5086492111AEBUL.toLong(), 0x8CBCCC096F5088CCUL.toLong(), 0xD1B71758E219652CUL.toLong(),
0x9C40000000000000UL.toLong(), 0xE8D4A51000000000UL.toLong(), 0xAD78EBC5AC620000UL.toLong(), 0x813F3978F8940984UL.toLong(),
0xC097CE7BC90715B3UL.toLong(), 0x8F7E32CE7BEA5C70UL.toLong(), 0xD5D238A4ABE98068UL.toLong(), 0x9F4F2726179A2245UL.toLong(),
0xED63A231D4C4FB27UL.toLong(), 0xB0DE65388CC8ADA8UL.toLong(), 0x83C7088E1AAB65DBUL.toLong(), 0xC45D1DF942711D9AUL.toLong(),
0x924D692CA61BE758UL.toLong(), 0xDA01EE641A708DEAUL.toLong(), 0xA26DA3999AEF774AUL.toLong(), 0xF209787BB47D6B85UL.toLong(),
0xB454E4A179DD1877UL.toLong(), 0x865B86925B9BC5C2UL.toLong(), 0xC83553C5C8965D3DUL.toLong(), 0x952AB45CFA97A0B3UL.toLong(),
0xDE469FBD99A05FE3UL.toLong(), 0xA59BC234DB398C25UL.toLong(), 0xF6C69A72A3989F5CUL.toLong(), 0xB7DCBF5354E9BECEUL.toLong(),
0x88FCF317F22241E2UL.toLong(), 0xCC20CE9BD35C78A5UL.toLong(), 0x98165AF37B2153DFUL.toLong(), 0xE2A0B5DC971F303AUL.toLong(),
0xA8D9D1535CE3B396UL.toLong(), 0xFB9B7CD9A4A7443CUL.toLong(), 0xBB764C4CA7A44410UL.toLong(), 0x8BAB8EEFB6409C1AUL.toLong(),
0xD01FEF10A657842CUL.toLong(), 0x9B10A4E5E9913129UL.toLong(), 0xE7109BFBA19C0C9DUL.toLong(), 0xAC2820D9623BF429UL.toLong(),
0x80444B5E7AA7CF85UL.toLong(), 0xBF21E44003ACDD2DUL.toLong(), 0x8E679C2F5E44FF8FUL.toLong(), 0xD433179D9C8CB841UL.toLong(),
0x9E19DB92B4E31BA9UL.toLong(), 0xEB96BF6EBADF77D9UL.toLong(), 0xAF87023B9BF0EE6BUL.toLong()
)
private fun grisu2(value: Double, buffer: CharArray, sign: Int): Int {
// frexp routine
val uv = value.toBits()
var exp = ((uv and 0x7FF0000000000000) ushr 52).toInt()
val sid = uv and 0x000FFFFFFFFFFFFF
var frc = ((exp != 0).toLong() shl 52) + sid
exp = (if (exp != 0) exp else 1) - (0x3FF + 52)
normalizedBoundaries(frc, exp)
getCachedPower(_exp)
// normalize
val off = frc.countLeadingZeroBits()
frc = frc shl off
exp -= off
var frc_pow = _frc_pow
var exp_pow = _exp_pow
var w_frc = umul64f(frc, frc_pow)
var w_exp = umul64e(exp, exp_pow)
var wp_frc = umul64f(_frc_plus, frc_pow) - 1
var wp_exp = umul64e(_exp, exp_pow)
var wm_frc = umul64f(_frc_minus, frc_pow) + 1
var delta = wp_frc - wm_frc
return genDigits(buffer, w_frc, w_exp, wp_frc, wp_exp, delta, sign);
}
private fun umul64f(u: Long, v: Long): Long {
val u0 = u and 0xFFFFFFFF
val v0 = v and 0xFFFFFFFF
val u1 = u ushr 32
val v1 = v ushr 32
val l = u0 * v0
var t = u1 * v0 + (l ushr 32)
var w = u0 * v1 + (t and 0xFFFFFFFF)
w += 0x7FFFFFFF // rounding
t = t ushr 32
w = w ushr 32
return u1 * v1 + t + w
}
private fun umul64e(e1: Int, e2: Int): Int {
return e1 + e2 + 64 // where 64 is significand size
}
private fun normalizedBoundaries(f: Long, e: Int) {
var frc = (f shl 1) + 1
var exp = e - 1
val off = frc.countLeadingZeroBits()
frc = frc shl off
exp -= off
val m = 1 + (f == 0x0010000000000000).toInt()
_frc_plus = frc
_frc_minus = ((f shl m) - 1) shl e - m - exp
_exp = exp
}
private fun getCachedPower(minExp: Int) {
val c = Double.fromBits(0x3FD34413509F79FE) // 1 / lg(10) = 0.30102999566398114
val dk = (-61 - minExp) * c + 347 // dk must be positive, so can do ceiling in positive
var k = dk.toInt()
k += (k.toDouble() != dk).toInt() // conversion with ceil
val index = (k shr 3) + 1
_K = 348 - (index shl 3) // decimal exponent no need lookup table
_frc_pow = FRC_POWERS[index]
_exp_pow = EXP_POWERS[index].toInt()
}
private fun genDigits(buffer: CharArray, w_frc: Long, w_exp: Int, mp_frc: Long, mp_exp: Int, deltaInp: Long, sign: Int): Int {
var delta = deltaInp
val one_exp = -mp_exp
val one_frc = 1L shl one_exp
val mask = one_frc - 1
var wp_w_frc = mp_frc - w_frc
var p1 = (mp_frc ushr one_exp).toInt()
var p2 = mp_frc and mask
var kappa = decimalCount32(p1)
var len = sign
while (kappa > 0) {
var d: Int
var pow10: Long
when (kappa) {
0 -> { d = p1 / 1000000000; p1 %= 1000000000; pow10 = 1000000000; }
9 -> { d = p1 / 100000000; p1 %= 100000000; pow10 = 100000000; }
8 -> { d = p1 / 10000000; p1 %= 10000000; pow10 = 10000000; }
7 -> { d = p1 / 1000000; p1 %= 1000000; pow10 = 1000000; }
6 -> { d = p1 / 100000; p1 %= 100000; pow10 = 100000; }
5 -> { d = p1 / 10000; p1 %= 10000; pow10 = 10000; }
4 -> { d = p1 / 1000; p1 %= 1000; pow10 = 1000; }
3 -> { d = p1 / 100; p1 %= 100; pow10 = 100; }
2 -> { d = p1 / 10; p1 %= 10; pow10 = 10; }
1 -> { d = p1; p1 = 0; pow10 = 1; }
else -> { d = 0; pow10 = 1; }
}
if (d or len != 0)
buffer[len++] = digitToChar(d)
--kappa
val tmp = (p1.toLong() shl one_exp) + p2
if (tmp <= delta) {
_K += kappa
grisuRound(buffer, len, delta, tmp, pow10 shl one_exp, wp_w_frc)
return len;
}
}
var unit = 1L
while (true) {
p2 *= 10
delta *= 10
unit *= 10
val d = p2 ushr one_exp
if (d or len.toLong() != 0L)
buffer[len++] = digitToChar(d.toInt())
p2 = p2 and mask
--kappa
if (p2 < delta) {
_K += kappa
grisuRound(buffer, len, delta, p2, one_frc, wp_w_frc * unit)
return len
}
}
}
private fun grisuRound(buffer: CharArray, len: Int, delta: Long, restInp: Long, ten_kappa: Long, wp_w: Long) {
var rest = restInp
val lastp = len - 1
var digit = buffer[lastp]
while (
rest < wp_w &&
delta - rest >= ten_kappa && (
rest + ten_kappa < wp_w ||
wp_w - rest > rest + ten_kappa - wp_w
)
) {
--digit
rest += ten_kappa;
}
buffer[lastp] = digit
}
private class BufferWithOffset(val buf: CharArray, val off: Int) {
operator fun set(addr: Int, value: Char) {
buf[off + addr] = value
}
fun memoryCopy(destAddr: Int, srcAddr: Int, len: Int) {
val startIdx = off + srcAddr
buf.copyInto(buf, off + destAddr, startIdx, startIdx + len)
}
fun offsetABitMore(anotherOff: Int) = BufferWithOffset(buf, off + anotherOff)
}
private fun prettify(buffer: BufferWithOffset, lengthInp: Int, k: Int): Int {
var length = lengthInp
if (k == 0) {
buffer[length] = CharCodes.DOT.code.toChar()
buffer[length + 1] = CharCodes._0.code.toChar()
return length + 2
}
var kk = length + k
if (length <= kk && kk <= 21) {
// 1234e7 -> 12340000000
for (i in length until kk) {
buffer[i] = CharCodes._0.code.toChar()
}
buffer[kk] = CharCodes.DOT.code.toChar()
buffer[kk + 1] = CharCodes._0.code.toChar()
return kk + 2
} else if (kk > 0 && kk <= 21) {
// 1234e-2 -> 12.34
buffer.memoryCopy(kk + 1, kk, -k)
buffer[kk] = CharCodes.DOT.code.toChar()
return length + 1
} else if (-6 < kk && kk <= 0) {
// 1234e-6 -> 0.001234
val offset = 2 - kk
buffer.memoryCopy(offset, 0, length)
buffer[0] = CharCodes._0.code.toChar()
buffer[1] = CharCodes.DOT.code.toChar()
for (i in 2 until offset) {
buffer[i] = CharCodes._0.code.toChar()
}
return length + offset
} else if (length == 1) {
// 1e30
buffer[1] = CharCodes.e.code.toChar()
length = genExponent(buffer.offsetABitMore(2), kk - 1)
return length + 2
} else {
val len = length
buffer.memoryCopy(2, 1, len - 1)
buffer[1] = CharCodes.DOT.code.toChar()
buffer[len + 1] = CharCodes.e.code.toChar()
length += genExponent(buffer.offsetABitMore(len + 2), kk - 1)
return length + 2
}
}
private fun genExponent(buffer: BufferWithOffset, kInp: Int): Int {
var k = kInp
val sign = k < 0
if (sign) k = -k
val kStr = k.toString()
for (i in kStr.indices)
buffer[i + 1] = kStr[i]
buffer[0] = if (sign) CharCodes.MINUS.code.toChar() else CharCodes.PLUS.code.toChar()
return kStr.length + 1
}
@@ -289,11 +289,17 @@ public external fun wasm_f64_convert_i64_s(a: Long): Double
@WasmOp(WasmOp.F64_PROMOTE_F32)
public external fun wasm_f64_promote_f32(a: Float): Double
@WasmOp(WasmOp.F32_REINTERPRET_I32)
public external fun wasm_f32_reinterpret_i32(a: Int): Float
@WasmOp(WasmOp.F64_REINTERPRET_I64)
public external fun wasm_f64_reinterpret_i64(a: Long): Double
@WasmOp(WasmOp.I32_REINTERPRET_F32)
public external fun wasm_i32_reinterpret_f32(a: Float): Int
@WasmOp(WasmOp.F32_REINTERPRET_I32)
public external fun wasm_f32_reinterpret_i32(a: Int): Float
@WasmOp(WasmOp.I64_REINTERPRET_F64)
public external fun wasm_i64_reinterpret_f64(a: Double): Long
@WasmOp(WasmOp.I32_TRUNC_SAT_F32_S)
public external fun wasm_i32_trunc_sat_f32_s(a: Float): Int
@@ -317,4 +323,10 @@ public external fun wasm_i32_load16_u(x: Int): Int
public external fun wasm_i32_store(addr: Int, i: Int): Unit
@WasmOp(WasmOp.I32_STORE16)
public external fun wasm_i32_store16(addr: Int, c: Char): Unit
public external fun wasm_i32_store16(addr: Int, c: Char): Unit
@WasmOp(WasmOp.I32_CLZ)
public external fun wasm_i32_clz(a: Int): Int
@WasmOp(WasmOp.I64_CLZ)
public external fun wasm_i64_clz(a: Long): Long
+16 -12
View File
@@ -7,6 +7,10 @@ package kotlin
import kotlin.annotation.AnnotationTarget.FIELD
import kotlin.annotation.AnnotationTarget.PROPERTY
import kotlin.wasm.internal.wasm_f32_reinterpret_i32
import kotlin.wasm.internal.wasm_f64_reinterpret_i64
import kotlin.wasm.internal.wasm_i32_reinterpret_f32
import kotlin.wasm.internal.wasm_i64_reinterpret_f64
/**
@@ -27,19 +31,19 @@ public actual fun interface Comparator<T> {
// From numbers.kt
actual fun Double.isNaN(): Boolean = TODO("Wasm stdlib: Kotlin")
actual fun Float.isNaN(): Boolean = TODO("Wasm stdlib: Kotlin")
actual fun Double.isInfinite(): Boolean = TODO("Wasm stdlib: Kotlin")
actual fun Float.isInfinite(): Boolean = TODO("Wasm stdlib: Kotlin")
actual fun Double.isFinite(): Boolean = TODO("Wasm stdlib: Kotlin")
actual fun Float.isFinite(): Boolean = TODO("Wasm stdlib: Kotlin")
actual fun Double.isNaN(): Boolean = this != this
actual fun Float.isNaN(): Boolean = this != this
actual fun Double.isInfinite(): Boolean = (this == Double.POSITIVE_INFINITY) || (this == Double.NEGATIVE_INFINITY)
actual fun Float.isInfinite(): Boolean = (this == Float.POSITIVE_INFINITY) || (this == Float.NEGATIVE_INFINITY)
actual fun Double.isFinite(): Boolean = !isInfinite() && !isNaN()
actual fun Float.isFinite(): Boolean = !isInfinite() && !isNaN()
/**
* Returns a bit representation of the specified floating-point value as [Long]
* according to the IEEE 754 floating-point "double format" bit layout.
*/
@SinceKotlin("1.2")
public actual fun Double.toBits(): Long = TODO("Wasm stdlib: Kotlin")
public actual fun Double.toBits(): Long = if (isNaN()) Double.NaN.toRawBits() else toRawBits()
/**
* Returns a bit representation of the specified floating-point value as [Long]
@@ -47,20 +51,20 @@ public actual fun Double.toBits(): Long = TODO("Wasm stdlib: Kotlin")
* preserving `NaN` values exact layout.
*/
@SinceKotlin("1.2")
public actual fun Double.toRawBits(): Long = TODO("Wasm stdlib: Kotlin")
public actual fun Double.toRawBits(): Long = wasm_i64_reinterpret_f64(this)
/**
* Returns the [Double] value corresponding to a given bit representation.
*/
@SinceKotlin("1.2")
public actual fun Double.Companion.fromBits(bits: Long): Double = TODO("Wasm stdlib: Kotlin")
public actual fun Double.Companion.fromBits(bits: Long): Double = wasm_f64_reinterpret_i64(bits)
/**
* Returns a bit representation of the specified floating-point value as [Int]
* according to the IEEE 754 floating-point "single format" bit layout.
*/
@SinceKotlin("1.2")
public actual fun Float.toBits(): Int = TODO("Wasm stdlib: Kotlin")
public actual fun Float.toBits(): Int = if (isNaN()) Float.NaN.toRawBits() else toRawBits()
/**
* Returns a bit representation of the specified floating-point value as [Int]
@@ -68,13 +72,13 @@ public actual fun Float.toBits(): Int = TODO("Wasm stdlib: Kotlin")
* preserving `NaN` values exact layout.
*/
@SinceKotlin("1.2")
public actual fun Float.toRawBits(): Int = TODO("Wasm stdlib: Kotlin")
public actual fun Float.toRawBits(): Int = wasm_i32_reinterpret_f32(this)
/**
* Returns the [Float] value corresponding to a given bit representation.
*/
@SinceKotlin("1.2")
public actual fun Float.Companion.fromBits(bits: Int): Float = TODO("Wasm stdlib: Kotlin")
public actual fun Float.Companion.fromBits(bits: Int): Float = wasm_f32_reinterpret_i32(bits)
// From concurrent.kt
@@ -5,6 +5,8 @@
package kotlin
import kotlin.wasm.internal.*
/**
* Counts the number of set bits in the binary representation of this [Int] number.
*/
@@ -17,7 +19,7 @@ public actual fun Int.countOneBits(): Int = TODO("Wasm stdlib: Numbers")
*/
@SinceKotlin("1.3")
@ExperimentalStdlibApi
public actual fun Int.countLeadingZeroBits(): Int = TODO("Wasm stdlib: Numbers")
public actual fun Int.countLeadingZeroBits(): Int = wasm_i32_clz(this)
/**
* Counts the number of consecutive least significant bits that are zero in the binary representation of this [Int] number.
@@ -84,7 +86,7 @@ public actual fun Long.countOneBits(): Int = TODO("Wasm stdlib: Numbers")
*/
@SinceKotlin("1.3")
@ExperimentalStdlibApi
public actual fun Long.countLeadingZeroBits(): Int = TODO("Wasm stdlib: Numbers")
public actual fun Long.countLeadingZeroBits(): Int = wasm_i64_clz(this).toInt()
/**
* Counts the number of consecutive least significant bits that are zero in the binary representation of this [Long] number.