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);
}