Provide string-to-number conversions in JS and common stdlib.
Relates to #KT-8286 #KT-4497 Fixed
This commit is contained in:
@@ -121,3 +121,83 @@ internal inline header fun String.nativeLastIndexOf(ch: Char, fromIndex: Int): I
|
||||
|
||||
header fun CharSequence.isBlank(): Boolean
|
||||
header fun CharSequence.regionMatches(thisOffset: Int, other: CharSequence, otherOffset: Int, length: Int, ignoreCase: Boolean): Boolean
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Parses the string as a signed [Byte] number and returns the result.
|
||||
* @throws NumberFormatException if the string is not a valid representation of a number.
|
||||
*/
|
||||
header fun String.toByte(): Byte
|
||||
|
||||
/**
|
||||
* Parses the string as a signed [Byte] number and returns the result.
|
||||
* @throws NumberFormatException if the string is not a valid representation of a number.
|
||||
*/
|
||||
header fun String.toByte(radix: Int): Byte
|
||||
|
||||
|
||||
/**
|
||||
* Parses the string as a [Short] number and returns the result.
|
||||
* @throws NumberFormatException if the string is not a valid representation of a number.
|
||||
*/
|
||||
header fun String.toShort(): Short
|
||||
|
||||
/**
|
||||
* Parses the string as a [Short] number and returns the result.
|
||||
* @throws NumberFormatException if the string is not a valid representation of a number.
|
||||
*/
|
||||
header fun String.toShort(radix: Int): Short
|
||||
|
||||
/**
|
||||
* Parses the string as an [Int] number and returns the result.
|
||||
* @throws NumberFormatException if the string is not a valid representation of a number.
|
||||
*/
|
||||
header fun String.toInt(): Int
|
||||
|
||||
/**
|
||||
* Parses the string as an [Int] number and returns the result.
|
||||
* @throws NumberFormatException if the string is not a valid representation of a number.
|
||||
*/
|
||||
header fun String.toInt(radix: Int): Int
|
||||
|
||||
/**
|
||||
* Parses the string as a [Long] number and returns the result.
|
||||
* @throws NumberFormatException if the string is not a valid representation of a number.
|
||||
*/
|
||||
header fun String.toLong(): Long
|
||||
|
||||
/**
|
||||
* Parses the string as a [Long] number and returns the result.
|
||||
* @throws NumberFormatException if the string is not a valid representation of a number.
|
||||
*/
|
||||
header fun String.toLong(radix: Int): Long
|
||||
|
||||
/**
|
||||
* Parses the string as a [Double] number and returns the result.
|
||||
* @throws NumberFormatException if the string is not a valid representation of a number.
|
||||
*/
|
||||
header fun String.toDouble(): Double
|
||||
|
||||
/**
|
||||
* Parses the string as a [Float] number and returns the result.
|
||||
* @throws NumberFormatException if the string is not a valid representation of a number.
|
||||
*/
|
||||
header fun String.toFloat(): Float
|
||||
|
||||
/**
|
||||
* Parses the string as a [Double] number and returns the result
|
||||
* or `null` if the string is not a valid representation of a number.
|
||||
*/
|
||||
header fun String.toDoubleOrNull(): Double?
|
||||
|
||||
/**
|
||||
* Parses the string as a [Float] number and returns the result
|
||||
* or `null` if the string is not a valid representation of a number.
|
||||
*/
|
||||
header fun String.toFloatOrNull(): Float?
|
||||
|
||||
|
||||
@PublishedApi
|
||||
internal header fun checkRadix(radix: Int): Int
|
||||
internal header fun digitOf(char: Char, radix: Int): Int
|
||||
@@ -1,6 +1,5 @@
|
||||
@file:kotlin.jvm.JvmMultifileClass
|
||||
@file:kotlin.jvm.JvmName("StringsKt")
|
||||
@file:kotlin.jvm.JvmVersion
|
||||
@file:Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
|
||||
|
||||
package kotlin.text
|
||||
@@ -9,6 +8,7 @@ package kotlin.text
|
||||
* Returns a string representation of this [Byte] value in the specified [radix].
|
||||
*/
|
||||
@SinceKotlin("1.1")
|
||||
@kotlin.jvm.JvmVersion
|
||||
@kotlin.internal.InlineOnly
|
||||
public inline fun Byte.toString(radix: Int): String = this.toInt().toString(checkRadix(radix))
|
||||
|
||||
@@ -16,6 +16,7 @@ public inline fun Byte.toString(radix: Int): String = this.toInt().toString(chec
|
||||
* Returns a string representation of this [Short] value in the specified [radix].
|
||||
*/
|
||||
@SinceKotlin("1.1")
|
||||
@kotlin.jvm.JvmVersion
|
||||
@kotlin.internal.InlineOnly
|
||||
public inline fun Short.toString(radix: Int): String = this.toInt().toString(checkRadix(radix))
|
||||
|
||||
@@ -23,6 +24,7 @@ public inline fun Short.toString(radix: Int): String = this.toInt().toString(che
|
||||
* Returns a string representation of this [Int] value in the specified [radix].
|
||||
*/
|
||||
@SinceKotlin("1.1")
|
||||
@kotlin.jvm.JvmVersion
|
||||
@kotlin.internal.InlineOnly
|
||||
public inline fun Int.toString(radix: Int): String = java.lang.Integer.toString(this, checkRadix(radix))
|
||||
|
||||
@@ -30,12 +32,14 @@ public inline fun Int.toString(radix: Int): String = java.lang.Integer.toString(
|
||||
* Returns a string representation of this [Long] value in the specified [radix].
|
||||
*/
|
||||
@SinceKotlin("1.1")
|
||||
@kotlin.jvm.JvmVersion
|
||||
@kotlin.internal.InlineOnly
|
||||
public inline fun Long.toString(radix: Int): String = java.lang.Long.toString(this, checkRadix(radix))
|
||||
|
||||
/**
|
||||
* Returns `true` if the contents of this string is equal to the word "true", ignoring case, and `false` otherwise.
|
||||
*/
|
||||
@kotlin.jvm.JvmVersion
|
||||
@kotlin.internal.InlineOnly
|
||||
public inline fun String.toBoolean(): Boolean = java.lang.Boolean.parseBoolean(this)
|
||||
|
||||
@@ -43,6 +47,7 @@ public inline fun String.toBoolean(): Boolean = java.lang.Boolean.parseBoolean(t
|
||||
* Parses the string as a signed [Byte] number and returns the result.
|
||||
* @throws NumberFormatException if the string is not a valid representation of a number.
|
||||
*/
|
||||
@kotlin.jvm.JvmVersion
|
||||
@kotlin.internal.InlineOnly
|
||||
public inline fun String.toByte(): Byte = java.lang.Byte.parseByte(this)
|
||||
|
||||
@@ -51,6 +56,7 @@ public inline fun String.toByte(): Byte = java.lang.Byte.parseByte(this)
|
||||
* @throws NumberFormatException if the string is not a valid representation of a number.
|
||||
*/
|
||||
@SinceKotlin("1.1")
|
||||
@kotlin.jvm.JvmVersion
|
||||
@kotlin.internal.InlineOnly
|
||||
public inline fun String.toByte(radix: Int): Byte = java.lang.Byte.parseByte(this, checkRadix(radix))
|
||||
|
||||
@@ -59,6 +65,7 @@ public inline fun String.toByte(radix: Int): Byte = java.lang.Byte.parseByte(thi
|
||||
* Parses the string as a [Short] number and returns the result.
|
||||
* @throws NumberFormatException if the string is not a valid representation of a number.
|
||||
*/
|
||||
@kotlin.jvm.JvmVersion
|
||||
@kotlin.internal.InlineOnly
|
||||
public inline fun String.toShort(): Short = java.lang.Short.parseShort(this)
|
||||
|
||||
@@ -67,6 +74,7 @@ public inline fun String.toShort(): Short = java.lang.Short.parseShort(this)
|
||||
* @throws NumberFormatException if the string is not a valid representation of a number.
|
||||
*/
|
||||
@SinceKotlin("1.1")
|
||||
@kotlin.jvm.JvmVersion
|
||||
@kotlin.internal.InlineOnly
|
||||
public inline fun String.toShort(radix: Int): Short = java.lang.Short.parseShort(this, checkRadix(radix))
|
||||
|
||||
@@ -74,6 +82,7 @@ public inline fun String.toShort(radix: Int): Short = java.lang.Short.parseShort
|
||||
* Parses the string as an [Int] number and returns the result.
|
||||
* @throws NumberFormatException if the string is not a valid representation of a number.
|
||||
*/
|
||||
@kotlin.jvm.JvmVersion
|
||||
@kotlin.internal.InlineOnly
|
||||
public inline fun String.toInt(): Int = java.lang.Integer.parseInt(this)
|
||||
|
||||
@@ -82,6 +91,7 @@ public inline fun String.toInt(): Int = java.lang.Integer.parseInt(this)
|
||||
* @throws NumberFormatException if the string is not a valid representation of a number.
|
||||
*/
|
||||
@SinceKotlin("1.1")
|
||||
@kotlin.jvm.JvmVersion
|
||||
@kotlin.internal.InlineOnly
|
||||
public inline fun String.toInt(radix: Int): Int = java.lang.Integer.parseInt(this, checkRadix(radix))
|
||||
|
||||
@@ -89,6 +99,7 @@ public inline fun String.toInt(radix: Int): Int = java.lang.Integer.parseInt(thi
|
||||
* Parses the string as a [Long] number and returns the result.
|
||||
* @throws NumberFormatException if the string is not a valid representation of a number.
|
||||
*/
|
||||
@kotlin.jvm.JvmVersion
|
||||
@kotlin.internal.InlineOnly
|
||||
public inline fun String.toLong(): Long = java.lang.Long.parseLong(this)
|
||||
|
||||
@@ -97,6 +108,7 @@ public inline fun String.toLong(): Long = java.lang.Long.parseLong(this)
|
||||
* @throws NumberFormatException if the string is not a valid representation of a number.
|
||||
*/
|
||||
@SinceKotlin("1.1")
|
||||
@kotlin.jvm.JvmVersion
|
||||
@kotlin.internal.InlineOnly
|
||||
public inline fun String.toLong(radix: Int): Long = java.lang.Long.parseLong(this, checkRadix(radix))
|
||||
|
||||
@@ -104,6 +116,7 @@ public inline fun String.toLong(radix: Int): Long = java.lang.Long.parseLong(thi
|
||||
* Parses the string as a [Float] number and returns the result.
|
||||
* @throws NumberFormatException if the string is not a valid representation of a number.
|
||||
*/
|
||||
@kotlin.jvm.JvmVersion
|
||||
@kotlin.internal.InlineOnly
|
||||
public inline fun String.toFloat(): Float = java.lang.Float.parseFloat(this)
|
||||
|
||||
@@ -111,6 +124,7 @@ public inline fun String.toFloat(): Float = java.lang.Float.parseFloat(this)
|
||||
* Parses the string as a [Double] number and returns the result.
|
||||
* @throws NumberFormatException if the string is not a valid representation of a number.
|
||||
*/
|
||||
@kotlin.jvm.JvmVersion
|
||||
@kotlin.internal.InlineOnly
|
||||
public inline fun String.toDouble(): Double = java.lang.Double.parseDouble(this)
|
||||
|
||||
@@ -279,6 +293,7 @@ public fun String.toLongOrNull(radix: Int): Long? {
|
||||
* or `null` if the string is not a valid representation of a number.
|
||||
*/
|
||||
@SinceKotlin("1.1")
|
||||
@kotlin.jvm.JvmVersion
|
||||
public fun String.toFloatOrNull(): Float? = screenFloatValue(this, java.lang.Float::parseFloat)
|
||||
|
||||
/**
|
||||
@@ -286,11 +301,13 @@ public fun String.toFloatOrNull(): Float? = screenFloatValue(this, java.lang.Flo
|
||||
* or `null` if the string is not a valid representation of a number.
|
||||
*/
|
||||
@SinceKotlin("1.1")
|
||||
@kotlin.jvm.JvmVersion
|
||||
public fun String.toDoubleOrNull(): Double? = screenFloatValue(this, java.lang.Double::parseDouble)
|
||||
|
||||
/**
|
||||
* Recommended floating point number validation RegEx from the javadoc of `java.lang.Double.valueOf(String)`
|
||||
*/
|
||||
@kotlin.jvm.JvmVersion
|
||||
private object ScreenFloatValueRegEx {
|
||||
@JvmField val value = run {
|
||||
val Digits = "(\\p{Digit}+)"
|
||||
@@ -310,6 +327,7 @@ private object ScreenFloatValueRegEx {
|
||||
}
|
||||
}
|
||||
|
||||
@kotlin.jvm.JvmVersion
|
||||
private inline fun <T> screenFloatValue(str: String, parse: (String) -> T): T? {
|
||||
return try {
|
||||
if (ScreenFloatValueRegEx.value.matches(str))
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
@file:kotlin.jvm.JvmVersion
|
||||
package test.text
|
||||
|
||||
import kotlin.test.*
|
||||
@@ -6,6 +5,7 @@ import org.junit.Test
|
||||
|
||||
class StringNumberConversionTest {
|
||||
|
||||
@kotlin.jvm.JvmVersion
|
||||
@Test fun toBoolean() {
|
||||
assertEquals(true, "true".toBoolean())
|
||||
assertEquals(true, "True".toBoolean())
|
||||
@@ -125,6 +125,7 @@ class StringNumberConversionTest {
|
||||
}
|
||||
}
|
||||
|
||||
@kotlin.jvm.JvmVersion
|
||||
@Test fun toFloat() {
|
||||
compareConversion(String::toFloat, String::toFloatOrNull) {
|
||||
assertProduces("77.0", 77.0f)
|
||||
@@ -135,7 +136,7 @@ class StringNumberConversionTest {
|
||||
}
|
||||
|
||||
@Test fun toDouble() {
|
||||
compareConversion(String::toDouble, String::toDoubleOrNull) {
|
||||
compareConversion(String::toDouble, String::toDoubleOrNull, ::doubleTotalOrderEquals) {
|
||||
assertProduces("-77", -77.0)
|
||||
assertProduces("77.", 77.0)
|
||||
assertProduces("77.0", 77.0)
|
||||
@@ -147,15 +148,23 @@ class StringNumberConversionTest {
|
||||
|
||||
assertProduces("-NaN", -Double.NaN)
|
||||
assertProduces("+Infinity", Double.POSITIVE_INFINITY)
|
||||
assertProduces("0x77p1", (0x77 shl 1).toDouble())
|
||||
assertProduces("0x.77P8", 0x77.toDouble())
|
||||
|
||||
assertFailsOrNull("7..7")
|
||||
assertFailsOrNull("0x77e1")
|
||||
assertFailsOrNull("007 not a number")
|
||||
}
|
||||
}
|
||||
|
||||
@kotlin.jvm.JvmVersion
|
||||
@Test fun toHexDouble() {
|
||||
compareConversion(String::toDouble, String::toDoubleOrNull, ::doubleTotalOrderEquals) {
|
||||
assertProduces("0x77p1", (0x77 shl 1).toDouble())
|
||||
assertProduces("0x.77P8", 0x77.toDouble())
|
||||
|
||||
assertFailsOrNull("0x77e1")
|
||||
}
|
||||
}
|
||||
|
||||
@kotlin.jvm.JvmVersion
|
||||
@Test fun byteToStringWithRadix() {
|
||||
assertEquals("7a", 0x7a.toByte().toString(16))
|
||||
assertEquals("-80", Byte.MIN_VALUE.toString(radix = 16))
|
||||
@@ -166,6 +175,7 @@ class StringNumberConversionTest {
|
||||
assertFailsWith<IllegalArgumentException>("Expected to fail with radix 1") { 1.toByte().toString(radix = 1) }
|
||||
}
|
||||
|
||||
@kotlin.jvm.JvmVersion
|
||||
@Test fun shortToStringWithRadix() {
|
||||
assertEquals("7FFF", 0x7FFF.toShort().toString(radix = 16).toUpperCase())
|
||||
assertEquals("-8000", (-0x8000).toShort().toString(radix = 16))
|
||||
@@ -175,6 +185,7 @@ class StringNumberConversionTest {
|
||||
assertFailsWith<IllegalArgumentException>("Expected to fail with radix 1") { 1.toShort().toString(radix = 1) }
|
||||
}
|
||||
|
||||
@kotlin.jvm.JvmVersion
|
||||
@Test fun intToStringWithRadix() {
|
||||
assertEquals("-ff", (-255).toString(radix = 16))
|
||||
assertEquals("1100110", 102.toString(radix = 2))
|
||||
@@ -184,6 +195,7 @@ class StringNumberConversionTest {
|
||||
|
||||
}
|
||||
|
||||
@kotlin.jvm.JvmVersion
|
||||
@Test fun longToStringWithRadix() {
|
||||
assertEquals("7f11223344556677", 0x7F11223344556677.toString(radix = 16))
|
||||
assertEquals("hazelnut", 1356099454469L.toString(radix = 36))
|
||||
@@ -197,8 +209,9 @@ class StringNumberConversionTest {
|
||||
|
||||
private fun <T : Any> compareConversion(convertOrFail: (String) -> T,
|
||||
convertOrNull: (String) -> T?,
|
||||
equality: (T, T?) -> Boolean = { a, b -> a == b },
|
||||
assertions: ConversionContext<T>.() -> Unit) {
|
||||
ConversionContext(convertOrFail, convertOrNull).assertions()
|
||||
ConversionContext(convertOrFail, convertOrNull, equality).assertions()
|
||||
}
|
||||
|
||||
|
||||
@@ -210,10 +223,16 @@ private fun <T : Any> compareConversionWithRadix(convertOrFail: String.(Int) ->
|
||||
|
||||
|
||||
private class ConversionContext<T: Any>(val convertOrFail: (String) -> T,
|
||||
val convertOrNull: (String) -> T?) {
|
||||
val convertOrNull: (String) -> T?,
|
||||
val equality: (T, T?) -> Boolean) {
|
||||
|
||||
private fun assertEquals(expected: T, actual: T?, input: String, operation: String) {
|
||||
assertTrue(equality(expected, actual), "Expected $operation('$input') to produce $expected but was $actual")
|
||||
}
|
||||
|
||||
fun assertProduces(input: String, output: T) {
|
||||
assertEquals(output, convertOrFail(input.removeLeadingPlusOnJava6()))
|
||||
assertEquals(output, convertOrNull(input))
|
||||
assertEquals(output, convertOrFail(input.removeLeadingPlusOnJava6()), input, "convertOrFail")
|
||||
assertEquals(output, convertOrNull(input), input, "convertOrNull")
|
||||
}
|
||||
|
||||
fun assertFailsOrNull(input: String) {
|
||||
@@ -222,22 +241,27 @@ private class ConversionContext<T: Any>(val convertOrFail: (String) -> T,
|
||||
}
|
||||
}
|
||||
|
||||
private class ConversionWithRadixContext<T: Any>(val convertOrFail: String.(Int) -> T,
|
||||
val convertOrNull: String.(Int) -> T?) {
|
||||
private class ConversionWithRadixContext<T: Any>(val convertOrFail: (String, Int) -> T,
|
||||
val convertOrNull: (String, Int) -> T?) {
|
||||
fun assertProduces(radix: Int, input: String, output: T) {
|
||||
assertEquals(output, input.removeLeadingPlusOnJava6().convertOrFail(radix))
|
||||
assertEquals(output, input.convertOrNull(radix))
|
||||
assertEquals(output, convertOrFail(input.removeLeadingPlusOnJava6(), radix))
|
||||
assertEquals(output, convertOrNull(input, radix))
|
||||
}
|
||||
|
||||
fun assertFailsOrNull(radix: Int, input: String) {
|
||||
assertFailsWith<NumberFormatException>("Expected to fail on input \"$input\" with radix $radix",
|
||||
{ input.convertOrFail(radix) })
|
||||
{ convertOrFail(input, radix) })
|
||||
|
||||
assertNull(input.convertOrNull(radix), message = "On input \"$input\" with radix $radix")
|
||||
assertNull(convertOrNull(input, radix), message = "On input \"$input\" with radix $radix")
|
||||
}
|
||||
}
|
||||
|
||||
@kotlin.jvm.JvmVersion
|
||||
private val isJava6 = System.getProperty("java.version").startsWith("1.6.")
|
||||
|
||||
@kotlin.jvm.JvmVersion
|
||||
private fun String.removeLeadingPlusOnJava6(): String =
|
||||
if (isJava6) removePrefix("+") else this
|
||||
if (isJava6) removePrefix("+") else this
|
||||
|
||||
@kotlin.jvm.JvmVersion
|
||||
private fun doubleTotalOrderEquals(a: Any?, b: Any?) = a == b
|
||||
|
||||
Reference in New Issue
Block a user