base_convert()函数探秘及小bug记录

php base_convert函数原型:

 string base_convert ( string $number , int $frombase , int $tobase )

base_convert — 在任意进制之间转换数字
返回一字符串,包含 numbertobase 进制的表示。number 本身的进制由 frombase 指定。frombasetobase 都只能在 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 }
View Code

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         else
37             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 }
View Code

由上面的代码可以看到,\_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 }
View Code

这部分代码的主体部分是用来处理用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 }
View Code

 

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 13526009
11 php >

 

转载于:https://www.cnblogs.com/jade640/p/7581350.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值