New Algorithm for TRUNC, ROUNDUP, and ROUNDDOWN

Fix #4113. TRUNC isn't always producing the expected result. There was a promising algorithm at https://stackoverflow.com/questions/4668628/truncate-float-numbers-with-php from user Juan. It works through Php8.3, but failed in Php8.4 (more on this later). User Savageman on the same page has a solution that needs work, but, once the work had taken place, it works on Php8.1-8.4.

The ROUNDUP and ROUNDDOWN functions were adversely affected by Php8.4, probably for the same reasons as Juan's TRUNC suggestion. I put a kludge in place for them some time ago, but I wasn't happy with it. The solution used for TRUNC here suggested a change to the ROUNDUP and ROUNDDOWN code that would no longer require the kludge. The change to those functions now works more cleanly on Php8.1-8.4.
This commit is contained in:
oleibman
2024-07-26 23:02:11 -07:00
parent b406367425
commit f553019f95
5 changed files with 56 additions and 13 deletions
@@ -10,8 +10,6 @@ class Round
{
use ArrayEnabled;
private const ROUNDING_ADJUSTMENT = (PHP_VERSION_ID < 80400) ? 0 : 1e-14;
/**
* ROUND.
*
@@ -69,11 +67,22 @@ class Round
return 0.0;
}
$digitsPlus1 = $digits + 1;
if ($number < 0.0) {
return round($number - 0.5 * 0.1 ** $digits + self::ROUNDING_ADJUSTMENT, $digits, PHP_ROUND_HALF_DOWN);
if ($digitsPlus1 < 0) {
return round($number - 0.5 * 0.1 ** $digits, $digits, PHP_ROUND_HALF_DOWN);
}
$result = sprintf("%.{$digitsPlus1}f", $number - 0.5 * 0.1 ** $digits);
return round((float) $result, $digits, PHP_ROUND_HALF_DOWN);
}
return round($number + 0.5 * 0.1 ** $digits - self::ROUNDING_ADJUSTMENT, $digits, PHP_ROUND_HALF_DOWN);
if ($digitsPlus1 < 0) {
return round($number + 0.5 * 0.1 ** $digits, $digits, PHP_ROUND_HALF_DOWN);
}
$result = sprintf("%.{$digitsPlus1}f", $number + 0.5 * 0.1 ** $digits);
return round((float) $result, $digits, PHP_ROUND_HALF_DOWN);
}
/**
@@ -105,11 +114,23 @@ class Round
return 0.0;
}
$digitsPlus1 = $digits + 1;
if ($number < 0.0) {
return round($number + 0.5 * 0.1 ** $digits - self::ROUNDING_ADJUSTMENT, $digits, PHP_ROUND_HALF_UP);
if ($digitsPlus1 < 0) {
return round($number + 0.5 * 0.1 ** $digits, $digits, PHP_ROUND_HALF_UP);
}
$result = sprintf("%.{$digitsPlus1}f", $number + 0.5 * 0.1 ** $digits);
return round((float) $result, $digits, PHP_ROUND_HALF_UP);
}
return round($number - 0.5 * 0.1 ** $digits + self::ROUNDING_ADJUSTMENT, $digits, PHP_ROUND_HALF_UP);
if ($digitsPlus1 < 0) {
return round($number - 0.5 * 0.1 ** $digits, $digits, PHP_ROUND_HALF_UP);
}
$result = sprintf("%.{$digitsPlus1}f", $number - 0.5 * 0.1 ** $digits);
return round((float) $result, $digits, PHP_ROUND_HALF_UP);
}
/**
@@ -34,15 +34,26 @@ class Trunc
return $e->getMessage();
}
$digits = floor($digits);
// Truncate
$adjust = 10 ** $digits;
if (($digits > 0) && (rtrim((string) (int) ((abs($value) - abs((int) $value)) * $adjust), '0') < $adjust / 10)) {
if ($value == 0) {
return $value;
}
return ((int) ($value * $adjust)) / $adjust;
if ($value >= 0) {
$minusSign = '';
} else {
$minusSign = '-';
$value = -$value;
}
$digits = (int) floor($digits);
if ($digits < 0) {
$power = (int) (10 ** -$digits);
$result = intdiv((int) floor($value), $power) * $power;
return ($minusSign === '') ? $result : -$result;
}
$digitsPlus1 = $digits + 1;
$result = substr($minusSign . sprintf("%.{$digitsPlus1}f", $value), 0, -1);
return (float) $result;
}
}
@@ -33,4 +33,5 @@ return [
[0, 'B1, 0'],
['exception', ''],
['exception', '35.51'],
'negative number and precision' => [-31400, '-31415.92654, -2'],
];
@@ -33,4 +33,5 @@ return [
[0, 'B1, 0'],
['exception', ''],
['exception', '35.51'],
'negative number and precision' => [-31500, '-31415.92654, -2'],
];
@@ -11,6 +11,7 @@ return [
[-31415.92654, '-31415.92654, 10'],
[31415.92, '31415.92654, 2'],
[31400, '31415.92654, -2'],
'negative number and precision' => [-31400, '-31415.92654, -2'],
[0, '31415.92654, -10'],
[0, '-31415.92654, -10'],
[12000, '12345.6789, -3'],
@@ -32,4 +33,12 @@ return [
[-3, 'A4'],
[-5, 'A5'],
[0, 'B1'],
'issue4113' => [1.0, '1.01, 1'],
'issue4113 negative' => [-1.0, '-1.01, 1'],
'issue4113 additional' => [10.04, '10.04, 2'],
'issue4113 additional negative' => [-10.04, '-10.04, 2'],
'issue4113 small fraction keep all' => [0.04, '0.04, 2'],
'issue4113 small negative fraction keep all' => [-0.04, '-0.04, 2'],
'issue4113 small fraction lose some' => [0.0, '0.01, 1'],
'issue4113 small negative fraction lose some' => [0.0, '-0.001, 1'],
];