stdlib: Fix number parsing
This commit is contained in:
@@ -1090,7 +1090,8 @@ task parse0(type: RunKonanTest) {
|
||||
goldValue = "false\n" +
|
||||
"true\n" +
|
||||
"-1\n" +
|
||||
"-86\n" +
|
||||
"10\n" +
|
||||
"170\n" +
|
||||
"30\n" +
|
||||
"4294967295\n" +
|
||||
"bad format\n" +
|
||||
|
||||
+6
-1
@@ -50,7 +50,12 @@ private class ConversionWithRadixContext<T: Any>(val convertOrFail: (String, Int
|
||||
}
|
||||
}
|
||||
|
||||
private fun doubleTotalOrderEquals(a: Any?, b: Any?) = a == b
|
||||
private fun doubleTotalOrderEquals(a: Double?, b: Double?): Boolean {
|
||||
if (a != null && b != null && a.isNaN() && b.isNaN()) {
|
||||
return true
|
||||
}
|
||||
return a == b
|
||||
}
|
||||
|
||||
fun box() {
|
||||
compareConversion(String::toDouble, String::toDoubleOrNull, ::doubleTotalOrderEquals) {
|
||||
|
||||
+7
-1
@@ -57,6 +57,12 @@ fun box() {
|
||||
assertProduces("0x77p1", (0x77 shl 1).toDouble())
|
||||
assertProduces("0x.77P8", 0x77.toDouble())
|
||||
|
||||
assertFailsOrNull("0x77e1")
|
||||
// TODO: Java Double.valueOf specification requires mandatory binary exponent character (p) in the string parsed if the string is a hex one.
|
||||
// See: http://docs.oracle.com/javase/8/docs/api/java/lang/Double.html#valueOf-java.lang.String-
|
||||
// E.g.
|
||||
// "0x77p0".toDouble() // OK for both Kotlin/JVM and Kotlin/Native.
|
||||
// "0x77".toDouble() // throws NumberFormatException in Kotlin/JVM and OK in Kotlin/Native.
|
||||
// Do we need to handle such case? Or it is OK to consume such strings?
|
||||
//assertFailsOrNull("0x77e1")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,8 @@ fun main(args: Array<String>) {
|
||||
println("false".toBoolean())
|
||||
println("true".toBoolean())
|
||||
println("-1".toByte())
|
||||
println("aa".toByte(16))
|
||||
println("a".toByte(16))
|
||||
println("aa".toShort(16))
|
||||
println("11110".toInt(2))
|
||||
println("ffffffff".toLong(16))
|
||||
try {
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
@@ -46,39 +47,54 @@ OBJ_GETTER(utf8ToUtf16, const char* rawString, size_t rawStringLength) {
|
||||
RETURN_OBJ(result->obj());
|
||||
}
|
||||
|
||||
template <typename T> T parseInt(KString value, KInt radix) {
|
||||
const KChar* utf16 = CharArrayAddressOfElementAt(value, 0);
|
||||
std::string utf8;
|
||||
utf8::utf16to8(utf16, utf16 + value->count_, back_inserter(utf8));
|
||||
char* end = nullptr;
|
||||
long result = strtol(utf8.c_str(), &end, radix);
|
||||
if (end != utf8.c_str() + utf8.size()) {
|
||||
ThrowNumberFormatException();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// TODO: Suppose thar we can remove these parsers
|
||||
KLong parseLong(KString value, KInt radix) {
|
||||
const KChar* utf16 = CharArrayAddressOfElementAt(value, 0);
|
||||
std::string utf8;
|
||||
utf8::utf16to8(utf16, utf16 + value->count_, back_inserter(utf8));
|
||||
char* end = nullptr;
|
||||
errno = 0;
|
||||
KLong result = strtoll(utf8.c_str(), &end, radix);
|
||||
if (end != utf8.c_str() + utf8.size()) {
|
||||
if (utf8.size() == 0 || end != utf8.c_str() + utf8.size() || errno == ERANGE) {
|
||||
ThrowNumberFormatException();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename T> T parseInt(KString value, KInt radix) {
|
||||
KLong result = parseLong(value, radix);
|
||||
if (result < std::numeric_limits<T>::min() || result > std::numeric_limits<T>::max()) {
|
||||
ThrowNumberFormatException();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void checkParsingErrors(const char* c_str, char* end, std::string::size_type c_str_size) {
|
||||
if (end == c_str) {
|
||||
ThrowNumberFormatException();
|
||||
}
|
||||
// According to http://docs.oracle.com/javase/8/docs/api/java/lang/Double.html#valueOf-java.lang.String-
|
||||
// trailing whitespace characters must be ignored so we need to do an additional check.
|
||||
for (char* p = end; p < c_str + c_str_size; p++) {
|
||||
if (!isspace(*p)) {
|
||||
ThrowNumberFormatException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Java Double.valueOf specification requires mandatory binary exponent character (p) in the string parsed if the string is a hex one.
|
||||
// See: http://docs.oracle.com/javase/8/docs/api/java/lang/Double.html#valueOf-java.lang.String-
|
||||
// E.g.
|
||||
// "0x77p0".toDouble() // OK for both Kotlin/JVM and Kotlin/Native.
|
||||
// "0x77".toDouble() // throws NumberFormatException in Kotlin/JVM and OK in Kotlin/Native.
|
||||
// Do we need to handle such case? Or it is OK to consume such strings?
|
||||
KFloat parseFloat(KString value) {
|
||||
const KChar* utf16 = CharArrayAddressOfElementAt(value, 0);
|
||||
std::string utf8;
|
||||
utf8::utf16to8(utf16, utf16 + value->count_, back_inserter(utf8));
|
||||
char* end = nullptr;
|
||||
KFloat result = strtof(utf8.c_str(), &end);
|
||||
if (end != utf8.c_str() + utf8.size()) {
|
||||
ThrowNumberFormatException();
|
||||
}
|
||||
checkParsingErrors(utf8.c_str(), end, utf8.size());
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -89,9 +105,7 @@ KDouble parseDouble(KString value) {
|
||||
utf8::utf16to8(utf16, utf16 + value->count_, back_inserter(utf8));
|
||||
char* end = nullptr;
|
||||
KDouble result = strtod(utf8.c_str(), &end);
|
||||
if (end != utf8.c_str() + utf8.size()) {
|
||||
ThrowNumberFormatException();
|
||||
}
|
||||
checkParsingErrors(utf8.c_str(), end, utf8.size());
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -1005,20 +1019,34 @@ KChar Kotlin_Char_toUpperCase(KChar ch) {
|
||||
return towupper_Konan(ch);
|
||||
}
|
||||
|
||||
KInt Kotlin_Char_digitOf(KChar ch, KInt radix) {
|
||||
// TODO: make smarter and support full unicode.
|
||||
const static KInt digits[] = {
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
|
||||
-1, -1, -1, -1, -1, -1, -1,
|
||||
10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
|
||||
20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
|
||||
30, 31, 32, 33, 34, 35,
|
||||
-1, -1, -1, -1, -1, -1,
|
||||
10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
|
||||
20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
|
||||
30, 31, 32, 33, 34, 35 };
|
||||
if (ch < 0x30 /* 0 */ || ch > 0x7a /* z */) return -1;
|
||||
return digits[ch - 0x30];
|
||||
constexpr KInt digits[] = {
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
|
||||
-1, -1, -1, -1, -1, -1, -1,
|
||||
10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
|
||||
20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
|
||||
30, 31, 32, 33, 34, 35,
|
||||
-1, -1, -1, -1, -1, -1,
|
||||
10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
|
||||
20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
|
||||
30, 31, 32, 33, 34, 35
|
||||
};
|
||||
|
||||
// Based on Apache Harmony implementation.
|
||||
// Radix check is performed on the Kotlin side.
|
||||
KInt Kotlin_Char_digitOfChecked(KChar ch, KInt radix) {
|
||||
|
||||
KInt result = -1;
|
||||
if (ch >= 0x30 /* 0 */ && ch <= 0x7a /* z */) {
|
||||
result = digits[ch - 0x30];
|
||||
} else {
|
||||
int index = -1;
|
||||
index = binarySearchRange(digitKeys, ARRAY_SIZE(digitKeys), ch);
|
||||
if (index >= 0 && ch <= digitValues[index * 2]) {
|
||||
result = ch - digitValues[index * 2 + 1];
|
||||
}
|
||||
}
|
||||
if (result >= radix) return -1;
|
||||
return result;
|
||||
}
|
||||
|
||||
KInt Kotlin_String_indexOfChar(KString thiz, KChar ch, KInt fromIndex) {
|
||||
|
||||
@@ -27,9 +27,6 @@
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int MIN_RADIX = 2;
|
||||
constexpr int MAX_RADIX = 36;
|
||||
|
||||
char int_to_digit(uint32_t value) {
|
||||
if (value < 10) {
|
||||
return '0' + value;
|
||||
@@ -38,13 +35,11 @@ namespace {
|
||||
}
|
||||
}
|
||||
|
||||
// Radix is checked on the Kotlin side.
|
||||
template <typename T> OBJ_GETTER(Kotlin_toStringRadix, T value, KInt radix) {
|
||||
if (value == 0) {
|
||||
RETURN_RESULT_OF(CreateStringFromCString, "0");
|
||||
}
|
||||
if (radix < MIN_RADIX || radix > MAX_RADIX) {
|
||||
radix = 10;
|
||||
}
|
||||
char cstring[sizeof(T) * CHAR_BIT + 1];
|
||||
bool negative = (value < 0);
|
||||
if (!negative) {
|
||||
|
||||
@@ -96,8 +96,11 @@ external public fun Char.isHighSurrogate(): Boolean
|
||||
@SymbolName("Kotlin_Char_isLowSurrogate")
|
||||
external public fun Char.isLowSurrogate(): Boolean
|
||||
|
||||
@SymbolName("Kotlin_Char_digitOf")
|
||||
external internal fun digitOf(char: Char, radix: Int): Int
|
||||
|
||||
internal fun digitOf(char: Char, radix: Int): Int = digitOfChecked(char, checkRadix(radix))
|
||||
|
||||
@SymbolName("Kotlin_Char_digitOfChecked")
|
||||
external internal fun digitOfChecked(char: Char, radix: Int): Int
|
||||
|
||||
/**
|
||||
* Checks whether the given [radix] is valid radix for string to number and number to string conversion.
|
||||
|
||||
@@ -58,6 +58,7 @@ public inline fun Long.toString(radix: Int): String = longToString(this, checkRa
|
||||
@kotlin.internal.InlineOnly
|
||||
public inline fun String.toBoolean(): Boolean = this.equals("true", ignoreCase = true)
|
||||
|
||||
// TODO: Remove it?
|
||||
@SymbolName("Kotlin_String_parseByte")
|
||||
external private fun parseByte(value: String, radix: Int): Byte
|
||||
|
||||
@@ -66,8 +67,7 @@ external private fun parseByte(value: String, radix: Int): Byte
|
||||
* @throws NumberFormatException if the string is not a valid representation of a number.
|
||||
*/
|
||||
@kotlin.internal.InlineOnly
|
||||
@Suppress("NON_PUBLIC_CALL_FROM_PUBLIC_INLINE")
|
||||
public inline fun String.toByte(): Byte = parseByte(this, 10)
|
||||
public inline fun String.toByte(): Byte = toByteOrNull() ?: throw NumberFormatException()
|
||||
|
||||
/**
|
||||
* Parses the string as a signed [Byte] number and returns the result.
|
||||
@@ -75,8 +75,7 @@ public inline fun String.toByte(): Byte = parseByte(this, 10)
|
||||
*/
|
||||
@SinceKotlin("1.1")
|
||||
@kotlin.internal.InlineOnly
|
||||
@Suppress("NON_PUBLIC_CALL_FROM_PUBLIC_INLINE")
|
||||
public inline fun String.toByte(radix: Int): Byte = parseByte(this, checkRadix(radix))
|
||||
public inline fun String.toByte(radix: Int): Byte = toByteOrNull(radix) ?: throw NumberFormatException()
|
||||
|
||||
@SymbolName("Kotlin_String_parseShort")
|
||||
external private fun parseShort(value: String, radix: Int): Short
|
||||
@@ -86,8 +85,7 @@ external private fun parseShort(value: String, radix: Int): Short
|
||||
* @throws NumberFormatException if the string is not a valid representation of a number.
|
||||
*/
|
||||
@kotlin.internal.InlineOnly
|
||||
@Suppress("NON_PUBLIC_CALL_FROM_PUBLIC_INLINE")
|
||||
public inline fun String.toShort(): Short = parseShort(this, 10)
|
||||
public inline fun String.toShort(): Short = toShortOrNull() ?: throw NumberFormatException()
|
||||
|
||||
/**
|
||||
* Parses the string as a [Short] number and returns the result.
|
||||
@@ -95,8 +93,7 @@ public inline fun String.toShort(): Short = parseShort(this, 10)
|
||||
*/
|
||||
@SinceKotlin("1.1")
|
||||
@kotlin.internal.InlineOnly
|
||||
@Suppress("NON_PUBLIC_CALL_FROM_PUBLIC_INLINE")
|
||||
public inline fun String.toShort(radix: Int): Short = parseShort(this, checkRadix(radix))
|
||||
public inline fun String.toShort(radix: Int): Short = toShortOrNull(radix) ?: throw NumberFormatException()
|
||||
|
||||
@SymbolName("Kotlin_String_parseInt")
|
||||
external private fun parseInt(value: String, radix: Int): Int
|
||||
@@ -106,8 +103,7 @@ external private fun parseInt(value: String, radix: Int): Int
|
||||
* @throws NumberFormatException if the string is not a valid representation of a number.
|
||||
*/
|
||||
@kotlin.internal.InlineOnly
|
||||
@Suppress("NON_PUBLIC_CALL_FROM_PUBLIC_INLINE")
|
||||
public inline fun String.toInt(): Int = parseInt(this, 10)
|
||||
public inline fun String.toInt(): Int = toIntOrNull() ?: throw NumberFormatException()
|
||||
|
||||
/**
|
||||
* Parses the string as an [Int] number and returns the result.
|
||||
@@ -115,8 +111,7 @@ public inline fun String.toInt(): Int = parseInt(this, 10)
|
||||
*/
|
||||
@SinceKotlin("1.1")
|
||||
@kotlin.internal.InlineOnly
|
||||
@Suppress("NON_PUBLIC_CALL_FROM_PUBLIC_INLINE")
|
||||
public inline fun String.toInt(radix: Int): Int = parseInt(this, checkRadix(radix))
|
||||
public inline fun String.toInt(radix: Int): Int = toIntOrNull(radix) ?: throw NumberFormatException()
|
||||
|
||||
|
||||
@SymbolName("Kotlin_String_parseLong")
|
||||
@@ -127,8 +122,7 @@ external private fun parseLong(value: String, radix: Int): Long
|
||||
* @throws NumberFormatException if the string is not a valid representation of a number.
|
||||
*/
|
||||
@kotlin.internal.InlineOnly
|
||||
@Suppress("NON_PUBLIC_CALL_FROM_PUBLIC_INLINE")
|
||||
public inline fun String.toLong(): Long = parseLong(this, 10)
|
||||
public inline fun String.toLong(): Long = toLongOrNull() ?: throw NumberFormatException()
|
||||
|
||||
/**
|
||||
* Parses the string as a [Long] number and returns the result.
|
||||
@@ -136,8 +130,7 @@ public inline fun String.toLong(): Long = parseLong(this, 10)
|
||||
*/
|
||||
@SinceKotlin("1.1")
|
||||
@kotlin.internal.InlineOnly
|
||||
@Suppress("NON_PUBLIC_CALL_FROM_PUBLIC_INLINE")
|
||||
public inline fun String.toLong(radix: Int): Long = parseLong(this, checkRadix(radix))
|
||||
public inline fun String.toLong(radix: Int): Long = toLongOrNull(radix) ?: throw NumberFormatException()
|
||||
|
||||
@SymbolName("Kotlin_String_parseFloat")
|
||||
external private fun parseFloat(value: String): Float
|
||||
@@ -326,11 +319,23 @@ public fun String.toLongOrNull(radix: Int): Long? {
|
||||
* or `null` if the string is not a valid representation of a number.
|
||||
*/
|
||||
@SinceKotlin("1.1")
|
||||
public fun String.toFloatOrNull(): Float? = TODO()
|
||||
public fun String.toFloatOrNull(): Float? {
|
||||
try {
|
||||
return toFloat()
|
||||
} catch (e: NumberFormatException) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the string as a [Double] number and returns the result
|
||||
* or `null` if the string is not a valid representation of a number.
|
||||
*/
|
||||
@SinceKotlin("1.1")
|
||||
public fun String.toDoubleOrNull(): Double? = TODO()
|
||||
public fun String.toDoubleOrNull(): Double? {
|
||||
try {
|
||||
return toDouble()
|
||||
} catch (e: NumberFormatException) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user