Java 中的这个绝对值有点不绝对啊!
现象
假如有如下代码定义了一个方法 test()
,它入参可以任何一个 int 类型的整数,那么它输出结果可能是什么?
public class Test { public static void test(int a) { System.out.println("The result of absolute value compare to zero is:" + (Math.abs(a) >= 0)); } }
如果你的结论是 true,那么恭喜你,你掉入到绝对值不绝对 的坑里面了。这个方法输出的结果有可能是 true,但是也有可能是 false。比如下面的调用代码将分别输出 true,true,true , false,如下图所示:
public static void main(String[] args) { test(1); test(-1); test(Integer.MAX_VALUE); test(Integer.MIN_VALUE); }
在 Java 中,通过 Math.abs()
函数返回的值有的时候并不是这个数的绝对值。如下面的代码所示:
public static void main(String[] args) { System.out.println(Math.abs(Integer.MIN_VALUE)); }
上面的代码输出的结果并不是 Integer.MIN_VALUE
的绝对值,输出的结果是它自己,如下图所示:
从输出可以看到因为 Math.abs(Integer.MIN_VALUE)
的结果还是 Integer.MIN_VALUE
,因此它是小于 0 的,这个也解释了上面判断大于等于 0 结果有可能输出的是 false。
为什么 Math.abs(Integer.MIN_VALUE)
的结果还是 Integer.MIN_VALUE
而不是它的绝对值呢?
原理
从 int
类型可以表示的数的范围解释是比较好理解的。以 int
类型为例,它能够表示的范围是 2^31 到 2^31 - 1。即 -2147483648 到 2147483647,可以看到最小的负数是 -2147483648 。它的绝对值实际上应该是 2147483648,但是这个值已经超过了 int
类型能够表示的最大的数 2147483647 了。如果返回 2147483648,它是不能在一个 int
类型的数中表示的。如果我们直接把 2147483648 这个数赋值给一个 int
类型的变量,编译器也会提示 Integer number too large
,如下图所示:
因此这里 Math.abs()
函数返回的结果并不能是 214748364,因为 int
类型根本表示不了这个数。
那 Math.abs()
方法做了什么操作呢?查看 Math.abs()
方法的源码,实现逻辑如下:
在方法中就是判断了一下这个数是否小于 0,如果小于 0 的话,就返回对这个数取反后的值。那这个取反操作具体做了什么事情呢?为什么对 Integer.MIN_VALUE
进行了求反操作返回的还是它自己?
要回答这些问题,那就得知道计算机底层是表示一个整数的方式以及 int
类型表示的数的范围是 2^31 到 2^31 - 1 的原因。
Java语言规范中对此做了描述,规范中说到 Java 语言中使用 「two's-complement representation 」 来表示整数,因为 「two's-complement representation 」 的值不是对称的,所以对 int
或者 long
类型的最小值的取反的结果还是它们自身,在这个场景是有「溢出」发生的。而对一个整数的取反操作相当于把它的所有比特位取反,然后再加上 1。如下图所示:
规范这里的提到的 「two's-complement 」 就是我们常常说的「补码」,学过计算机组成原理相关课程的应该对这个词语比较耳熟。
补码就是将二进制位的最高位作为符号位,它的权重是 -2^(w -1) (这里的 w 为比特位的个数) ,如果它设置为 1 表示负数,如果设置为 0,表示非负数。如下图所示:
根据补码的定义来看,补码能够表示的最大的数是 2^(w -1) - 1,而它能够表示的最小的的数是 -2^(w -1) (这里的 w 为比特位的个数)。那么最小数的绝对值是比最大数的绝对值还要大 1 的。从上面的图也可以看出(上图中的比特位数为 4),数轴最左侧的刻度是 -8,而数轴最右侧的刻度是 7。
对于补码的取反操作是把每个比特位都取反,然后加上 1。为什么补码的取反要这样操作?从数学的角度上讲一个数 x 加上它的取反 -x 的结果应该是 0。从计算机的角度我们可以知道 x 加上 x 的每个比特位取反的结果是每个比特位都是 1,按照补码的表示方式就是 10 进制的 -1,然后再加上 1 那就是 0,这样的结果就和数学上是相符合的了。比如假设总的比特位数是 8,1 的补码是 0000_0001,取反之后就是 1111_1110,相加的结果是 1111_1111,即 -1,然后加上 1 就是 0000_0000,即 0。如下图所示:
补码的英文名字「 two's complement 」 这个名字的由来是如果把一个数的补码和它取反的补码得到的二进制都看作是无符号数的话,它相加的结果就是 2^w (这里的 w 为比特位的个数)。如下图所示:
回过头来看 Java 中的 int
类型,它的最小值 Integer.MIN_VALUE
的补码表示形式就是 1000_0000_0000_0000_0000_0000_0000_0000,按照补码取反操作的规则,应该是把它的补码按位取反得到 0111_1111_1111_1111_1111_1111_1111_1111,然后加 1,得到的结果还是 1000_0000_0000_0000_0000_0000_0000_0000,即它自己。因此在 Math.abs()
函数中对 Integer.MIN_VALUE
取反后得到的值仍然是 Integer.MIN_VALUE
。
解决方法
对于 Integer.MIN_VALUE
的绝对值溢出现象的解决方法有以下几种:
一种是使用 Math.absExact()
方法,该方法在获取绝对值之前会判断是否超过了表示范围,如果超过了表示范围会抛出一个异常,如下图所示:
public class Test { public static void main(String[] args) { System.out.println(Math.absExact(Integer.MIN_VALUE)); } }
实现原理其实就是在进入方法时判断了一下是否是 Integer.MIN_VALUE
,如果是就直接抛出异常了,如下图所示:
也可以转为 long
类型后再获取绝对值,因为 Integer.MIN_VALUE
的绝对值是可以用 long
类型来表示的,因此转为 long
类型来获取绝对值也是可以的,但是这个方法就解决不了 Long.MIN_VALUE
绝对值溢出现象。如下图所示:
public static void main(String[] args) { int a = Integer.MIN_VALUE; System.out.println(Math.abs((long) a) >= 0); }
还有一种可以使用 Integer.MIN_VALUE
构造一个 BigInteger
对象,然后通过获取这个对象的绝对值来和 BigDecimal.ZERO
来比较,这种方式不仅可以解决 Integer.MIN_VALUE
的绝对值溢出问题,还可以解决 Long.MIN_VALUE
的绝对值溢出问题。如下面的代码:
public static void main(String[] args) { BigInteger minInt = BigInteger.valueOf(Integer.MIN_VALUE); System.out.println(minInt.abs().compareTo(BigInteger.ZERO) >= 0); BigInteger minLong = BigInteger.valueOf(Long.MIN_VALUE); System.out.println(minLong.abs().compareTo(BigInteger.ZERO) >= 0); }
参考

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
《鸿蒙HarmonyOS应用开发从入门到精通(第2版)》学习笔记——HarmonyOS产生的背景
1.1 HarmonyOS产生的背景 2024年4月17日,第21届华为分析师大会在深圳开幕。华为副董事长、轮值董事长徐直军在大会上透露,华为希望通过2024年一年的时间,先在中国市场把智能手机上使用超过99%时间的5000个应用全面迁移到鸿蒙原生操作系统上,真正实现操作系统和应用生态的统一,“当把这5000个应用以及其它成千上万的应用都从安卓生态迁移到鸿蒙操作系统上时,我们的鸿蒙操作系统就真正完成了打造,并真正成为除了苹果iOS和谷歌安卓外的全球第三个移动操作系统。” 那么到底什么是HarmonyOS?为什么需要HarmonyOS? 1.1.1 为什么需要HarmonyOS 2019年5月15日,美国商务部宣布,把华为及70家关联企业列入其所谓的“实体清单”(Entities List)。这意味着,今后如果没有美国政府的批准,华为将无法向美国企业购买元器件和购买技术。“实体清单”是美国为维护其国家安全利益而设立的出口管制条例。在未得到许可证前,美国各出口商不得帮助这些名单上的企业获取受本条例管辖的任何物项。简单地说,“实体清单”就是一份“黑名单”,一旦进入此榜单实际上是剥夺了相关企业...
- 下一篇
启用声明式 DNS 只需一个 POST
原文作者:Eric Chen - 云解决方案架构师 原文链接:启用声明式 DNS 只需一个 POST 转载来源:NGINX 中文官网 NGINX 唯一中文官方社区 ,尽在nginx.org.cn 在 1990 年的电影《猎杀红色十月号》中,由 Sean Connery 饰演的苏联新型核潜艇舰长 Marko Ramius 打算指挥潜艇叛逃到美国。由年轻的 Alec Baldwin 饰演的中央情报局 (CIA) 分析师 Jack Ryan 凭直觉判断出了 Ramius 的动机,他必须说服美国海军相信他的推断,以防止超级大国之间爆发暴力冲突。为了证明自己的判断,Ryan 首先需要与 Ramius 对话。随着他花费过多的时间寻找 Ramius,悬念也在不断累积。 从表面上看,Ryan 的搜索如同浏览器客户端尝试在互联网上查找一个网站,只不过这些网站在域名系统 (DNS) 中注册了其位置,因此客户端可以不费吹灰之力就找到它们。如果 Ramius 公布了他的位置,这部电影就不会那么惊心动魄了,但在我们的应用上,我们还是希望能够快速、轻松地解决问题。 在本文中,我们使用了 F5 的两项 DNS 技...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- 2048小游戏-低调大师作品
- Windows10,CentOS7,CentOS8安装MongoDB4.0.16
- SpringBoot2全家桶,快速入门学习开发网站教程
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- Eclipse初始化配置,告别卡顿、闪退、编译时间过长
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- SpringBoot2更换Tomcat为Jetty,小型站点的福音
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- CentOS7,8上快速安装Gitea,搭建Git服务器
- CentOS6,CentOS7官方镜像安装Oracle11G