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...


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

   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);
     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
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
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
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
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 you or anyone else thinks it's
worth sorting
this out properly.  Personally, I just don't see the point of having
in php that can go wrong at even a single decimal digit!



PHP General Mailing List (
To unsubscribe, visit:

Reply via email to