Simplify atoi*/strto* for signed integers.
Make the cost of strto<signed> closer to the cost of strto<unsigned>
by removing an `if` from the inner loop. Previously a signed conversion
cost 10ns more than an unsigned one.
After:
BM_inttypes_strtoimax 81 ns 81 ns 8603362
BM_inttypes_strtoumax 78 ns 78 ns 8967174
BM_stdlib_strtol 81 ns 81 ns 8685537
BM_stdlib_strtoll 81 ns 81 ns 8685481
BM_stdlib_strtoul 78 ns 78 ns 8962569
BM_stdlib_strtoull 78 ns 78 ns 8972023
Bug: N/A
Test: ran tests, benchmarks
Change-Id: I72dd5499427b6a940bd94c4d6f727f7efe134d7e
diff --git a/libc/bionic/strtol.cpp b/libc/bionic/strtol.cpp
index f4c8c5f..f6005fa 100644
--- a/libc/bionic/strtol.cpp
+++ b/libc/bionic/strtol.cpp
@@ -66,31 +66,11 @@
}
if (base == 0) base = (c == '0') ? 8 : 10;
- // Compute the cutoff value between legal numbers and illegal
- // numbers. That is the largest legal value, divided by the
- // base. An input number that is greater than this value, if
- // followed by a legal input character, is too big. One that
- // is equal to this value may be valid or not; the limit
- // between valid and invalid numbers is then based on the last
- // digit. For instance, if the range for intmax_t is
- // [-9223372036854775808..9223372036854775807] and the input base
- // is 10, cutoff will be set to 922337203685477580 and cutlim to
- // either 7 (neg==0) or 8 (neg==1), meaning that if we have
- // accumulated a value > 922337203685477580, or equal but the
- // next digit is > 7 (or 8), the number is too big, and we will
- // return a range error.
- T cutoff = neg ? Min : Max;
- int cutlim = cutoff % base;
- cutoff /= base;
- if (neg) {
- if (cutlim > 0) {
- cutlim -= base;
- cutoff += 1;
- }
- cutlim = -cutlim;
- }
-
- // Set `any` if any digits consumed; make it negative to indicate overflow.
+ // We always work in the negative space because the most negative value has a
+ // larger magnitude than the most positive value.
+ T cutoff = Min / base;
+ int cutlim = -(Min % base);
+ // Non-zero if any digits consumed; negative to indicate overflow/underflow.
int any = 0;
T acc = 0;
for (; ; c = *s++) {
@@ -103,29 +83,25 @@
}
if (c >= base) break;
if (any < 0) continue;
- if (neg) {
- if (acc < cutoff || (acc == cutoff && c > cutlim)) {
- any = -1;
- acc = Min;
- errno = ERANGE;
- } else {
- any = 1;
- acc *= base;
- acc -= c;
- }
+ if (acc < cutoff || (acc == cutoff && c > cutlim)) {
+ any = -1;
+ acc = Min;
+ errno = ERANGE;
} else {
- if (acc > cutoff || (acc == cutoff && c > cutlim)) {
- any = -1;
- acc = Max;
- errno = ERANGE;
- } else {
- any = 1;
- acc *= base;
- acc += c;
- }
+ any = 1;
+ acc *= base;
+ acc -= c;
}
}
if (endptr != nullptr) *endptr = const_cast<char*>(any ? s - 1 : nptr);
+ if (!neg) {
+ if (acc == Min) {
+ errno = ERANGE;
+ acc = Max;
+ } else {
+ acc = -acc;
+ }
+ }
return acc;
}
@@ -177,7 +153,7 @@
errno = ERANGE;
} else {
any = 1;
- acc *= static_cast<T>(base);
+ acc *= base;
acc += c;
}
}
diff --git a/tests/stdlib_test.cpp b/tests/stdlib_test.cpp
index 93877f3..caa7a85 100644
--- a/tests/stdlib_test.cpp
+++ b/tests/stdlib_test.cpp
@@ -679,22 +679,51 @@
if (std::numeric_limits<T>::is_signed) {
// Minimum (such as -128).
std::string min{std::to_string(std::numeric_limits<T>::min())};
+ end_p = nullptr;
+ errno = 0;
ASSERT_EQ(std::numeric_limits<T>::min(), fn(min.c_str(), &end_p, 0));
+ ASSERT_EQ(0, errno);
+ ASSERT_EQ('\0', *end_p);
// Too negative (such as -129).
min.back() = (min.back() + 1);
+ end_p = nullptr;
errno = 0;
ASSERT_EQ(std::numeric_limits<T>::min(), fn(min.c_str(), &end_p, 0));
ASSERT_EQ(ERANGE, errno);
+ ASSERT_EQ('\0', *end_p);
}
// Maximum (such as 127).
std::string max{std::to_string(std::numeric_limits<T>::max())};
+ end_p = nullptr;
+ errno = 0;
ASSERT_EQ(std::numeric_limits<T>::max(), fn(max.c_str(), &end_p, 0));
+ ASSERT_EQ(0, errno);
+ ASSERT_EQ('\0', *end_p);
// Too positive (such as 128).
max.back() = (max.back() + 1);
+ end_p = nullptr;
errno = 0;
ASSERT_EQ(std::numeric_limits<T>::max(), fn(max.c_str(), &end_p, 0));
ASSERT_EQ(ERANGE, errno);
+ ASSERT_EQ('\0', *end_p);
+
+ // In case of overflow, strto* leaves us pointing past the end of the number,
+ // not at the digit that overflowed.
+ end_p = nullptr;
+ errno = 0;
+ ASSERT_EQ(std::numeric_limits<T>::max(),
+ fn("99999999999999999999999999999999999999999999999999999abc", &end_p, 0));
+ ASSERT_EQ(ERANGE, errno);
+ ASSERT_STREQ("abc", end_p);
+ if (std::numeric_limits<T>::is_signed) {
+ end_p = nullptr;
+ errno = 0;
+ ASSERT_EQ(std::numeric_limits<T>::min(),
+ fn("-99999999999999999999999999999999999999999999999999999abc", &end_p, 0));
+ ASSERT_EQ(ERANGE, errno);
+ ASSERT_STREQ("abc", end_p);
+ }
}
TEST(stdlib, strtol_smoke) {