wouldnt ~==  ,~>, ~<, ~>= .... etc. be relivant here -
from what I remember of maths, (to long ago) ~ was the symbol for 
similar (and 2 together for approximatly equal) - hey even found a 
reference for it...
http://www.gomath.com/htdocs/ToGosheet/algebra/mathsymbols.html

the only thing is it would confuse perl people :) =~ (it;s the regex 
symbol in perl....)

regards
alan

Rasmus Lerdorf wrote:

>That'd be a pretty serious performance hit to take.  Do you know of any
>language where floating point comparisons work like that?  Certainly in
>both C and Perl you will never get 0.8 to equal 0.7+0.1 exactly.
>
>Just because nobody else does it is of course not reason enough not to do
>it, but doing 2 sprintf's for every floating point comparison makes me
>cringe.
>
>-Rasmus
>
>On Tue, 19 Mar 2002, George Whiffen wrote:
>
>>Hi Folks,
>>
>>0.8 == 0.7 + 0.1 or does it?
>>
>>I know I've brought this up before, but it's still driving me nuts, and
>>I still don't know how to explain it to a novice, so rather than go on
>>ranting I thought I'd try a fix.
>>
>>I'm no C programmer, and what I know about php source can be written on
>>the back of a very small envelope.  But at least you can laugh at my
>>appalling code, and, hopefully, explain to me why it can't go in the
>>next release ;).
>>
>>Summary
>>=======
>>The fix is based on a string convert and comparison. It seems to work,
>>is not too serious from a performance point of view, and only involves
>>two sources, four functions and a 100 lines or so of code.  It does not
>>create cumulative rounding errors, is easily controlled via an existing
>>ini variable i.e. "precision" and improves the consistency of php's use
>>of precision.
>>
>>The Bugs/Undesirable Features
>>=====================
>>0.8 == 0.7 + 0.1 evaluates to false
>>(int) (8.2 - 0.2) evaluates 7
>>intval(8.2 - 0.2) evaluates to 7
>>floor(10 * (0.7 + 0.1)) evaluates to 7
>>ceil (10 * (-0.7 + -0.1) evaluates to -7
>>
>>Basis of the Fix
>>==========
>>In general these would all evaluate correctly if they were evaluated to
>>the precision specified in php.ini (typically 14 for IEEE 64bit).
>>
>>The fix replaces the current equality test on doubles, (a - b == 0),
>>with  a string compare to 'precision' decimal places e.g.
>>sprintf("%.14G",a) == sprintf("%.14G",b).  This is a modification to the
>>doubles section of the Zend/zend_operators.c compare_function, which
>>ultimately handles all comparisons, ==, !=, >=, >, <, <=.
>>
>>Floor, ceil, (int), intval(), are fixed with an equality check of their
>>integer result to one above or below it, (as  appropriate) via the
>>modified compare_function.
>>
>>e.g. floor becomes
>>if  a == floor(a) + 1
>>return floor(a) + 1;
>>else
>>return floor(a)
>>
>>Apart from some performance tweaks, that's about it i.e.
>>Zend/zend_operators.c:compare_function
>>Zend/zend_operators.c:convert_to_long_base
>>ext/standard/math.c:ceil
>>ext/standard/math.c:floor
>>
>>Testing
>>=======
>>The fixes seem to work. However, they have only been tested on a 4.1.2
>>source under Linux 2.4.8-26mdk. They solve the problems listed above
>>and  do as well as a basic cgi build of 4.1.2 on run-tests.php (i.e.
>>they pass everything except pow.phpt, pear_registry.phpt and 029.phpt).
>>They also seem to behave identically to an unfixed version if
>>'precision' is set high enough e.g. set_ini('precision',18).
>>
>>
>>Backward Compatibility
>>================
>>I've tried and failed to come up with a realistic scenario where this
>>fix compromises existing user code.
>>
>>The main reason is that  string, printf, sprintf already force the
>>precision set in php.ini.  This means it quite difficult for someone to
>>have exploited the fact that compare_function does not.  To hit problems
>>with the fix they would have to have first  decided they care about the
>>digits beyond 'precision',  but do not care enough to use the bc
>>library.  They then have to gone to some trouble to get hold of those
>>extra digits. They could not, for instance, easily get them into a page
>>or database without a conversion to string automatically removing the
>>extra digits along the way.
>>
>>In contrast to a "round to precision after each floating point
>>operation" approach, (which would be a nightmare), these fixes should
>>not create any new issues with cumulative rounding errors.  All the
>>changed functions already return booleans, ints, or integer-rounded
>>floats.
>>
>>In any case, everything can easily be reverted to the old functionality
>>at execution time simply by  increasing the value of precision e.g.
>>set_ini('precision',18).
>>
>>
>>Performance
>>===========
>>Only doubles are effected.  Long comparisons, such as integer for-loops
>>(for($i=0;$i<$sizeof($aray);++$i) etc.), are unchanged and run just as
>>fast.
>>
>>The effect on double comparisons is not negligible.  sprintf's are
>>relatively expensive in terms of performance and adding the overhead of
>>two sprintfs to every single double compare would have been nasty.
>>
>>To minimise this the new compare_function code first of all checks that
>>the operands are not already equal and are "close enough" that a string
>>compare is necessary before forcing the sprintfs. The test is for (a-b)
>>!= 0 && ((a-b)/a) < 1e-(precision-1) e.g. (0.2 - 0.1) != 0 && ((0.2 -
>>0.1)/0.1) < 1e-13.
>>
>>Even when the operands are close and this test is the only overhead,
>>this still means an  extra floating point division which accounts for
>>nearly all the performance degradation.  compare_function is so fast
>>already, that this extra division seems to make comparisons of non-equal
>>doubles about twice as slow.
>>
>>Fortunately this is pretty small in absolute terms and when compared to
>>other simple comparisons. A double comparison using the fix e.g. 0.1 ==
>>0.2, is still no slower than a single character non-numeric string
>>comparison e.g. 'a' == 'b'.  They remain much faster than a numeric
>>string compare e.g. '0.1' == '0.2'.
>>
>>When operands are close e.g. 1.2345678901234e123 == 1.2345678901233e123
>>then the performance hit is much bigger as the two sprintfs and a string
>>compare are required.  Even then it is still faster than the old
>>workaround of casting to string before the compare i.e. (string) 0.1 ==
>>(string) 0.2.
>>
>>Further Optimisations
>>=====================
>>
>>The code can definitely be optimised further.
>>
>>The main slowdown on the ordinary, non-close, comparisons comes from the
>>floating point divide which is needed to make sure that it is the
>>"relative" difference not the absolute difference which is compared to
>>the precision.  If the precision were converted to binary and adjusted
>>by the value of the double's exponent it should be possible to avoid the
>>floating point division.  But this is significantly more complicated, or
>>rather I haven't worked out how to do it!
>>
>>The sprintf/string compare could also be improved.   For example,
>>sprintf("%a") seems to be about twice as fast as sprintf("%G").
>>Unfortunately %a can return un-normalized formats which would need
>>tweaking to stop them failing the string comparison.
>>
>>There are also significant performance improvements possible in the
>>floor, ceil, intval functions by not using compare_function but instead
>>doing an in-line comparison.  Since the results of these functions are
>>themselves integers their differences can be compared directly to
>>1e(-precision) without the floating point divide, (provided of course
>>they are less than 1e13 themselves).
>>
>>
>>Many thanks for taking the time to consider this. Any feedback will be
>>much appreciated.
>>
>>
>>George
>>
>>Changes to Zend/zend_operators.c:
>>
>>compare_function
>>============
>>
>>OLD CODE:
>>--------------
>> if ((op1->type == IS_DOUBLE || op1->type == IS_LONG)
>>  && (op2->type == IS_DOUBLE || op2->type == IS_LONG)) {
>>  result->value.dval = (op1->type == IS_LONG ? (double) op1->value.lval
>>: op1->value.dval) - (op2->type == IS_LONG ? (double) op2->value.lval :
>>op2->value.dval);
>>   result->value.lval = ZEND_NORMALIZE_BOOL(result->value.dval);
>>   result->type = IS_LONG;
>>   return SUCCESS;
>> }
>>
>>NEW CODE:
>>--------------
>>...
>>        double dval1, dval2;
>>        char   *sval1, *sval2;
>>        long   slen1, slen2;
>>...
>> if ((op1->type == IS_DOUBLE || op1->type == IS_LONG)
>>  && (op2->type == IS_DOUBLE || op2->type == IS_LONG)) {
>>
>>          if (op1->type == IS_DOUBLE) {
>>            dval1 = op1->value.dval;
>>          } else {
>>            dval1 = (double) op1->value.lval;
>>          }
>>          if (op2->type == IS_DOUBLE) {
>>            dval2 = op2->value.dval;
>>          } else {
>>            dval2 = (double) op2->value.lval;
>>          }
>>          result->value.dval = dval1 - dval2;
>>
>>          if (result->value.dval != 0 && fabs((dval1 - dval2)/(dval1 ==
>>0 ? 1 : dval1)) < pow(10.0,0 - (EG(precision) -1))) {
>>       sval1 = (char *) emalloc(MAX_LENGTH_OF_DOUBLE + EG(precision) +
>>1);
>>       slen1 = sprintf(sval1, "%.*G", (int) EG(precision),dval1);  /*
>>SAFE */
>>       sval2 = (char *) emalloc(MAX_LENGTH_OF_DOUBLE + EG(precision) +
>>1);
>>       slen2 = sprintf(sval2, "%.*G", (int) EG(precision),dval2);  /*
>>SAFE */
>>       if (0 == zend_binary_strcmp(sval1,slen1,sval2,slen2))
>>      {
>>         result->value.dval = 0;
>>       }
>>       efree(sval1);
>>       efree(sval2);
>>     }
>>     result->value.lval = ZEND_NORMALIZE_BOOL(result->value.dval);
>>     result->type = IS_LONG;
>>     return SUCCESS;
>> }
>>
>>
>>convert_to_long_base
>>===============
>>
>>OLD CODE:
>>--------------
>>...
>>  case IS_DOUBLE:
>>   DVAL_TO_LVAL(op->value.dval, op->value.lval);
>>   break;
>>...
>>
>>NEW CODE:
>>--------------
>>...
>> zval *op1, *op2, *result;
>>...
>>  case IS_DOUBLE:
>>    MAKE_STD_ZVAL(op1);
>>    MAKE_STD_ZVAL(op2);
>>    MAKE_STD_ZVAL(result);
>>    op1->type = IS_DOUBLE;
>>    op2->type = IS_LONG;
>>                  op1->value.dval = op->value.dval;
>>    DVAL_TO_LVAL(op->value.dval, op->value.lval);
>>                  if (op1->value.dval >= 0)
>>                   {
>>                      op2->value.lval = op->value.lval + 1;
>>                    } else {
>>                      op2->value.lval = op->value.lval - 1;
>>                    }
>>    compare_function(result, op1, op2  TSRMLS_CC);
>>    if (result->value.lval == 0) {
>>      op->value.lval = op2->value.lval;
>>    }
>>    zval_dtor(result);
>>    zval_dtor(op1);
>>    zval_dtor(op2);
>>   break;
>>
>>The changes to floor and ceil in ext/standard/math.c are very similar to
>>the convert_to_long_base change.
>>
>
>




-- 
PHP Development Mailing List <http://www.php.net/>
To unsubscribe, visit: http://www.php.net/unsub.php

Reply via email to