> Repeat of previous message in thread without the extra ugly wrapping, (sorry!!!)

Matthew Clark wrote:

   Seeing as the mathematically correct way to round numbers is to round down
   to n for n-1<=m<=n.5 and up to n+1 for n.5<m<n+1, I wonder why the PHP
   round() function couldn't include a little 'fuzz' to handle the rounding
   problems we encounter due to floating point representation in the hardware?
   It could even be a configurable option - but it would save writing a
   wrapper...

Matthew,

I can't agree with you more.

I really don't understand the point of php having a round function which
gives the wrong answer on even very simple decimals
e.g. round(0.35,1) returns 0.3.

The fuzz you suggest works fine and need only be very small.
pow(10.0,places-DBL_DIG) seems to do the job. e.g. a change
to the source of   math.c:PHP_FUNCTION(round) as follows, (changes
underlined):

   f = pow(10.0, (double) places);

   return_val *= f;
   if (return_val >= 0.0)
     return_val = floor(pow(10.0,places - DBL_DIG)) + 0.5 + return_val);
                                        ---------------------
  else
     return_val = ceil(return_val - (0.5 + pow(10.0,places - DBL_DIG)));
                                                               ------------------------
   return_val /= f;


You'll note that this implies a bias to high absolute values, but then we already
have that bias since we're rounding up anyway.  The only numbers which
would be incorrectly rounded because of the bias in the fix, already have more
than 14 significant figures e.g 0.349999999999999 rounds to 0.4 but
0.34999999999999 still rounds to 0.3.

I can't see any possible reason for this not being fixed, but then I
also think we should fix the rest of the binary representation problems i.e.

1. Comparison of Floating Points
0.8  == 0.7 + 0.1; evaluates as false not true.
In general, all the comparison operators, ==, !=, >=, >, <, >=, === may
give incorrect results if either of the operands is a floating point.

2. Conversion of Floating Point to Integer
floor(10 * (0.7 + 0.1)); evaluates to 7 not 8.
In general, floor(), ceil() and (int) may give incorrect results.

3. Spurious Differences
print (0.8 - (0.7 + 0.1)); outputs 1.1102230246252E-16 not 0

4. Cumulative Conversion Errors
for($i=1,$i<=100000,++$i){$total = $total + 0.1;}; calculates $total
as 10000.00001111 not 10000

They all have the same cause as the round problem i.e. the use of binary
floating points for decimal arithmetic without any compensation for
conversion errors.

As it happens, there's a simple fix for all of these as well   The fix is to 
automatically
round the results of php's arithmetic operators to 15 significant figures when
floating point numbers are involved.  It comes to about 20 lines of code change
to zend_operators.c i.e.8 calls to the following new function:

double decimalise(double dval)
{
  double f;
  if (dval == 0)
  {
     return dval;
  }
  f = pow(10.0, DBL_DIG - (1 + floor(log10(fabs(dval)))));
  return (double) (rint(dval*f))/f;
}

There is a performance downside, although much less than doing your own
workarounds.  To put it in perspective, the impact is a twentieth of that of using
a string cast/sprintf.  Indeed, the slowdown is less than using objects or arrays in 
your
arithmetic i.e. with the fix $a = $b + $c takes the same or less time than
unfixed $a = $b + $c->d

Or, to put it another way, if you are not worried about the performance impact of using
objects and arrays in arithmetic operations, you should not be worried by the impact of
this fix for decimal arithmetic.  (The decimalise function could also be speeded up 
with a
more clever calculation of "f", e.g. by skipping the log10 and pow functions but I'd 
rather
leave that to a real C programmer ;))

I haven't had a very enthusiastic response from the php developers in the past on these
issues, but I'm keen to have another go if anyone else shares my view that it's time to
sort out decimal arithmetic properly.  I just don't see the point of these 
operators/functions
that can go wrong at even a single decimal digit!

Regards,

George


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

Reply via email to