> 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 General Mailing List (http://www.php.net/) To unsubscribe, visit: http://www.php.net/unsub.php