踩坑实录2

在做一道需要用一个 long 变量压缩存储两个 int 的题时最开始写了这样一段代码:

1
long s = (long) ((start[0] << 32) | start[1]); // start是一个int数组
提交出错,看了后续的代码逻辑总是找不到错误,试图跟踪了一下变量发现 s 并不是预想的值

这里出错的原因在于首先执行的是 (start[0] << 32),但由于是 int 的运算,实际上这个表达式的结果恒为 0!最终 long 变量 s 中实则只存储了 start[1]

应该先将 start[0] 强制转换为 long 类型,这样后续的类型也将自动转换为 long 类型间的运算:

1
long s = (long) start[0] << 32 | start[1];

这段代码首先执行的是 (long) start[0]


2024-03-14 update

在做某一道需要记忆化搜索的题目时,对于一个 long 压缩两个 int 又有了新的理解(又踩进了新的坑)

典型场景如下:

1
long key = (long) k1 << 32 | k2; // k1, k2 are int

其实对于key,我们期待的是,若将其看成是一个 64 位的二进制字符串,那么它的高 32 位完整保存 k1 的二进制串,它的低 32 位完整保存 k2 的二进制串。但是考虑 k2 为负数时,由于补码的符号扩展,其转变为 long 之后,它的高 32 位全部都是 1!再进行或运算后,将导致 key 的高 32 位全为 1,这样就丢失了 k1 的信息。因此,k2 不能为负数(k1 没这个要求)

当然如果题目中不涉及到频繁的 key 解码(即将 key 还原成 k1 和 k2),那么直接用 String key = k1 + " " + k2 也是可以的,此时对于 k1 和 k2 的正负性就都没有要求了


2024-04-14 update

今天又又又踩进 long 的坑了!

在做这周周赛的第三题时,二分搜索的上下界均为 long。考虑到题目的性质,其实二分的上界最坏情况下就是 k*mi,其中 mi 是数组元素的最小值。于是写出了这样的代码

1
2
long left = mi;
long right = mi * k;

不过某些数据出错了,并且比赛时看不到数据,改了半天还是不对,喜提 N 次罚时

灵光一闪把上界改成 Long.MAX_VALUE 就对了,当时百思不得其解

赛后看到错误数据,发现是一个很大的答案却返回了一个很小的数,此时我联想到溢出🤔。可我​不是开了 long 吗?慢着!mi*k

其实还是两个 int 在运算啊!又在这上面踩坑了。改成如下代码就对了,并且用时比用 Long.MAX_VALUE 短,这是可以预料的

1
2
long left = mi;
long right = (long)mi * k;

这再次提醒我,我以为的 long 也许不是 long...


踩坑实录2
https://balddemian.github.io/Happy-Debugging-2/
作者
Peiyang He
发布于
2024年3月6日
许可协议