Index: trunk/extensions/ParserFunctions/Expr.php |
— | — | @@ -153,6 +153,29 @@ |
154 | 154 | ); |
155 | 155 | |
156 | 156 | /** |
| 157 | + * Checks if $expr is an integer within one part in 10^10. |
| 158 | + * If so, recast as integer and return, else return $expr unchanged. |
| 159 | + * |
| 160 | + * Explicit checking for round-off errors should eliminate |
| 161 | + * many bugs associated with floating point conversion to integers. |
| 162 | + */ |
| 163 | + function checkInteger( $expr ) { |
| 164 | + $intval = (int)$expr; |
| 165 | + |
| 166 | + if( $expr >= 0 ) { |
| 167 | + if( $expr - $intval > 0.9999999999 ) { $intval += 1; } |
| 168 | + } else { |
| 169 | + if( $expr - $intval < -0.9999999999 ) { $intval -= 1; } |
| 170 | + } |
| 171 | + |
| 172 | + if( abs( $intval - $expr ) < 0.0000000001 ) { |
| 173 | + return $intval; |
| 174 | + } else { |
| 175 | + return $expr; |
| 176 | + } |
| 177 | + } |
| 178 | + |
| 179 | + /** |
157 | 180 | * Evaluate a mathematical expression |
158 | 181 | * |
159 | 182 | * The algorithm here is based on the infix to RPN algorithm given in |
— | — | @@ -400,7 +423,7 @@ |
401 | 424 | $right = array_pop( $stack ); |
402 | 425 | $left = array_pop( $stack ); |
403 | 426 | if ( $right == 0 ) throw new ExprError('division_by_zero', $this->names[$op]); |
404 | | - $stack[] = $left % $right; |
| 427 | + $stack[] = $this->checkInteger( $left ) % $this->checkInteger( $right ); |
405 | 428 | break; |
406 | 429 | case EXPR_PLUS: |
407 | 430 | if ( count( $stack ) < 2 ) throw new ExprError('missing_operand', $this->names[$op]); |
— | — | @@ -430,7 +453,7 @@ |
431 | 454 | if ( count( $stack ) < 2 ) throw new ExprError('missing_operand', $this->names[$op]); |
432 | 455 | $right = array_pop( $stack ); |
433 | 456 | $left = array_pop( $stack ); |
434 | | - $stack[] = ( $left == $right ) ? 1 : 0; |
| 457 | + $stack[] = ( $this->checkInteger( $left ) == $this->checkInteger( $right ) ) ? 1 : 0; |
435 | 458 | break; |
436 | 459 | case EXPR_NOT: |
437 | 460 | if ( count( $stack ) < 1 ) throw new ExprError('missing_operand', $this->names[$op]); |
— | — | @@ -441,43 +464,43 @@ |
442 | 465 | if ( count( $stack ) < 2 ) throw new ExprError('missing_operand', $this->names[$op]); |
443 | 466 | $digits = intval( array_pop( $stack ) ); |
444 | 467 | $value = array_pop( $stack ); |
445 | | - $stack[] = round( $value, $digits ); |
| 468 | + $stack[] = round( $this->checkInteger( $value ), $digits ); |
446 | 469 | break; |
447 | 470 | case EXPR_LESS: |
448 | 471 | if ( count( $stack ) < 2 ) throw new ExprError('missing_operand', $this->names[$op]); |
449 | 472 | $right = array_pop( $stack ); |
450 | 473 | $left = array_pop( $stack ); |
451 | | - $stack[] = ( $left < $right ) ? 1 : 0; |
| 474 | + $stack[] = ( $this->checkInteger( $left ) < $this->checkInteger( $right ) ) ? 1 : 0; |
452 | 475 | break; |
453 | 476 | case EXPR_GREATER: |
454 | 477 | if ( count( $stack ) < 2 ) throw new ExprError('missing_operand', $this->names[$op]); |
455 | 478 | $right = array_pop( $stack ); |
456 | 479 | $left = array_pop( $stack ); |
457 | | - $stack[] = ( $left > $right ) ? 1 : 0; |
| 480 | + $stack[] = ( $this->checkInteger( $left ) > $this->checkInteger( $right ) ) ? 1 : 0; |
458 | 481 | break; |
459 | 482 | case EXPR_LESSEQ: |
460 | 483 | if ( count( $stack ) < 2 ) throw new ExprError('missing_operand', $this->names[$op]); |
461 | 484 | $right = array_pop( $stack ); |
462 | 485 | $left = array_pop( $stack ); |
463 | | - $stack[] = ( $left <= $right ) ? 1 : 0; |
| 486 | + $stack[] = ( $this->checkInteger( $left ) <= $this->checkInteger( $right ) ) ? 1 : 0; |
464 | 487 | break; |
465 | 488 | case EXPR_GREATEREQ: |
466 | 489 | if ( count( $stack ) < 2 ) throw new ExprError('missing_operand', $this->names[$op]); |
467 | 490 | $right = array_pop( $stack ); |
468 | 491 | $left = array_pop( $stack ); |
469 | | - $stack[] = ( $left >= $right ) ? 1 : 0; |
| 492 | + $stack[] = ( $this->checkInteger( $left ) >= $this->checkInteger( $right ) ) ? 1 : 0; |
470 | 493 | break; |
471 | 494 | case EXPR_NOTEQ: |
472 | 495 | if ( count( $stack ) < 2 ) throw new ExprError('missing_operand', $this->names[$op]); |
473 | 496 | $right = array_pop( $stack ); |
474 | 497 | $left = array_pop( $stack ); |
475 | | - $stack[] = ( $left != $right ) ? 1 : 0; |
| 498 | + $stack[] = ( $this->checkInteger( $left ) != $this->checkInteger( $right ) ) ? 1 : 0; |
476 | 499 | break; |
477 | 500 | case EXPR_EXPONENT: |
478 | 501 | if ( count( $stack ) < 2 ) throw new ExprError('missing_operand', $this->names[$op]); |
479 | 502 | $right = array_pop( $stack ); |
480 | 503 | $left = array_pop( $stack ); |
481 | | - $stack[] = $left * pow(10,$right); |
| 504 | + $stack[] = $left * pow(10, $this->checkInteger( $right ) ); |
482 | 505 | break; |
483 | 506 | case EXPR_SINE: |
484 | 507 | if ( count( $stack ) < 1 ) throw new ExprError('missing_operand', $this->names[$op]); |
— | — | @@ -530,17 +553,17 @@ |
531 | 554 | case EXPR_FLOOR: |
532 | 555 | if ( count( $stack ) < 1 ) throw new ExprError('missing_operand', $this->names[$op]); |
533 | 556 | $arg = array_pop( $stack ); |
534 | | - $stack[] = floor($arg); |
| 557 | + $stack[] = floor( $this->checkInteger( $arg ) ); |
535 | 558 | break; |
536 | 559 | case EXPR_TRUNC: |
537 | 560 | if ( count( $stack ) < 1 ) throw new ExprError('missing_operand', $this->names[$op]); |
538 | 561 | $arg = array_pop( $stack ); |
539 | | - $stack[] = (int)$arg; |
| 562 | + $stack[] = (int)( $this->checkInteger( $arg ) ); |
540 | 563 | break; |
541 | 564 | case EXPR_CEIL: |
542 | 565 | if ( count( $stack ) < 1 ) throw new ExprError('missing_operand', $this->names[$op]); |
543 | 566 | $arg = array_pop( $stack ); |
544 | | - $stack[] = ceil($arg); |
| 567 | + $stack[] = ceil( $this->checkInteger( $arg ) ); |
545 | 568 | break; |
546 | 569 | case EXPR_POW: |
547 | 570 | if ( count( $stack ) < 2 ) throw new ExprError('missing_operand', $this->names[$op]); |