自从从学校出来当了 Java 码农后,离这些经典又重要的计算机基础知识越来越远了。。。现在的情况是,连原码,反码,补码都傻傻分不清了!
最近在对 PHP 代码使用 Java 重构时,遇到了这么个问题:之前的 PHP 程序员在存储客户端 IPv4 地址时,是将获取到的点分十进制 IPv4 地址转换成了相应的整数存入数据库的,这么做的好处嘛就是节省空间,方便索引。“将点分十进制的 IPv4 地址转换成整数”,这在 PHP 中很简单,原生 API 支持,API 文档在此!但对于 Java 程序员来说就不是这么简单的了,因为 Java 并没有提供原生 API 的支持!
那么,现在只能考虑自己使用 Java 实现了:
首先,需要弄清楚 PHP 中 ip2long($ip)
的实现原理,经过参考某位仁兄的写的博客PHP: 详解ip2long和long2ip,基本上就了解其原理了。原理其实很简单:就是把点分十进制的 IPv4 地址字符串,按照其二进制形式放入一个四字节的整型变量中即可!上面那位仁兄博客里有用 C 语言实现了下 PHP 方法 ip2long()
,直接使用逻辑左移即可,但输出的一正一负的结果却引起了我的兴趣(因为不理解嘛)。于是,我在例子上又加了一行:
#include <stdio.h>
int main(int argc, char** argv){
unsigned int ip_long = (157 << 24) | (23 << 16) | (56 << 8) | 90;
printf("%x
", ip_long);
printf("%u
", ip_long);
printf("%d
", ip_long);
return 0;
}
即输出整形变量 ip_long
的十六进制形式,运行结果如下:
9d17385a
2635544666
-1659422630
那么,该怎么解释着三行输出呢?此时便想起了原码,反码,补码的转换规则:
- 正数的原码,反码,补码都一样;
- 负数原码符号位不变,数值位按位取反的结果就是反码;
- 负数的反码加一便是其补码;
- 计算机中存储的数字的二进制形式是该数字的补码形式;
也就是说:数字 A 在计算机中存储的二进制形式是:数字 A 转换为二进制形式 B,此时 B 就是数字 A 的原码,再将 B 转换为反码 C,最后将反码 C 转换为补码 D,补码 D 就是数字 A 最终在计算机中的二进制形式。验证下这个说法,十六进制数 9d17385a
转换为二进制形式是:10011101000101110011100001011010
。
- 将变量
ip_long
解释为无符号整数,则ip_long
即为正整数,则其原码,反码,补码都一样,所以推导出的ip_long
的原码就是:10011101000101110011100001011010
,转换为十进制即:2635544666
,验证正确; - 将变量
ip_long
解释为有符号整数,则从补码10011101000101110011100001011010
的第一位即符号位为 1 可知ip_long
是一个负数,再把补码10011101000101110011100001011010
转换为其原码可得到原码:11100010111010001100011110100110
,先不算符号位的 1,可得此数的绝对值的二进制是:1100010111010001100011110100110
,转换为十进制是:1659422630,因为它是负数,所以最终结果是:-1659422630,验证正确;