PHP源码分析-进制转换函数分析

PHP源码分析-进制转换函数分析

Php的进制转换大概可以分为两个类

1.其他进制转换十进制
bindec 二进制转换十进制
hexdec 十六进制转换十进制
octdec 八进制转换十进制
base_convert在任意进制之间转换数字

2.十进制转换其他进制
decbin 十进制转换为二进制
dechex 十进制转换为十六进制
decoct 十进制转换为八进制
base_convert在任意进制之间转换数字

先来看看其他进制转换十进制以2进制为例
先简单说明计算方法.
例如:10101转换为10进制
计算方法1*2^4+0*2^3+1*2^2+0*2^1+1*2^0
这是我们常规的计算方法,但是需要注意的是cpu中并没有直接计算乘法的指令,而且数字的大小是左大右小和计算机的接收顺序相反,所以需要采用更适合计算机的计算方法
上边一共进行了5+4+3+2+1=15次乘法4次加法
如果写成如下形式
(((1*2+0)*2+1)*2+0)*2+1
则只需要进行4次乘法4次加法就可以完成上述操作大大的加快了计算机的运行速度

php中正是这样做的:

PHP_FUNCTION(bindec)
{
	zval *arg;

	ZEND_PARSE_PARAMETERS_START(1, 1)
		Z_PARAM_ZVAL(arg)
	ZEND_PARSE_PARAMETERS_END();

	convert_to_string_ex(arg);
	//其他可以忽略主要是_php_math_basetozval函数
	if (_php_math_basetozval(arg, 2, return_value) == FAILURE) {
		RETURN_FALSE;
	}
}

PHPAPI int _php_math_basetozval(zval *arg, int base, zval *ret)
{
	zend_long num = 0;
	double fnum = 0;
	zend_long i;
	int mode = 0;
	char c, *s;
	zend_long cutoff;
	int cutlim;

	if (Z_TYPE_P(arg) != IS_STRING || base < 2 || base > 36) {
		return FAILURE;
	}

	s = Z_STRVAL_P(arg);

	cutoff = ZEND_LONG_MAX / base;
	cutlim = ZEND_LONG_MAX % base;

	for (i = Z_STRLEN_P(arg); i > 0; i--) {
		c = *s++;

		/* might not work for EBCDIC */
		//这几个if&else是算出当前字符所代表的字面量
		//例如 '1' -' 0'结果为1, 'B' –' A'+10结果为11,
		if (c >= '0' && c <= '9')
			c -= '0';
		else if (c >= 'A' && c <= 'Z')
			c -= 'A' - 10;
		else if (c >= 'a' && c <= 'z')
			c -= 'a' - 10;
		else
			continue;

		if (c >= base)
			continue;

		switch (mode) {
		case 0: /* Integer */
			if (num < cutoff || (num == cutoff && c <= cutlim)) {
				//核心为这一块计算方法就是上边提到的方法
				num = num * base + c;
				break;
			} else {
				fnum = (double)num;
				mode = 1;
			}
			/* fall-through */
		case 1: /* Float */
			fnum = fnum * base + c;
		}
	}

	if (mode == 1) {
		ZVAL_DOUBLE(ret, fnum);
	} else {
		ZVAL_LONG(ret, num);
	}
	return SUCCESS;
}

再看下十进制转换其他进制的方法,还以二进制为例,
这个就比较简单了不做过多的解释了,就是常规的计算方法辗转相除

PHP_FUNCTION(decbin)
{
	zval *arg;
	zend_string *result;

	ZEND_PARSE_PARAMETERS_START(1, 1)
		Z_PARAM_ZVAL(arg)
	ZEND_PARSE_PARAMETERS_END();

	convert_to_long_ex(arg);
	result = _php_math_longtobase(arg, 2);
	RETURN_STR(result);
}

PHPAPI zend_string * _php_math_longtobase(zval *arg, int base)
{
	static char digits[] = "0123456789abcdefghijklmnopqrstuvwxyz";
	char buf[(sizeof(zend_ulong) << 3) + 1];
	char *ptr, *end;
	zend_ulong value;

	if (Z_TYPE_P(arg) != IS_LONG || base < 2 || base > 36) {
		return ZSTR_EMPTY_ALLOC();
	}

	value = Z_LVAL_P(arg);

	end = ptr = buf + sizeof(buf) - 1;
	*ptr = '\0';
	//核心是这里
	do {
		ZEND_ASSERT(ptr > buf);
		*--ptr = digits[value % base];
		value /= base;
	} while (value);

	return zend_string_init(ptr, end - ptr, 0);
}

至于为什么把base_convert放在两个地方是因为任意进制之间的转换并不是直接转换的而是通过十进制做中转的所以符合两边的方式,从函数名字也可以看出太随意了….php

PHP_FUNCTION(base_convert)
{
	zval *number, temp;
	zend_long frombase, tobase;
	zend_string *result;

	ZEND_PARSE_PARAMETERS_START(3, 3)
		Z_PARAM_ZVAL(number)
		Z_PARAM_LONG(frombase)
		Z_PARAM_LONG(tobase)
	ZEND_PARSE_PARAMETERS_END();
	convert_to_string_ex(number);

	if (frombase < 2 || frombase > 36) {
		php_error_docref(NULL, E_WARNING, "Invalid `from base' (" ZEND_LONG_FMT ")", frombase);
		RETURN_FALSE;
	}
	if (tobase < 2 || tobase > 36) {
		php_error_docref(NULL, E_WARNING, "Invalid `to base' (" ZEND_LONG_FMT ")", tobase);
		RETURN_FALSE;
	}
	//先转为10进制
	if(_php_math_basetozval(number, (int)frombase, &temp) == FAILURE) {
		RETURN_FALSE;
	}
	//再转为其他进制,典型的数学中的复用思想
	result = _php_math_zvaltobase(&temp, (int)tobase);
	RETVAL_STR(result);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值