atoi的实现

【转载请注明出处: http://blog.csdn.net/lzl124631x

stdlib中的atoi实现与此OJ中要求的一致. 但是测试样例不够完善, 下面的两个红色的测试样例是LeetCode所没有的.

我的测试样例
int普通溢出
21474836472147483647valid
21474836482147483647overflow
-2147483648-2147483648valid
-2147483649-2147483648underflow
long long int普通溢出
92233720368547758072147483647overflow(int), valid(long long int)
92233720368547758082147483647overflow(int), overflow(long long int)
-9223372036854775808-2147483648underflow(int), valid(long long int)
-9223372036854775809-2147483648underflow(int), underflow(long long int)
上溢为正数
42949672952147483647overflow, (int)(429496729 * 10 + 5) == -1
42949672962147483647overflow, (int)(429496729 * 10 + 6) == 0
42949672972147483647overflow, (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.
经验: 当涉及 整数除法和乘法共存的表达式时, 不要仓促地对乘除进行化简!

[更新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;
			}
		}


评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值