Java整型变量溢出的判断方法
2018-02-11 00:00
204 查看
最近在做牛客网上《剑指Offer》的一道算法题:
题目要求:将一个字符串转换成一个整数,要求不能使用字符串转换整数的库函数。 数值为0或者字符串不是一个合法的数值则返回0
输入描述:
输入一个字符串,包括数字字母符号,可以为空
输入描述:
如果是合法的数值表达则返回该数字,否则返回0
实例1:
输入:+2147483647
1a33
输出:2147483647
0
在最初做这道题时,我和其他很多人一样都不同程度犯了错误,下面先看代码。
错误一:没有考虑溢出
这大概是很多人初次写的代码了吧,乍一看这题十分简单,信手拈来就写出了这样的代码,毫无疑问会因为没有通过可能导致溢出的测试用例而不被AC。
错误二:判断溢出错误1
首先,一部分人在做这道题时根本没有考虑溢出的问题,另一部分人,则是在判断整型溢出时写出了错误的代码,比如上面的代码中的判断。用sum > 0x7fffffff 和-sum < 0x80000000是判断不了整型溢出的。而这样的错误代码竟然是某《剑指Offer》Java版的示例代码。我们来看一下测试用例的运行结果。
上面是测试代码,然后来看测试结果。
结果并不是显示0以表示转换异常,显然,错误二中的代码没能正确判断是否溢出,转换结果异常。
事实上,由于整型数字(包括32位的int和64位的long)是以2进制的形式在计算机中表示,通常以补码形式表示,而在Java语言中,只存在有符号类型,没有无符号类型。那么对于32位的int来说,最高位如果是1,则表示负数,最高位是0,则表示正数,最大正数0x7fffffff的最高位是0,其余后面31位都是1,因此,sum > 0x7fffffff永远不可能成立,判断结果始终为false,同理,最小负数0x80000000,最高位是1,其他31位是0,转成10进制就是-2^31,sum < 0x80000000永远不成立,判断结果始终为false。因此,在上述错误二的判断溢出代码中,判断结果始终为false,相当于if中的代码块始终没有执行,这种错误的判断逻辑肯定得不到正确的结果。
错误三:溢出判断错误2
测试结果:
一看,0x7fffffffL+1得到了正确判断,返回了结果0表示非法字符串,但是-0x80000000L明明没有溢出,为什么也返回了0,是不是很奇怪。
其实是因为这种判断逻辑存在一个问题。在错误1,2的字符串转换过程中,均是先计算一个正数sum值,然后根据符号是‘+’或是‘-’返回sum或者sum的相反数。然后,在有符号数中,最大正数与最小负数不是互为相反数的,比如,short型,最大为32767,最小为-32768,int型,最大为2147483647,最小为-2147483648,可以看出,最小负数的相反数比最大整数大1。实际上,对于n位的有符号数来说,表示范围为[-2^n - 1,2^n]。要想转换-0x80000000,上述代码的思路是先计算该值的绝对值,然后根据符号,返回结果。而-0x80000000的绝对值是大于最大整型值的,也就是会导致溢出,所以这种方法也是不正确的。然而,在牛客网该题的测试用例中,并没有包含最小负数这个测试用例,以至于很多人使用这种判断逻辑,也成功AC了。
从上面的代码,我们可以看出,不能通过先求绝对值,再取相反数的方式来转换负数。那我们换一种思路,既然有符号数的最小负数的绝对值大于最大正数的绝对值,那我们可不可以先求负的绝对值,再根据符号转成相应的正确值呢?例如,123,我们可以先求-123,然后判断符号,返回123,这样就可以避免最小负数转换失败的情况了。下面上完整版的代码。
正确解答:
测试结果如下:
注释已经写的很清楚了,如果还没有理解的话,可以画一个数轴来帮助理解。这个代码还对单独的“+”’或者“-”做了判断,可以满足更加严格的测试用例,鲁棒性更好,实际上,这也是JDK中Integer.parseInt()方法所采用的思路。
题目要求:将一个字符串转换成一个整数,要求不能使用字符串转换整数的库函数。 数值为0或者字符串不是一个合法的数值则返回0
输入描述:
输入一个字符串,包括数字字母符号,可以为空
输入描述:
如果是合法的数值表达则返回该数字,否则返回0
实例1:
输入:+2147483647
1a33
输出:2147483647
0
在最初做这道题时,我和其他很多人一样都不同程度犯了错误,下面先看代码。
错误一:没有考虑溢出
public class Solution { public int StrToInt(String str) { if (str == null || str.trim().length() == 0)//空字符串情况 return 0; char[] a = str.trim().toCharArray(); int i = 0; char fuhao;//+-符号位 if (a[0] == '-'){ fuhao = '-'; i = 1;//第一位如果是-号,则从第二位开始循环 }else if(a[0] == '+'){ fuhao = '+';//第一位是+正号,则从第二位开始循环 i = 1; }else if(a[i ]>= 48 && a[i] <= 57){ fuhao = '+'; i = 0;//第一位是0-9的数字,则从第一位开始循环 }else{ return 0;//字符串非法 } int sum = 0; for (; i < a.length; i++) { if (a[i] < 48 || a[i] > 57) return 0;//有非数字字符 sum = sum * 10 + a[i] - 48; } return fuhao == '+' ? sum : -sum; } }
这大概是很多人初次写的代码了吧,乍一看这题十分简单,信手拈来就写出了这样的代码,毫无疑问会因为没有通过可能导致溢出的测试用例而不被AC。
错误二:判断溢出错误1
public class Solution { public int StrToInt(String str) { if (str == null || str.trim().length() == 0)//空字符串情况 return 0; char[] a = str.trim().toCharArray(); int i = 0; char fuhao;//+-符号位 if (a[0] == '-'){ fuhao = '-'; i = 1;//第一位如果是-号,则从第二位开始循环 }else if(a[0] == '+'){ fuhao = '+';//第一位是+正号,则从第二位开始循环 i = 1; }else if(a[i ]>= 48 && a[i] <= 57){ fuhao = '+'; i = 0;//第一位是0-9的数字,则从第一位开始循环 }else{ return 0;//字符串非法 } int sum = 0; for (; i < a.length; i++) { if (a[i] < 48 || a[i] > 57) return 0;//有非数字字符 sum = sum * 10 + a[i] - 48; //判断是否溢出,正整数最大0X7FFFFFFF,最小负整数0X80000000 //可将0x7fffffff用Integer.MAX_VALUE,0x80000000用Integer.MIN_VALUE替代 if((fuhao == '+' && sum > 0X7fffffff) || (fuhao == '--' && -sum < 0X80000000)){ return 0; } } return fuhao == '+' ? sum : -sum; } }
首先,一部分人在做这道题时根本没有考虑溢出的问题,另一部分人,则是在判断整型溢出时写出了错误的代码,比如上面的代码中的判断。用sum > 0x7fffffff 和-sum < 0x80000000是判断不了整型溢出的。而这样的错误代码竟然是某《剑指Offer》Java版的示例代码。我们来看一下测试用例的运行结果。
/** * Created by ray on 2018/2/11. */ public class Test { public static void main(String[] args) { Solution solution = new Solution(); long value1 = 0x7fffffffL + 1; long value2 = -0x80000000L - 1;//-不可少,0x80000000在long型中表示正数而不是负数 String str1 = String.valueOf(value1); String str2 = String.valueOf(value2);; System.out.println("0x7fffffffL + 1转换结果:" + result1); System.out.println("-0x80000000L - 1转换结果:" + result2); } } class Solution { public int StrToInt(String str) { if (str == null || str.trim().length() == 0)//空字符串情况 return 0; char[] a = str.trim().toCharArray(); int i = 0; char fuhao;//+-符号位 if (a[0] == '-'){ fuhao = '-'; i = 1;//第一位如果是-号,则从第二位开始循环 }else if(a[0] == '+'){ fuhao = '+';//第一位是+正号,则从第二位开始循环 i = 1; }else if(a[i ]>= 48 && a[i] <= 57){ fuhao = '+'; i = 0;//第一位是0-9的数字,则从第一位开始循环 }else{ return 0;//字符串非法 } int sum = 0; for (; i < a.length; i++) { if (a[i] < 48 || a[i] > 57) return 0;//有非数字字符 sum = sum * 10 + a[i] - 48; //判断是否溢出,正整数最大0X7FFFFFFF,最小负整数0X80000000 //可将0x7fffffff用Integer.MAX_VALUE,0x80000000用Integer.MIN_VALUE替代 if((fuhao == '+' && sum > 0X7fffffff) || (fuhao == '-' && -sum < 0X80000000)){ return 0; } } return fuhao == '+' ? sum : -sum; } }
上面是测试代码,然后来看测试结果。
结果并不是显示0以表示转换异常,显然,错误二中的代码没能正确判断是否溢出,转换结果异常。
事实上,由于整型数字(包括32位的int和64位的long)是以2进制的形式在计算机中表示,通常以补码形式表示,而在Java语言中,只存在有符号类型,没有无符号类型。那么对于32位的int来说,最高位如果是1,则表示负数,最高位是0,则表示正数,最大正数0x7fffffff的最高位是0,其余后面31位都是1,因此,sum > 0x7fffffff永远不可能成立,判断结果始终为false,同理,最小负数0x80000000,最高位是1,其他31位是0,转成10进制就是-2^31,sum < 0x80000000永远不成立,判断结果始终为false。因此,在上述错误二的判断溢出代码中,判断结果始终为false,相当于if中的代码块始终没有执行,这种错误的判断逻辑肯定得不到正确的结果。
错误三:溢出判断错误2
/** * Created by ray on 2018/2/11. */ public class Test { public static void main(String[] args) { Solution solution = new Solution(); long value1 = 0x7fffffffL + 1; long value2 = -0x80000000L;//-不可少,0x80000000在long型中表示正数而不是负数 String str1 = String.valueOf(value1); String str2 = String.valueOf(value2); int result1 = solution.StrToInt(str1); int result2 = solution.StrToInt(str2); System.out.println("0x7fffffffL + 1转换结果:" + result1); System.out.println("-0x80000000L转换结果:" + result2); } } class Solution { public int StrToInt(String str) { if (str == null || str.trim().length() == 0)//空字符串情况 return 0; char[] a = str.trim().toCharArray(); int i = 0; char fuhao;//+-符号位 if (a[0] == '-') { fuhao = '-'; i = 1;//第一位如果是-号,则从第二位开始循环 } else if (a[0] == '+') { fuhao = '+';//第一位是+正号,则从第二位开始循环 i = 1; } else if (a[i] >= 48 && a[i] <= 57) { fuhao = '+'; i = 0;//第一位是0-9的数字,则从第一位开始循环 } else { return 0;//字符串非法 } int sum = 0; for (; i < a.length; i++) { if (a[i] < 48 || a[i] > 57) return 0;//有非数字字符 //在对sum计算之前先判断此次计算是否会导致溢出 //实际上就是用边界值Integer.MAX_VALUE对此次计算做一个逆运算来比较 if (sum > (Integer.MAX_VALUE - (a[i] - 48)) / 10){ return 0; } sum = sum * 10 + a[i] - 48; //判断是否溢出,正整数最大0X7FFF FFFF,最小负整数0X8000 0000 //可将0x7fffffff用Integer.MAX_VALUE,0x80000000用Integer.MIN_VALUE替代 // if((fuhao == '+' && sum > 0X7fffffff) || (fuhao == '-' && -sum < 0X80000000)){ // return 0; // } } return fuhao == '+' ? sum : -sum; } }
测试结果:
一看,0x7fffffffL+1得到了正确判断,返回了结果0表示非法字符串,但是-0x80000000L明明没有溢出,为什么也返回了0,是不是很奇怪。
其实是因为这种判断逻辑存在一个问题。在错误1,2的字符串转换过程中,均是先计算一个正数sum值,然后根据符号是‘+’或是‘-’返回sum或者sum的相反数。然后,在有符号数中,最大正数与最小负数不是互为相反数的,比如,short型,最大为32767,最小为-32768,int型,最大为2147483647,最小为-2147483648,可以看出,最小负数的相反数比最大整数大1。实际上,对于n位的有符号数来说,表示范围为[-2^n - 1,2^n]。要想转换-0x80000000,上述代码的思路是先计算该值的绝对值,然后根据符号,返回结果。而-0x80000000的绝对值是大于最大整型值的,也就是会导致溢出,所以这种方法也是不正确的。然而,在牛客网该题的测试用例中,并没有包含最小负数这个测试用例,以至于很多人使用这种判断逻辑,也成功AC了。
从上面的代码,我们可以看出,不能通过先求绝对值,再取相反数的方式来转换负数。那我们换一种思路,既然有符号数的最小负数的绝对值大于最大正数的绝对值,那我们可不可以先求负的绝对值,再根据符号转成相应的正确值呢?例如,123,我们可以先求-123,然后判断符号,返回123,这样就可以避免最小负数转换失败的情况了。下面上完整版的代码。
正确解答:
/** * Created by ray on 2018/2/11. */ public class Test { public static void main(String[] args) { Solution solution = new Solution(); long value1 = 0x7fffffffL; long value2 = -0x80000000L; long value3 = 0x7fffffffL + 1; long value4 = -0x80000000L - 1; String str1 = String.valueOf(value1); String str2 = String.valueOf(value2); String str3 = String.valueOf(value3); String str4 = String.valueOf(value4); int result1 = solution.StrToInt(str1); int result2 = solution.StrToInt(str2); int result3 = solution.StrToInt(str3); int result4 = solution.StrToInt(str4); System.out.println("0x7fffffffL转换结果:" + result1); System.out.println("-0x80000000L转换结果:" + result2); System.out.println("0x7fffffffL + 1转换结果:" + result3); System.out.println("-0x80000000L - 1转换结果:" + result4); } } class Solution { public int StrToInt(String str) { if (str == null || str.trim().length() == 0)//空字符串情况 return 0; char[] a = str.trim().toCharArray(); int i = 0; int sum = 0; int len = a.length; //limit是sum的边界值 int limit = -Integer.MAX_VALUE; //multmin是倍乘前的边界值 int multmin; char fuhao;//+-符号位 if (a[0] == '-') { if (len == 1) return 0;//单独的'-'非法 fuhao = '-'; limit = Integer.MIN_VALUE; i = 1;//第一位如果是-号,则从第二位开始循环 } else if (a[0] == '+') { if (len == 1) return 0;//单独的'+'非法 fuhao = '+';//第一位是+正号,则从第二位开始循环 i = 1; } else if (a[i] >= 48 && a[i] <= 57) { fuhao = '+'; i = 0;//第一位是0-9的数字,则从第一位开始循环 } else { return 0;//字符串非法 } //因为字符串是10进制,所以除以10,进制为几,就除以几 multmin = limit / 10; for (; i < a.length; i++) { if (a[i] < 48 || a[i] > 57) return 0;//有非数字字符 //************************* //下面是核心判断代码 int digit = a[i] - 48; //在倍乘10前先判断 if (sum < multmin) return 0; sum *= 10; //在加之前先判断 //注意!sum < limit +digit不能写成sum - digit < limit //因为sum - digit就有可能导致溢出了 if (sum < limit + digit) return 0; sum -= digit; //************************ } //是正数就返回负数的相反数,是负数就直接返回 return fuhao == '+' ? -sum : sum; } }
测试结果如下:
注释已经写的很清楚了,如果还没有理解的话,可以画一个数轴来帮助理解。这个代码还对单独的“+”’或者“-”做了判断,可以满足更加严格的测试用例,鲁棒性更好,实际上,这也是JDK中Integer.parseInt()方法所采用的思路。
相关文章推荐
- 判断java String中是否有汉字的方法
- JAVA中判断字符是否为中文的方法
- java中判断字符串是否为数字的三种方法
- java中判断字符串是否为数字的方法
- 从效率考虑判断 Java 中的空字符串方法
- java中判断字符串是否为数字的四种方法
- java中判断字符串是否为数字的两种方法
- Java下判断全角空格、缩进、改行等的方法
- Java判断字符串是否为空的三种方法
- Java网络编程从入门到精通 (9):使用isXxx方法判断地址类型
- java中判断字符串是否为数字的三种方法
- java中判断字符串是否为数字的三种方法
- 利用java判断文件的编码方法
- Java判断字符串是否为空的三种方法
- 几种判断字符集编码的方法(Java) .未完
- java中判断字符串是否为数字的三种方法
- java中判断字符串是否为数字的三种方法
- Java网络编程从入门到精通 (9):使用isXxx方法判断地址类型
- java中判断字符串是否为数字的三种方法
- 高效回文判断方法(java)