详解ip2long和long2ip回答为什么PHP中的ip2long转化为负数

本文介绍了如何在PHP中将IPv4地址转换为整型数值,以节省存储空间,并探讨了转换过程中可能出现的负数问题及其解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 原文地址:

http://3shanmen.com/index.php/index/article/articledetail/article_id/3

  在PHP中,我们会经常遇到使用客户端的IP地址的操作,而如果需要大量的存取IP地址的时候,直接将IP地址以字符串存储到数据库中将占用不小的存储空间,而将IP地址以long整型进行存储将节约不少空间。

        如何将IP地址转换成整型呢?PHP自带函数ip2long()可以解决这一问题,但是我们在使用的时候往往会遇到转换的结果为负数的情况,虽然这并不影响我们通过long2ip()函数再转回来的操作,但是我们要考虑数据库字段类型的设计(不能为unsigned)。本着知其然知其所以然的精神,我们对其转换原理进行深入的了解。

        首先,我们知道,IP地址的组成形式是xxx.xxx.xxx.xxx的形式(IPv4),总共四段,且每一段都有8个bit位,而8个二进制位能表示多少个数字呢,2^8(256)个,而数字从0开始算起,所以最大数字为255,故IP地址的最大为255.255.255.255,最小为0.0.0.0。如果此时让我们对这样的一段IP地址进行转化,基本思路应该如下:

        C语言描述:

#include <stdio.h>

int main(int argc, char** argv)
{
        //假设,IP地址为154.23.56.90
        //定义无符号整型ip_long,将每个字段中的十进制分别执行逻辑左移到相应的位置,最后进行位运算合并成一个数字。
	unsigned int ip_long = (157 << 24) | (23 << 16) | (56 << 8) | 90;
	printf("%u\n", ip_long);
	printf("%d\n", ip_long);

	return 0;
}

输入结果:

2635544666
-1659422630

我们知道,在二进制中,最高位代表着符号位,0位正数,1位负数。可以看到,即使ip_long声明为无符号整型,在输出时也需要指明%u来格式化输出为无符号整型。这是因为157大于127(二进制为01111111),也就是说如果157(8位)用二进制来表示,最高位必然是1。当将157放在一个4字节整型的高8位时,导致这个4字节整型的最高位为1。虽然ip_long定义为无符号整型,但printf函数并不知道,因此需要指明无符号格式化字符。如果最高位为0,则使用%d就可以了。也就是说,如果把一个四字节的高八位换成小于等于127的数,其结果将不会出现负数,比如将上例中的IP地址换成126.23.56.90

        在PHP中,数据类型一般都是有符号位的,且PHP中提供了类C的sprintf()方法,因为我们可以很容易的解决负数的问题。如下:

<?php
echo sprintf("%u\n", ip2long("157.23.56.90"));

  有了上面的介绍,我们该如何实现long2ip()呢? 对于想通过移位来自己实现的同学来说,可能没有那么简单。因为PHP的>>运算符是算术右移运算符,所以如果最高位是1的话,右移的结果是在高位补1,这跟结果不符。但是我们可以用另一种思路去解决:保存最高位(符号位),然后将最高位置0,之后再将高8位的最高位置1(这取决于之前保存的符号位)。代码实现如下:

注:右移有逻辑右移和算术右移,无符号位的逻辑右移与算术右移的结果是一致的,如果最高位为1,那么逻辑右移依旧从左边补0,但是算术右移此时将从左边补1,导致右移之后的结果不一样。

<?php
$ip_long = 2636541643;
// 保存最高位(符号位)
$msb = 0;
/*
下面将一个十进制与一个十六进制进行位且运算,而十六进制转化为二进制共有(4*8)32位除了最高位为1,其余都为0
而运算的结果是一十进制的数0或者一个整数,0代表ip_long最高位为0,整数代表为1
当我们想要知道,某一位二进制的值时,用一个某位为1其余都为0的数与其进行位且运算
*/
if ($ip_long & 0x80000000) 
{
	$msb = 1;
}

// 将最高位(符号位)置0变成无符号数
$uip_long = $ip_long & 0x7fffffff;//想将某个位改为0,让该位为0其余为1与其进行位且运算
$ip1 = $uip_long >> 24;
if ($msb == 1)
{
	$ip1 |= 0x80;//如果原来为有符号位,此时再改回来。想将某个位改为1,让该位为1其余为0的数与其进行位或运算
}

$ip2 = ($uip_long >> 16) & 0xff; // 跟0xff做与运算的目的是取低8位
$ip3 = ($uip_long >> 8) & 0xff;
$ip4 = $uip_long & 0xff;
echo $ip1 . '.' . $ip2 . '.' . $ip3 . '.' . $ip4 . "\n";

        如上的代码可能会有移植性的问题,不推荐使用,此处只是用于帮助我们更好的了解ip2long和long2ip

原文地址:

http://3shanmen.com/index.php/index/article/articledetail/article_id/3

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值