PHPã®roundé¢æ°ã®è¬ãå°ã解ãã
2é±é以ä¸åã®è¨äºãPHPの奇妙なround関数ããããããã¨ã«ãªã£ã¦ãã¾ãããæè¿æ¸ãå§ããã°ããã®æ¥è¨ã«ãããªã«äººãæ¥ããªãã¦ãæå人ã®é客åã¯æµç³ã ãªãããªã©ã¨æå¿ãã¦ãã¾ãã
ãã®é客åã®ãããããããã¾ããããFreeBSDã¨Mac OS Xã ã¨æåãéãããã¨ããã³ã¡ã³ããé ãã¾ãããå®éã«FreeBSDã§è©¦ãã¦ã¿ãã¨ããã確ãã«Linuxã¨ç°ãªããããã°ããã¢ãªæåã§ãããã®åå ããããã¾ãããã¨ããã®ãæ¬ç¨¿ã®æ¦è¦ã§ããåãã¢ã¿ã¢ã¿è¨äºãæ¸ãã¦ããéã«çç±ãããã£ã¡ãã£ã人ãå± ããã¨ã¯æãã¾ããããã詳細ãªã¨ããã¾ã§ææ¡ãã人ãå± ããã§ãããåãªãã«ç¾æç¹ã§ããã£ããã¨ãæ¸ãã¦ã¿ã¾ãã
ååã®è¨äºã§ãPHP_ROUND_FUZZã¨ããå®æ°ããå°ãªãã¨ãåã®æå ã®ç°å¢ã§ã¯ã0.50000000001ã¨å®ç¾©ããã¦ãããã¨æ¸ãã¾ããããã®è©³ç´°ã説æããã¨ãconfigureã¹ã¯ãªããã§ä¸è¨ã®ã³ã¼ããå®è¡ãã¦ãexit codeã1ãªã0.50000000001ã«ã0ãªã0.5ã«å®ç¾©ãã¦ãã¾ãã
#include <math.h> /* keep this out-of-line to prevent use of gcc inline floor() */ double somefn(double n) { return floor(n*pow(10,2) + 0.5); } int main() { return somefn(0.045)/10.0 != 0.5; }
åã®æå ã§ã¯0.50000000001ã«ãªã£ã¦ãããã¨ããã ãã§ãé¢ç½ãäºå®ã ã¨æã£ãã®ã§ãããconfigure次第ã ã¨ãããã¨ãæ¸ããªãã£ãã®ã¯ãã§ã¢ã§ã¯ç¡ãã£ãããããã¾ãããããã§ãã¹ãªã¼ãããã人ãå± ããã¨æãã¾ããããããªããã
ä¸æ¹ã§ãåã¯ãã®ã³ã¼ãã®å®è¡çµæã¯æµ®åå°æ°ç¹æ°ã®ååæ¼ç®ã¨floor(3)ã®æåã«ã®ã¿ä¾åãã¦ãã¦ãå°ãªãã¨ãx86ç³»ã®CPUã§ããã°åã®ç°å¢ã¨åãæåã示ãã¯ãã ã¨èãã¦ãã¾ããããã®ã³ã¼ãã®æåãFreeBSDã¨Linuxã§éããªã©ã¨ããã®ã¯å ¨ãæ³åãã¾ããã§ããããå®éã®ã¨ããx86ãªFreeBSDä¸ã§configureããã¨PHP_ROUND_FUZZã¯0.5ã«ãªãã¾ãã
ã©ã¡ããCPUã¯x86ç³»ãªã®ã«å®è¡çµæãéãçç±ã¯ãgccã®ã³ã³ãã¤ã«çµæãéãããã§ããLinuxã§gcc -O2ã§ä¸ã®ã³ã¼ããã³ã³ãã¤ã«ããã¨ãä¸è¨ã½ã¼ã¹ã³ã¼ãä¸ã®ã³ã¡ã³ãã¨ã¯è£è ¹ã«ãn*pow(10,2)+0.5ã®è¨ç®çµæã¯ã¬ã¸ã¹ã¿ã«ä¹ã£ãã¾ã¾ã§ã¤ã³ã©ã¤ã³å±éãããfloorã«è©ä¾¡ããã¦ãã¾ãã¾ããã¤ã¾ãã80bitã§è©ä¾¡ããã¦ãã¾ãã¾ãããã®çµæã精度ãååéããããã«0.045*100+0.5ã5æªæºã¨ãªã£ã¦ãã¾ããconfigureã§ã®ãã¹ãã«å¤±æãã¦ãã¾ãã¾ããä¸æ¹FreeBSDã§ã³ã³ãã¤ã«ããã¨æå¾ éãfloor(3)ãå¼ã°ãã¾ãã®ã§ãä¸æ¦è¨ç®çµæã¯ã¡ã¢ãªã«è¨é²ããã¦*1ã64bitã«ä¸¸ãããã¦ããè©ä¾¡ããã¾ããçµæã0.045*100+0.5ã5.0ã¨ãªããconfigureã§ã®ãã¹ãã¯æåãã¾ãã
è£è¶³ãã¦ããã¨ãx86ç³»ã®æµ®åå°æ°ç¹æ°ã¬ã¸ã¹ã¿ã¯80bitããã¾ããdoubleã®64bitã®å¤ã¨ãã¦åãåã£ãå¤ããéä¸çµéã§ã¯80bitã®ç²¾åº¦ã§è¨ç®ãã¦ãå¿ è¦ãªã¨ãã«64bitã«ä¸¸ãã¦ããããã§ããgccã®æé©åãªãã·ã§ã³ã«-ffloat-storeã¨ããã®ãããã¾ããããããã¤ãã¦ã³ã³ãã¤ã«ããã¨ã¤ã³ã©ã¤ã³å±éãããfloorã«å ¥ãåã«fstplãã¦ããfldlã§åãåºãããã«ãªãã¾ããç¡é§ãªã¡ã¢ãªã¢ã¯ã»ã¹ããã¦ç²¾åº¦ãè½ã¡ããã¤ã¾ã丸ããèµ·ããã®ã§Linuxã§ãFreeBSDã¨åãçµæãå¾ãããããã«ãªãã¾ãã
`-ffloat-store' Do not store floating point variables in registers, and inhibit other options that might change whether a floating point value is taken from a register or memory. This option prevents undesirable excess precision on machines such as the 68000 where the floating registers (of the 68881) keep more precision than a `double' is supposed to have. Similarly for the x86 architecture. For most programs, the excess precision does only good, but a few programs rely on the precise definition of IEEE floating point. Use `-ffloat-store' for such programs, after modifying them to store all pertinent intermediate computations into variables.
ã§ã誰ã®ãããã¨ãã話ã«ç«ã¡æ»ãã¨ãconfigureã§ã®ãã°ã®ããã«æãã¾ããååã¯ä»æ§ãã¨æãã¾ããããå°ãªãã¨ãLinuxã«ããã¦ã¯éçºé£ã®æå³ã¨ç°ãªãç¶æ ã ããã¨æãã¾ããä¸ã®ã³ã¼ãä¸ã®ã³ã¡ã³ããããã¦ãã¤ã³ã©ã¤ã³çã®floorã使ããã¦ãã¾ãã¨æ¬æ¥ãããããã¹ãã¨ã¯ç°ãªã£ã¦ãã¾ãã®ã§ã¯ãªãã§ãããããLinuxä¸ã§ã-ffloat-storeãã¤ãã¦ã³ã³ãã¤ã«ããããæé©åã-O0ã«ãã¦ãã¾ãã°ããã¢ã«çã¾ãå¤ããã¾ããã©ãç´°ãããã°ãå¦ãªæ¹æ³ã§åé¿ãã¦ããã ãã§æ¬è³ªçã§ã¯ãªãããã«æãã¾ãã
PHPã®ä¸ã®äººãä½ãèãã¦ä¸è¨configureããã³ä¾ã®0.50000000001ã®å®ç¾©ããã¦ãããã¯åã®å¦æ³ãªã®ã§ããã¡ãã¡ã§äºå®ã®ããã«å¼ç¨ãããã¨å°ã£ã¦ãã¾ãã®ã§ãããå°ãªãã¨ãååã®å¦æ³ã¯ééãã§ããããã«æãã¦ãã¾ããããæµ®åå°æ°ç¹æ°ã®ä¸¸ãã®ç²¾åº¦ãæªããç°å¢ãªãã¦ç¸æã«ãã¦ããããªããããroundé¢æ°ãã¢ãã¦ãã«ãã¦ããã°ããããããï¼ãã¨ãããã¨ãªã®ããããã¾ãããã§ãLinuxãæªããã¨åéãããã¡ãã£ãã¨ããç¶æ³ãªãã§ãããããããå¦æ³ãªã®ã§ãçå®ãç¥ã£ã¦ãã人ã¯æ¬å½ã«æãã¦ã»ããã§ãã
æå¾ã«ã両è ã®ã¢ã»ã³ããªåºåãè²¼ã£ã¦ããã¾ããã¾ãLinuxã§ã®-O2ã®çµæãããsomefn:ããretã¾ã§ãè¦ãã°ååã ã¨æãã¾ããã念ã®ããå ¨é¨è²¼ã£ã¦ããã¾ãã
.long 1120403456 .align 4 .LC1: .long 1056964608 .text .p2align 2,,3 .globl somefn .type somefn, @function somefn: pushl %ebp movl %esp, %ebp subl $16, %esp flds .LC0 fmull 8(%ebp) fadds .LC1 #APP fnstcw -2(%ebp) #NO_APP movw -2(%ebp), %ax andb $243, %ah orb $4, %ah movw %ax, -4(%ebp) #APP fldcw -4(%ebp) frndint fldcw -2(%ebp) #NO_APP fstpl -16(%ebp) fldl -16(%ebp) leave ret .size somefn, .-somefn .section .rodata.cst4 .align 4 .LC4: .long 1092616192 .align 4 .LC5: .long 1056964608 .text .p2align 2,,3 .globl main .type main, @function main: pushl %ebp movl %esp, %ebp subl $8, %esp andl $-16, %esp subl $16, %esp pushl $1067911741 pushl $1889785610 call somefn fdivs .LC4 flds .LC5 fxch %st(1) popl %eax fucompp fnstsw %ax andb $69, %ah xorb $64, %ah setne %al popl %edx movzbl %al, %eax leave ret .size main, .-main .section .note.GNU-stack,"",@progbits .ident "GCC: (GNU) 3.4.6 20060404 (Red Hat 3.4.6-3)"
次ãFreeBSDã®-O2ã§ãã
.file "round-test.c" .section .rodata.cst4,"aM",@progbits,4 .p2align 2 .LC0: .long 1120403456 .p2align 2 .LC1: .long 1056964608 .text .p2align 2,,3 .globl somefn .type somefn, @function somefn: pushl %ebp movl %esp, %ebp flds .LC0 fmull 8(%ebp) fadds .LC1 fstpl 8(%ebp) leave jmp floor .size somefn, .-somefn .section .rodata.cst4 .p2align 2 .LC4: .long 1092616192 .p2align 2 .LC5: .long 1056964608 .text .p2align 2,,3 .globl main .type main, @function main: pushl %ebp movl %esp, %ebp subl $8, %esp andl $-16, %esp subl $24, %esp pushl $1067911741 pushl $1889785610 call somefn fdivs .LC4 flds .LC5 fxch %st(1) fucompp fnstsw %ax andb $69, %ah addl $16, %esp xorb $64, %ah setne %al movzbl %al, %eax leave ret .size main, .-main .ident "GCC: (GNU) 3.4.4 [FreeBSD] 20050518"
追è¨ï¼æ¸ãå¿ãã¦ãã¾ãããåã®æå
ã®ç°å¢ã®Linux2.6+gcc 4.1.0ã§ãã¤ã³ã©ã¤ã³çã®floorã使ããã¾ãã
*1:ã¨ããããã¹ã¿ãã¯ã«ç©ã¾ãã¦