无意间看到了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#else647 /*648 * 'result' may alias with op1 or op2, so we need to649 * ensure that 'result' is not updated until after we650 * have read the values of op1 and op2.651 */652653 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#endif662 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个字节