diff --git a/libraries/stdlib/js/src/kotlin/numberConversions.kt b/libraries/stdlib/js/src/kotlin/numberConversions.kt index 69b66185762..4c33c66b092 100644 --- a/libraries/stdlib/js/src/kotlin/numberConversions.kt +++ b/libraries/stdlib/js/src/kotlin/numberConversions.kt @@ -151,5 +151,3 @@ internal actual fun digitOf(char: Char, radix: Int): Int = when { char >= 'a' && char <= 'z' -> char - 'a' + 10 else -> -1 }.let { if (it >= radix) -1 else it } - -private fun numberFormatError(input: String): Nothing = throw NumberFormatException("Invalid number format: '$input'") \ No newline at end of file diff --git a/libraries/stdlib/src/kotlin/text/StringNumberConversions.kt b/libraries/stdlib/src/kotlin/text/StringNumberConversions.kt index 44b336fbe2c..f8aacf58090 100644 --- a/libraries/stdlib/src/kotlin/text/StringNumberConversions.kt +++ b/libraries/stdlib/src/kotlin/text/StringNumberConversions.kt @@ -178,3 +178,6 @@ public fun String.toLongOrNull(radix: Int): Long? { return if (isNegative) result else -result } + + +internal fun numberFormatError(input: String): Nothing = throw NumberFormatException("Invalid number format: '$input'") \ No newline at end of file diff --git a/libraries/stdlib/test/text/StringNumberConversionTest.kt b/libraries/stdlib/test/text/StringNumberConversionTest.kt index 97754b6c772..aa539693b75 100644 --- a/libraries/stdlib/test/text/StringNumberConversionTest.kt +++ b/libraries/stdlib/test/text/StringNumberConversionTest.kt @@ -154,6 +154,128 @@ class StringNumberConversionTest { } } + + + @Test fun toUByte() { + compareConversion({it.toUByte()}, {it.toUByteOrNull()}) { + assertProduces("255", UByte.MAX_VALUE) +// assertProduces("+77", 77.toUByte()) + assertProduces("128", 128u.toUByte()) + assertFailsOrNull("-1") + assertFailsOrNull("256") + assertFailsOrNull("") + assertFailsOrNull(" ") + } + + compareConversionWithRadix(String::toUByte, String::toUByteOrNull) { + assertProduces(16, "7a", 0x7a.toUByte()) +// assertProduces(16, "+7F", 127.toByte()) + assertProduces(16, "80", 128u.toUByte()) + assertProduces(16, "Ff", 255u.toUByte()) + assertFailsOrNull(2, "100000000") + assertFailsOrNull(8, "") + assertFailsOrNull(8, " ") + } + } + + @Test fun toUShort() { + compareConversion({it.toUShort()}, {it.toUShortOrNull()}) { +// assertProduces("+77", 77.toUShort()) + assertProduces("65535", UShort.MAX_VALUE) + assertFailsOrNull("+65536") + assertFailsOrNull("-32768") + assertFailsOrNull("") + assertFailsOrNull(" ") + } + + compareConversionWithRadix(String::toUShort, String::toUShortOrNull) { + assertProduces(16, "7FFF", 0x7FFF.toUShort()) + assertProduces(16, "FfFf", UShort.MAX_VALUE) + assertFailsOrNull(16, "-8000") + assertFailsOrNull(5, "10000000") + assertFailsOrNull(2, "") + assertFailsOrNull(2, " ") + } + } + + @Test fun toUInt() { + compareConversion({it.toUInt()}, {it.toUIntOrNull()}) { + assertProduces("77", 77u) + assertProduces("4294967295", UInt.MAX_VALUE) + + assertFailsOrNull("-1") + assertFailsOrNull("4294967296") + assertFailsOrNull("42949672940") + assertFailsOrNull("-2147483649") + assertFailsOrNull("239239kotlin") + assertFailsOrNull("") + assertFailsOrNull(" ") + } + + compareConversionWithRadix(String::toUInt, String::toUIntOrNull) { + assertProduces(10, "0", 0u) + assertProduces(10, "473", 473u) +// assertProduces(10, "+42", 42u) + assertProduces(10, "2147483647", 2147483647u) + + assertProduces(16, "FF", 255) + assertProduces(16, "ffFFff01", 0u - 255u) + assertProduces(2, "1100110", 102) + assertProduces(27, "Kona", 411787) + + assertFailsOrNull(10, "-0") + assertFailsOrNull(10, "42949672940") + assertFailsOrNull(8, "99") + assertFailsOrNull(10, "Kona") + assertFailsOrNull(16, "") + assertFailsOrNull(16, " ") + } + + assertFailsWith("Expected to fail with radix 1") { "1".toUInt(radix = 1) } + assertFailsWith("Expected to fail with radix 37") { "37".toUIntOrNull(radix = 37) } + } + + + @Test fun toULong() { + compareConversion({it.toULong()}, {it.toULongOrNull()}) { + assertProduces("77", 77uL) + assertProduces("18446744073709551615", ULong.MAX_VALUE) + + assertFailsOrNull("-1") + assertFailsOrNull("18446744073709551616") + assertFailsOrNull("922337 75809") + assertFailsOrNull("92233,75809") + assertFailsOrNull("92233`75809") + assertFailsOrNull("-922337KOTLIN775809") + assertFailsOrNull("") + assertFailsOrNull(" ") + } + + compareConversionWithRadix(String::toULong, String::toULongOrNull) { + assertProduces(10, "0", 0uL) + assertProduces(10, "473", 473uL) +// assertProduces(10, "+42", 42uL) + + assertProduces(16, "7F11223344556677", 0x7F11223344556677uL) +// assertProduces(16, "+7faabbccddeeff00", 0x7faabbccddeeff00uL) + assertProduces(16, "8000000000000000", Long.MIN_VALUE.toULong()) + assertProduces(16, "FFFFffffFFFFffff", ULong.MAX_VALUE) + assertProduces(2, "1100110", 102uL) + assertProduces(36, "Hazelnut", 1356099454469uL) + + assertFailsOrNull(8, "-7") + assertFailsOrNull(8, "99") + assertFailsOrNull(10, "Hazelnut") + assertFailsOrNull(4, "") + assertFailsOrNull(4, " ") + } + + assertFailsWith("Expected to fail with radix 37") { "37".toULong(radix = 37) } + assertFailsWith("Expected to fail with radix 1") { "1".toULongOrNull(radix = 1) } + } + + + @Test fun byteToStringWithRadix() { assertEquals("7a", 0x7a.toByte().toString(16)) assertEquals("-80", Byte.MIN_VALUE.toString(radix = 16)) diff --git a/libraries/stdlib/unsigned/src/kotlin/UStringNumberConversions.kt b/libraries/stdlib/unsigned/src/kotlin/UStringNumberConversions.kt index e4e76fbc6e3..c7cc4f9a34c 100644 --- a/libraries/stdlib/unsigned/src/kotlin/UStringNumberConversions.kt +++ b/libraries/stdlib/unsigned/src/kotlin/UStringNumberConversions.kt @@ -46,3 +46,212 @@ public /*inline*/ fun UInt.toString(radix: Int): String = this.toLong().toString public fun ULong.toString(radix: Int): String = ulongToString(this.toLong(), checkRadix(radix)) +/** + * Parses the string as a signed [UByte] number and returns the result. + * @throws NumberFormatException if the string is not a valid representation of a number. + */ +@SinceKotlin("1.3") +@ExperimentalUnsignedTypes +public fun String.toUByte(): UByte = toUByteOrNull() ?: numberFormatError(this) + +/** + * Parses the string as a signed [UByte] number and returns the result. + * @throws NumberFormatException if the string is not a valid representation of a number. + * @throws IllegalArgumentException when [radix] is not a valid radix for string to number conversion. + */ +@SinceKotlin("1.3") +@ExperimentalUnsignedTypes +public fun String.toUByte(radix: Int): UByte = toUByteOrNull(radix) ?: numberFormatError(this) + + +/** + * Parses the string as a [UShort] number and returns the result. + * @throws NumberFormatException if the string is not a valid representation of a number. + */ +@SinceKotlin("1.3") +@ExperimentalUnsignedTypes +public fun String.toUShort(): UShort = toUShortOrNull() ?: numberFormatError(this) + +/** + * Parses the string as a [UShort] number and returns the result. + * @throws NumberFormatException if the string is not a valid representation of a number. + * @throws IllegalArgumentException when [radix] is not a valid radix for string to number conversion. + */ +@SinceKotlin("1.3") +@ExperimentalUnsignedTypes +public fun String.toUShort(radix: Int): UShort = toUShortOrNull(radix) ?: numberFormatError(this) + +/** + * Parses the string as an [UInt] number and returns the result. + * @throws NumberFormatException if the string is not a valid representation of a number. + */ +@SinceKotlin("1.3") +@ExperimentalUnsignedTypes +public fun String.toUInt(): UInt = toUIntOrNull() ?: numberFormatError(this) + +/** + * Parses the string as an [UInt] number and returns the result. + * @throws NumberFormatException if the string is not a valid representation of a number. + * @throws IllegalArgumentException when [radix] is not a valid radix for string to number conversion. + */ +@SinceKotlin("1.3") +@ExperimentalUnsignedTypes +public fun String.toUInt(radix: Int): UInt = toUIntOrNull(radix) ?: numberFormatError(this) + +/** + * Parses the string as a [ULong] number and returns the result. + * @throws NumberFormatException if the string is not a valid representation of a number. + */ +@SinceKotlin("1.3") +@ExperimentalUnsignedTypes +public fun String.toULong(): ULong = toULongOrNull() ?: numberFormatError(this) + +/** + * Parses the string as a [ULong] number and returns the result. + * @throws NumberFormatException if the string is not a valid representation of a number. + * @throws IllegalArgumentException when [radix] is not a valid radix for string to number conversion. + */ +@SinceKotlin("1.3") +@ExperimentalUnsignedTypes +public fun String.toULong(radix: Int): ULong = toULongOrNull(radix) ?: numberFormatError(this) + + + + + +/** + * Parses the string as an [UByte] number and returns the result + * or `null` if the string is not a valid representation of a number. + */ +@SinceKotlin("1.3") +@ExperimentalUnsignedTypes +public fun String.toUByteOrNull(): UByte? = toUByteOrNull(radix = 10) + +/** + * Parses the string as an [UByte] number and returns the result + * or `null` if the string is not a valid representation of a number. + * + * @throws IllegalArgumentException when [radix] is not a valid radix for string to number conversion. + */ +@SinceKotlin("1.3") +@ExperimentalUnsignedTypes +public fun String.toUByteOrNull(radix: Int): UByte? { + val int = this.toUIntOrNull(radix) ?: return null + if (int > UByte.MAX_VALUE) return null + return int.toUByte() +} + +/** + * Parses the string as an [UShort] number and returns the result + * or `null` if the string is not a valid representation of a number. + */ +@SinceKotlin("1.3") +@ExperimentalUnsignedTypes +public fun String.toUShortOrNull(): UShort? = toUShortOrNull(radix = 10) + +/** + * Parses the string as an [UShort] number and returns the result + * or `null` if the string is not a valid representation of a number. + * + * @throws IllegalArgumentException when [radix] is not a valid radix for string to number conversion. + */ +@SinceKotlin("1.3") +@ExperimentalUnsignedTypes +public fun String.toUShortOrNull(radix: Int): UShort? { + val int = this.toUIntOrNull(radix) ?: return null + if (int > UShort.MAX_VALUE) return null + return int.toUShort() +} + +/** + * Parses the string as an [UInt] number and returns the result + * or `null` if the string is not a valid representation of a number. + */ +@SinceKotlin("1.3") +@ExperimentalUnsignedTypes +public fun String.toUIntOrNull(): UInt? = toUIntOrNull(radix = 10) + +/** + * Parses the string as an [UInt] number and returns the result + * or `null` if the string is not a valid representation of a number. + * + * @throws IllegalArgumentException when [radix] is not a valid radix for string to number conversion. + */ +@SinceKotlin("1.3") +@ExperimentalUnsignedTypes +public fun String.toUIntOrNull(radix: Int): UInt? { + checkRadix(radix) + + val length = this.length + if (length == 0) return null + + val limit: UInt = UInt.MAX_VALUE + + val firstChar = this[0] + if (firstChar < '0') return null + + val uradix = radix.toUInt() + val limitBeforeMul = limit / uradix + var result = 0u + for (i in 0 until length) { + val digit = digitOf(this[i], radix) + + if (digit < 0) return null + if (result > limitBeforeMul) return null + + result *= uradix + + if (result > limit - digit.toUInt()) return null + + result += digit.toUInt() + } + + return result +} + +/** + * Parses the string as an [ULong] number and returns the result + * or `null` if the string is not a valid representation of a number. + */ +@SinceKotlin("1.3") +@ExperimentalUnsignedTypes +public fun String.toULongOrNull(): ULong? = toULongOrNull(radix = 10) + +/** + * Parses the string as an [ULong] number and returns the result + * or `null` if the string is not a valid representation of a number. + * + * @throws IllegalArgumentException when [radix] is not a valid radix for string to number conversion. + */ +@SinceKotlin("1.3") +@ExperimentalUnsignedTypes +public fun String.toULongOrNull(radix: Int): ULong? { + checkRadix(radix) + + val length = this.length + if (length == 0) return null + + val limit: ULong = ULong.MAX_VALUE + + val firstChar = this[0] + if (firstChar < '0') return null + + + val uradix = radix.toUInt() + val limitBeforeMul = limit / uradix + var result = 0uL + for (i in 0 until length) { + val digit = digitOf(this[i], radix) + + if (digit < 0) return null + if (result > limitBeforeMul) return null + + result *= uradix + + if (result > limit - digit.toUInt()) return null + + result += digit.toUInt() + } + + return result +} diff --git a/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt b/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt index 9d538b999da..89868585e42 100644 --- a/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt +++ b/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt @@ -5064,5 +5064,21 @@ public final class kotlin/text/UStringNumberConversionsKt { public static final fun toString (II)Ljava/lang/String; public static final fun toString (JI)Ljava/lang/String; public static final fun toString (SI)Ljava/lang/String; + public static final fun toUByte (Ljava/lang/String;)B + public static final fun toUByte (Ljava/lang/String;I)B + public static final fun toUByteOrNull (Ljava/lang/String;)Lkotlin/UByte; + public static final fun toUByteOrNull (Ljava/lang/String;I)Lkotlin/UByte; + public static final fun toUInt (Ljava/lang/String;)I + public static final fun toUInt (Ljava/lang/String;I)I + public static final fun toUIntOrNull (Ljava/lang/String;)Lkotlin/UInt; + public static final fun toUIntOrNull (Ljava/lang/String;I)Lkotlin/UInt; + public static final fun toULong (Ljava/lang/String;)J + public static final fun toULong (Ljava/lang/String;I)J + public static final fun toULongOrNull (Ljava/lang/String;)Lkotlin/ULong; + public static final fun toULongOrNull (Ljava/lang/String;I)Lkotlin/ULong; + public static final fun toUShort (Ljava/lang/String;)S + public static final fun toUShort (Ljava/lang/String;I)S + public static final fun toUShortOrNull (Ljava/lang/String;)Lkotlin/UShort; + public static final fun toUShortOrNull (Ljava/lang/String;I)Lkotlin/UShort; }