【转载请注明出处: http://blog.csdn.net/lzl124631x】
stdlib中的atoi实现与此OJ中要求的一致. 但是测试样例不够完善, 下面的两个红色的测试样例是LeetCode所没有的.
我的测试样例
2147483647 | 2147483647 | valid |
2147483648 | 2147483647 | overflow |
-2147483648 | -2147483648 | valid |
-2147483649 | -2147483648 | underflow |
9223372036854775807 | 2147483647 | overflow(int), valid(long long int) |
9223372036854775808 | 2147483647 | overflow(int), overflow(long long int) |
-9223372036854775808 | -2147483648 | underflow(int), valid(long long int) |
-9223372036854775809 | -2147483648 | underflow(int), underflow(long long int) |
4294967295 | 2147483647 | overflow, (int)(429496729 * 10 + 5) == -1 |
4294967296 | 2147483647 | overflow, (int)(429496729 * 10 + 6) == 0 |
4294967297 | 2147483647 | overflow, (int)(429496729 * 10 + 7) == 1 |
我的代码
#define MAX_UINT ((unsigned)~0)
#define MAX_INT ((int)(MAX_UINT >> 1))
#define MIN_INT ((int)~MAX_INT)
class Solution{
static const int limit = MAX_INT / 10;
bool overflowed;
public:
int atoi(const char *str) {
// Start typing your C/C++ solution below
// DO NOT write int main() function
// local variables
int sum = 0;
bool neg = false;
// initialization
overflowed = false;
// handle exception
if(!str) return 0;
// skip spaces
while(*str == ' ') str++;
// handle sign
if(*str == '+') str++;
else if(*str == '-'){
neg = true;
str++;
}
// handle digits
while(*str >= '0' && *str <= '9'){
if(sum > limit){
overflowed = true;
break;
}
sum = sum * 10 + (*str++ - '0');
if(sum < 0){
overflowed = true;
break;
}
}
// handle overflow
if(overflowed){
return neg? MIN_INT : MAX_INT;
}
// normal output
return neg? -sum : sum;
}
};
关键点在于判断溢出, 而会导致上溢的地方就是sum = sum * 10 + (*str - '0'); 此操作定义为"AD"操作, Add Digit.
那如何判断当前的sum值进行AD之后会上溢呢?
判断sum进行AD后是否变为负数? 不准确, 因为一个数可能上溢之后变为正数!
上溢的结果可正可负!
例如, MAX_INT有10位, 那么987654321是有效的int值, 但是987654321 * 10 == 1286608618. 上溢为正数了! 事实上还上溢了两次!
我的思路是: AD溢出的充分必要条件是 (sum > MAX_INT / 10) && (AD(sum) < 0)
AD之前若sum > MAX_INT / 10成立说明sum * 10必然超过MAX_INT, 故而溢出;
但sum <= MAX_INT / 10的情况下, AD(sum)之后仍然可能溢出! 因为MAX_INT / 10代表MAX_INT的前9位, AD(MAX_INT / 10)之后末位可能超过MAX_INT的末位! 此时的溢出必然导致sum < 0.
(这个必然其实还值得思考. 当int仅有3位的时候, int范围为-4 ~ 3, 若输入为9则AD后会变为1, 仍然大于0! 不过3位的int连十以内的数都表达不全, 用它做atoi就算了吧)
因此还需在 AD之后判断sum是否<0, 若<0则说明溢出.
事情还没完, 自己被一个问题困扰: 若A*10后会上溢为正数, 那么最小的A是多少?
int数轴是一个环, 范围为-2147483648 (-2^31) ~ 2147483647 (2^31 - 1).
最大值2147483647 (0x7fffffff)加一之后会变为最小值-2147483648 (0x80000000)
int数轴的长度int_len = 2^32 = MAX_UINT + 1 = MAX_INT(正数个数) + |MIN_INT|(负数个数) + 1(还有0) = 2 * MAX_INT + 2
注意:
(int)int_len == 0.
forall n∈int, (int)(n + int_len * k) == n, k∈Z
因此, 若(int)n > 0, 则存在一个k使得 n == (int)n + int_len * k 成立.
经过以上分析, 若A * 10后上溢为正数, 至少说明 A * 10 > int_len
故A > (double)int_len / 10.0, 即 A > (int)int_len / 10 的时候就 可能上溢为正数. 这里强调"可能"是因为满足该条件的sum还可能继续上溢为负数!
因此 最小的A是int_len / 10 + 1 = 2^32 / 10 + 1 = 429496730 = (MAX_UINT + 1) / 10 + 1 = MAX_UINT / 10 + 1
啰嗦句, 最后一个等号不仅在int为32位的情况下成立, 在int为N位(N>0)的情况下都成立. 因为2^N末位是2, 4, 6, 8, 故MAX_UINT(2^N - 1)加不加1整除10后的结果都一样.
结合以上分析, 有了我的一组测试样例, 4294967295 ~ 4294967297, 因为4294967296正好是2^32, 可以视为上溢为0; 而4294967297就是上溢为1的情况了, 若仅以sum < 0 来判断溢出是无法正确处理这种情况的.
写代码的时候还发现自己一个疏忽的地方.
int A; 如果代码中有出现了A / 10 * 2的地方, 不要简单地将其替换为A / 5. 比如A为6的时候结果就不一样, 分别为0和1.
经验: 当涉及 整数除法和乘法共存的表达式时, 不要仓促地对乘除进行化简!
那如何判断当前的sum值进行AD之后会上溢呢?
判断sum进行AD后是否变为负数? 不准确, 因为一个数可能上溢之后变为正数!
上溢的结果可正可负!
例如, MAX_INT有10位, 那么987654321是有效的int值, 但是987654321 * 10 == 1286608618. 上溢为正数了! 事实上还上溢了两次!
我的思路是: AD溢出的充分必要条件是 (sum > MAX_INT / 10) && (AD(sum) < 0)
AD之前若sum > MAX_INT / 10成立说明sum * 10必然超过MAX_INT, 故而溢出;
但sum <= MAX_INT / 10的情况下, AD(sum)之后仍然可能溢出! 因为MAX_INT / 10代表MAX_INT的前9位, AD(MAX_INT / 10)之后末位可能超过MAX_INT的末位! 此时的溢出必然导致sum < 0.
(这个必然其实还值得思考. 当int仅有3位的时候, int范围为-4 ~ 3, 若输入为9则AD后会变为1, 仍然大于0! 不过3位的int连十以内的数都表达不全, 用它做atoi就算了吧)
因此还需在 AD之后判断sum是否<0, 若<0则说明溢出.
事情还没完, 自己被一个问题困扰: 若A*10后会上溢为正数, 那么最小的A是多少?
int数轴是一个环, 范围为-2147483648 (-2^31) ~ 2147483647 (2^31 - 1).
最大值2147483647 (0x7fffffff)加一之后会变为最小值-2147483648 (0x80000000)
int数轴的长度int_len = 2^32 = MAX_UINT + 1 = MAX_INT(正数个数) + |MIN_INT|(负数个数) + 1(还有0) = 2 * MAX_INT + 2
注意:
(int)int_len == 0.
forall n∈int, (int)(n + int_len * k) == n, k∈Z
因此, 若(int)n > 0, 则存在一个k使得 n == (int)n + int_len * k 成立.
经过以上分析, 若A * 10后上溢为正数, 至少说明 A * 10 > int_len
故A > (double)int_len / 10.0, 即 A > (int)int_len / 10 的时候就 可能上溢为正数. 这里强调"可能"是因为满足该条件的sum还可能继续上溢为负数!
因此 最小的A是int_len / 10 + 1 = 2^32 / 10 + 1 = 429496730 = (MAX_UINT + 1) / 10 + 1 = MAX_UINT / 10 + 1
啰嗦句, 最后一个等号不仅在int为32位的情况下成立, 在int为N位(N>0)的情况下都成立. 因为2^N末位是2, 4, 6, 8, 故MAX_UINT(2^N - 1)加不加1整除10后的结果都一样.
结合以上分析, 有了我的一组测试样例, 4294967295 ~ 4294967297, 因为4294967296正好是2^32, 可以视为上溢为0; 而4294967297就是上溢为1的情况了, 若仅以sum < 0 来判断溢出是无法正确处理这种情况的.
写代码的时候还发现自己一个疏忽的地方.
int A; 如果代码中有出现了A / 10 * 2的地方, 不要简单地将其替换为A / 5. 比如A为6的时候结果就不一样, 分别为0和1.
经验: 当涉及 整数除法和乘法共存的表达式时, 不要仓促地对乘除进行化简!
[更新20131006]
晚上想了一下, 判断溢出的代码可以优化.
// handle digits
while(*str >= '0' && *str <= '9'){
if(sum > limit){
overflowed = true;
break;
}
sum = sum * 10 + (*str++ - '0');
if(sum < 0){
overflowed = true;
break;
}
}
修改为如下一个条件判断即可. 代码更精简, 但是随之而来有个问题, 就是效率. 下面的代码每次进行溢出判断的时候都要进行两次减法一次除法, 而上面的代码只需要两次判断.
[更新20131007]
理由: 记D = (*str - '0')
AD溢出的充要条件为 sum * 10 + D > MAX_INT, 即
sum > (double)(MAX_INT - D) / 10.0 .
对 (double)(MAX_INT - D) / 10.0 向下取整为 (int)(MAX_INT - D) / 10
不会导致sum的范围改变.
因此
sum > (int)(MAX_INT - D) / 10 即可.
// handle digits
while(*str >= '0' && *str <= '9'){
if(sum > (MAX_INT - (*str - '0')) /10){
overflowed = true;
break;
}
sum = sum * 10 + (*str++ - '0');
}
再次优化. 由于读取前几位的时候进行溢出判断是浪费的, 因此通过记录位数, 只在已经读取不少于MAX_INT位数个字符时进行溢出判断. 此处利用了短路性质.
class Solution{
static const int limit = MAX_INT / 10;
bool overflowed;
int max_int_len;
int len;
public:
Solution(){
int n = MAX_INT;
// calculate the length of MAX_INT
max_int_len = 0;
while(n > 0){
n /= 10;
max_int_len++;
}
}
int atoi(const char *str) {
// Start typing your C/C++ solution below
// DO NOT write int main() function
// local variables
int sum = 0;
bool neg = false;
// initialization
overflowed = false;
// handle exception
if(!str) return 0;
// skip spaces
while(*str == ' ') str++;
// handle sign
if(*str == '+') str++;
else if(*str == '-'){
neg = true;
str++;
}
// handle digits
len = 0;
while(*str >= '0' && *str <= '9'){
if(++len >= max_int_len && sum > (MAX_INT - (*str - '0')) /10){
overflowed = true;
break;
}
sum = sum * 10 + (*str++ - '0');
}
// handle overflow
if(overflowed){
return neg? MIN_INT : MAX_INT;
}
// normal output
return neg? -sum : sum;
}
};
另一种比较好的方法是利用
long long int进行运算, 最后根据是否 > MAX_INT 判断溢出即可.
这样可以大大简化判断溢出的代码, 但是如果让你实现atolli呢? 上面的代码可扩展性更好一些.
long long int代码
#define MAX_UINT ((unsigned)~0)
#define MAX_INT ((int)(MAX_UINT >> 1))
#define MIN_INT ((int)~MAX_INT)
#define LLI long long int
class Solution{
public:
int atoi(const char *str) {
// Start typing your C/C++ solution below
// DO NOT write int main() function
// local variables
LLI sum = 0;
bool neg = false;
// handle exception
if(!str) return 0;
// skip spaces
while(*str == ' ') str++;
// handle sign
if(*str == '+') str++;
else if(*str == '-'){
neg = true;
str++;
}
// handle digits
while(*str >= '0' && *str <= '9'){
sum = sum * 10 + (*str++ - '0');
}
// handle overflow
if(sum > MAX_INT){
return neg? MIN_INT : MAX_INT;
}
// normal output
return neg? -sum : sum;
}
};
[更新20131006]
上面的代码虽然通过了LeetCodeOJ, 但是发现了个问题: 当输入超过long long int所能表达的极限时会输出错误的结果! 应将溢出处理放在循环内.
// handle digits
while(*str >= '0' && *str <= '9'){
sum = sum * 10 + (*str++ - '0');
// handle overflow
if(sum > MAX_INT){
return neg? MIN_INT : MAX_INT;
}
}