负数的绝对值居然还是负数,你知道吗?

开发 前端
“抽象泄漏”是软件开发时,本应隐藏实现细节的抽象化不可避免地暴露出底层细节与局限性。抽象泄露是棘手的问题,因为抽象化本来目的就是向用户隐藏不必要公开的细节。

发现问题

最近在处理一个线上问题时,遇到了非常奇怪的情况。

使用Math.abs(hash) % list.size()计算索引值时,竟然出现了负数!

看了下hash的值是-2147483648,恰好是Integer.MIN_VALUE。

按照绝对值的负负得正,结果应该是2147483648 % list.size(),结果是[0, list.size() - 1]。

但是结果是负的,这简直让人匪夷所思,难道Math.abs函数有问题吗?

本着技术人的思维,事出反常必有妖,咱们今天就来抓一下这个妖。

分析问题

直接找源码:

/**
 * Returns the absolute value of an {@code int} value.
 * If the argument is not negative, the argument is returned.
 * If the argument is negative, the negation of the argument is returned.
 *
 * <p>Note that if the argument is equal to the value of
 * {@link Integer#MIN_VALUE}, the most negative representable
 * {@code int} value, the result is that same value, which is
 * negative.
 *
 * @param   a   the argument whose absolute value is to be determined
 * @return  the absolute value of the argument.
 */
public static int abs(int a) {
    return (a < 0) ? -a : a;
}

代码没啥问题,数组小于0的时候,取反,大于等于0的时候,返回原值。

细心的已经开始看注释了。

官方注释解释了,如果参数是Integer.MIN_VALUE,返回的还是自身。现象和描述相符,但是和代码不符,按照代码运行应该是-Integer.MIN_VALUE,负负得正。

玄机在哪呢?

没错,就是你想的那样。

你答对了

越界了。

int类型占32位,数值范围是[-2147483648, 2147483647]。-Integer.MIN_VALUE是2147483648,比Integer.MAX_VALUE的2147483647大,所以越界了。

在计算机系统中,数值一律用补码来表示和存储。使用补码的主要优点是可以将符号位和数值位统一处理,同时加法和减法也可以统一处理。

补码系统中,正数的补码就是其本身,负数的补码是在其原码的基础上,符号位不变,其余各位取反,最后加1。

在补码表示法中,Integer.MIN_VALUE的二进制表示是10000000000000000000000000000000,取反后的结果是01111111111111111111111111111111,再加一变成10000000000000000000000000000000,这正好是Integer.MIN_VALUE本身。

因此,Math.abs(Integer.MIN_VALUE)的结果仍然是Integer.MIN_VALUE,即-2147483648。

解决问题

如何避免这个问题呢?两种方式:

  1. 扩展数值范围,在调用Math.abs方法是,把hash转为Long类型,这个时候就不会越界了;
  2. 判断hash值是否是Integer.MIN_VALUE,如果是,直接返回0。

总结问题

是不是有朋友会想,JDK中居然有这么低级错误的API。不过,这次的错误和JDK中居然也有反模式接口常量的不一样,这次是“抽象泄漏法则”的一种情况。

什么是抽象泄漏法则?

“抽象泄漏”是软件开发时,本应隐藏实现细节的抽象化不可避免地暴露出底层细节与局限性。抽象泄露是棘手的问题,因为抽象化本来目的就是向用户隐藏不必要公开的细节。

由于软件开发与运行环境越来越复杂,开发者必须依赖于各种抽象。使得开发者专注于高层次的领域相关的知识与技能,以提高工作效率。

但是,抽象泄漏法则指出“可靠”软件的开发者必须了解抽象之下的底层细节。否则一旦出了任何问题,根本不会知道是怎么回事,也不知道如何除错或回复。

程序设计工具抽象掉某些东西,但和其他所有抽象机制一样都有漏洞,而唯一能适当处理漏洞的方法,就是弄懂该抽象原理以及所隐藏的东西。

所以抽象机制虽然节省了工作的时间,不过学习的时间是省不掉的。

比如本次的问题,Math.abs函数本身是一个简单的抽象,用于计算一个数的绝对值。然而,由于 Integer.MIN_VALUE和反码的特殊性质,Math.abs(Integer.MIN_VALUE)仍然返回负数,这暴露了底层整数表示的细节。

这种抽象的泄漏使得我们在使用Math.abs(hash) % list.size()计算索引值时遇到了意料之外的问题。为了了解其中的原因,我们得需要知道数值在计算机中的表示形式。

所以,工具虽然好用,碰到问题也得挠头。

少年,做好成为高级程序员的准备了吗?

责任编辑:武晓燕 来源: 看山的小屋
相关推荐

2022-04-11 08:20:36

编程辅助工具GitHubCopilot

2023-12-12 08:41:01

2022-09-29 15:32:58

云计算计算模式

2021-10-14 06:52:47

算法校验码结构

2024-09-18 07:00:00

消息队列中间件消息队列

2019-12-12 09:23:29

Hello World操作系统函数库

2022-03-10 08:25:27

JavaScrip变量作用域

2024-05-28 09:12:10

2024-04-07 00:00:00

ESlint命令变量

2023-12-20 08:23:53

NIO组件非阻塞

2024-04-30 09:02:48

2023-04-26 10:21:04

2021-10-28 16:19:37

物联网人工智能IoT

2024-06-20 08:06:30

2024-06-03 14:27:08

ThisAPIThat

2014-05-30 10:23:15

乐跑手环智能手环运动手环

2024-10-15 11:37:06

2020-10-08 18:58:46

条件变量开发线程

2024-02-05 12:08:07

线程方式管理

2016-11-22 15:43:13

机房制冷发展史
点赞
收藏

51CTO技术栈公众号