无意间看到了php中关于加,减,乘,除 的计算方法
http://lxr.php.net/source/xref/PHP-5.6/Zend/zend_operators.h#596
static zend_always_inline int fast_add_function(zval *result, zval *op1, zval *op2 TSRMLS_DC) 597{ 598 if (EXPECTED(Z_TYPE_P(op1) == IS_LONG)) { 599 if (EXPECTED(Z_TYPE_P(op2) == IS_LONG)) { 600#if defined(__GNUC__) && defined(__i386__) 601 __asm__( 602 "movl (%1), %%eax\n\t" 603 "addl (%2), %%eax\n\t" 604 "jo 0f\n\t" 605 "movl %%eax, (%0)\n\t" 606 "movb %3, %c5(%0)\n\t" 607 "jmp 1f\n" 608 "0:\n\t" 609 "fildl (%1)\n\t" 610 "fildl (%2)\n\t" 611 "faddp %%st, %%st(1)\n\t" 612 "movb %4, %c5(%0)\n\t" 613 "fstpl (%0)\n" 614 "1:" 615 : 616 : "r"(&result->value), 617 "r"(&op1->value), 618 "r"(&op2->value), 619 "n"(IS_LONG), 620 "n"(IS_DOUBLE), 621 "n"(ZVAL_OFFSETOF_TYPE) 622 : "eax","cc"); 623#elif defined(__GNUC__) && defined(__x86_64__) 624 __asm__( 625 "movq (%1), %%rax\n\t" 626 "addq (%2), %%rax\n\t" 627 "jo 0f\n\t" 628 "movq %%rax, (%0)\n\t" 629 "movb %3, %c5(%0)\n\t" 630 "jmp 1f\n" 631 "0:\n\t" 632 "fildq (%1)\n\t" 633 "fildq (%2)\n\t" 634 "faddp %%st, %%st(1)\n\t" 635 "movb %4, %c5(%0)\n\t" 636 "fstpl (%0)\n" 637 "1:" 638 : 639 : "r"(&result->value), 640 "r"(&op1->value), 641 "r"(&op2->value), 642 "n"(IS_LONG), 643 "n"(IS_DOUBLE), 644 "n"(ZVAL_OFFSETOF_TYPE) 645 : "rax","cc"); 646#else 647 /* 648 * ‘result‘ may alias with op1 or op2, so we need to 649 * ensure that ‘result‘ is not updated until after we 650 * have read the values of op1 and op2. 651 */ 652 653 if (UNEXPECTED((Z_LVAL_P(op1) & LONG_SIGN_MASK) == (Z_LVAL_P(op2) & LONG_SIGN_MASK) 654 && (Z_LVAL_P(op1) & LONG_SIGN_MASK) != ((Z_LVAL_P(op1) + Z_LVAL_P(op2)) & LONG_SIGN_MASK))) { 655 Z_DVAL_P(result) = (double) Z_LVAL_P(op1) + (double) Z_LVAL_P(op2); 656 Z_TYPE_P(result) = IS_DOUBLE; 657 } else { 658 Z_LVAL_P(result) = Z_LVAL_P(op1) + Z_LVAL_P(op2); 659 Z_TYPE_P(result) = IS_LONG; 660 } 661#endif 662 return SUCCESS; 663 } else if (EXPECTED(Z_TYPE_P(op2) == IS_DOUBLE)) { 664 Z_DVAL_P(result) = ((double)Z_LVAL_P(op1)) + Z_DVAL_P(op2); 665 Z_TYPE_P(result) = IS_DOUBLE; 666 return SUCCESS; 667 } 668 } else if (EXPECTED(Z_TYPE_P(op1) == IS_DOUBLE)) { 669 if (EXPECTED(Z_TYPE_P(op2) == IS_DOUBLE)) { 670 Z_DVAL_P(result) = Z_DVAL_P(op1) + Z_DVAL_P(op2); 671 Z_TYPE_P(result) = IS_DOUBLE; 672 return SUCCESS; 673 } else if (EXPECTED(Z_TYPE_P(op2) == IS_LONG)) { 674 Z_DVAL_P(result) = Z_DVAL_P(op1) + ((double)Z_LVAL_P(op2)); 675 Z_TYPE_P(result) = IS_DOUBLE; 676 return SUCCESS; 677 } 678 } 679 return add_function(result, op1, op2 TSRMLS_CC); 680}
其中第653行中的宏 LONG_SIGN_MASK 定义为
#define LONG_SIGN_MASK (1L << (8*sizeof(long)-1))
在64位机下,LONG_SIGN_MASK的值为 1L<< (8*8-1) = 1L<<63 = 2^63次方
Intel IA32 浮点运算
IA32处理器和很多其它一些处理器一样,有专门用于保存浮点数的寄存器,当在cpu中进行浮点数运算时,这些寄存器就用来保存输入输出及相关的中间结果。
但IA32有一个比较特别的地方,它的浮点数寄存器是80位的,而我们在程序中只用到32和64位两种类型,因此当把float,double放入到cpu中时,它们都会先被转换成了80位,然后以80位的方式进行运算,最后得到的结果再转换回来。这样特性使得浮点数的计算可以相对更精确些,但同时,一不小心很可能也会引出一些意想不到的问题。
你可能突然恍然大悟了,对的,我们最开始提到那个奇怪的问题就与此相关。
s/e得到结果是个80位的浮点数,由这个浮点数先转换成double再转成int,与直接就转换成int,结果很可能是不同的。
比如在我们的例子中,s/e ~ 29.999999....时,s/e转换成double使用round-to-even的方式,会得到也许是30.0000001,再转成整形时,得到30.
但如果直接由29.99999...转换成整型,得到却是29。
后来新出的系列Intel处理器,包括IA32及64位的处理器,提供了专门的硬件来直接处理浮点数,使得可以分开对待float型与double型,这些硬件特性在compiler的支持下,可以生相对高效的代码,同时也避免了我们上面所遇到的问题,有兴趣的读者可以google一下相关的关键字:sse。
在64位机下
int 4个字节
long 8个字节
double 8个字节
float 4个字节
double long 8个字节
指针 8个字节
而在32位机下
int 4个字节
long 4个字节
double 8个字节
float 4个字节
double long 8个字节
指针 4个字节