php base_convert函数原型:
string base_convert ( string $number , int $frombase , int $tobase ) base_convert — 在任意进制之间转换数字 返回一字符串,包含 number 以 tobase 进制的表示。number 本身的进制由 frombase 指定。frombase 和 tobase 都只能在 2 和 36 之间(包括 2 和 36)。高于十进制的数字用字母 a-z 表示,例如 a 表示 10,b 表示 11 以及 z 表示 35。
内核源码如下:
1 /* { { { proto string base_convert(string number, int frombase, int tobase) 2 Converts a number in a string from any base <= 36 to any base <= 36 */ 3 PHP_FUNCTION(base_convert) 4 { 5 zval **number, temp; 6 long frombase, tobase; 7 char *result; 8 9 if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "Zll", &number, &frombase, &tobase) == FAILURE) {10 return;11 }12 convert_to_string_ex(number);13 14 if (frombase < 2 || frombase > 36) {15 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid `from base' (%ld)", frombase);16 RETURN_FALSE;17 }18 if (tobase < 2 || tobase > 36) {19 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid `to base' (%ld)", tobase);20 RETURN_FALSE;21 }22 23 if(_php_math_basetozval(*number, frombase, &temp) == FAILURE) {24 RETURN_FALSE;25 }26 result = _php_math_zvaltobase(&temp, tobase TSRMLS_CC);27 RETVAL_STRING(result, 0);28 }
PHP_FUNCTION(base_convert)首先对输入的参数进行了存储及校验,将\$number以string的形式存储于zval中,并校验\$frombase和\$tobase是否在2和36之间,不在则报错返回。之后调用\_php\_math_basetozval()函数根据frombase将存储于zval中的string转换成对应的数值,代码如下:
1 /* }}} */ 2 3 /* { { { _php_math_basetozval */ 4 /* 5 * Convert a string representation of a base(2-36) number to a zval. 6 */ 7 PHPAPI int _php_math_basetozval(zval *arg, int base, zval *ret) 8 { 9 long num = 0;10 double fnum = 0;11 int i;12 int mode = 0;13 char c, *s;14 long cutoff;15 int cutlim;16 17 if (Z_TYPE_P(arg) != IS_STRING || base < 2 || base > 36) {18 return FAILURE;19 }20 21 s = Z_STRVAL_P(arg);22 23 cutoff = LONG_MAX / base;24 cutlim = LONG_MAX % base;25 26 for (i = Z_STRLEN_P(arg); i > 0; i--) {27 c = *s++;28 29 /* might not work for EBCDIC */30 if (c >= '0' && c <= '9')31 c -= '0';32 else if (c >= 'A' && c <= 'Z')33 c -= 'A' - 10;34 else if (c >= 'a' && c <= 'z')35 c -= 'a' - 10;36 else37 continue;38 39 if (c >= base)40 continue;41 42 if (c >= base)43 continue;44 45 switch (mode) {46 case 0: /* Integer */47 if (num < cutoff || (num == cutoff && c <= cutlim)) {48 num = num * base + c;49 break;50 } else {51 fnum = num;52 mode = 1;53 }54 /* fall-through */55 case 1: /* Float */56 fnum = fnum * base + c;57 }58 }59 60 if (mode == 1) {61 ZVAL_DOUBLE(ret, fnum);62 } else {63 ZVAL_LONG(ret, num);64 }65 return SUCCESS;66 }
由上面的代码可以看到,\_php_math_basetozval()根据输入字符串的字面值大小,计算出对应数值并保存在num或fnum中(当数值能被long存储时存在num中,大于long的最大值时,则存储于fnum中,fnum是double类型),然后保存在zval中返回。这里就又一个小bug,该函数在基于from_base将字符串转换为数值时,如果遇到不是0-9或者a-z或者A-Z的字符时,是跳过该字符,并继续处理下一个字符(36行-37行代码),这个地方就隐含着bug了。即base_convert('122348738947.653',10,8)和echo base_convert('122348738947653',10,8)的输出相同。另外,如果$number中的字符超过了from_base时,也是跳过该字符不做处理(39行-40行代码)。
通过_php_math_basetozval()我们已经确定了$number所代表的数值,接着调用\_php\_math\_zvaltobase()将数值转换为以$to_base为基数的数值字符串,
1 /* { { { _php_math_zvaltobase */ 2 /* 3 * Convert a zval to a string containing a base(2-36) representation of 4 * the number. 5 */ 6 PHPAPI char * _php_math_zvaltobase(zval *arg, int base TSRMLS_DC) 7 { 8 static char digits[] = "0123456789abcdefghijklmnopqrstuvwxyz"; 9 10 if ((Z_TYPE_P(arg) != IS_LONG && Z_TYPE_P(arg) != IS_DOUBLE) || base < 2 || base > 36) {11 return STR_EMPTY_ALLOC();12 }13 14 if (Z_TYPE_P(arg) == IS_DOUBLE) {15 double fvalue = floor(Z_DVAL_P(arg)); /* floor it just in case */16 char *ptr, *end;17 char buf[(sizeof(double) << 3) + 1];18 19 /* Don't try to convert +/- infinity */20 if (fvalue == HUGE_VAL || fvalue == -HUGE_VAL) {21 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Number too large");22 return STR_EMPTY_ALLOC();23 }24 25 end = ptr = buf + sizeof(buf) - 1;26 *ptr = '\0';27 28 do {29 *--ptr = digits[(int) fmod(fvalue, base)];30 fvalue /= base;31 } while (ptr > buf && fabs(fvalue) >= 1);32 33 return estrndup(ptr, end - ptr);34 }35 36 return _php_math_longtobase(arg, base);37 }
这部分代码的主体部分是用来处理用double的数值,下面的函数_php_math_longtobase()则是用来处理long存储的数字。
1 /* { { { _php_math_longtobase */ 2 /* 3 * Convert a long to a string containing a base(2-36) representation of 4 * the number. 5 */ 6 PHPAPI char * _php_math_longtobase(zval *arg, int base) 7 { 8 static char digits[] = "0123456789abcdefghijklmnopqrstuvwxyz"; 9 char buf[(sizeof(unsigned long) << 3) + 1];10 char *ptr, *end;11 unsigned long value;12 13 if (Z_TYPE_P(arg) != IS_LONG || base < 2 || base > 36) {14 return STR_EMPTY_ALLOC();15 }16 17 value = Z_LVAL_P(arg);18 19 end = ptr = buf + sizeof(buf) - 1;20 *ptr = '\0';21 22 do {23 *--ptr = digits[value % base];24 value /= base;25 } while (ptr > buf && value);26 27 return estrndup(ptr, end - ptr);28 }
bug演示:
1 php > echo base_convert('122348738947.653',10,8); 2 3364321107725105 3 php > echo base_convert('122348738947',10,8); 4 1617443314603 5 php > echo base_convert('122348738947653',10,8); 6 3364321107725105 7 php > echo base_convert('13526~~009',10,10); 8 13526009 9 php > echo base_convert('13526bb009',10,10);10 1352600911 php >