Uh oh!
There was an error while loading. Please reload this page.
Reimplement php_round_helper() using modf() - #12220
Conversation
This change makes the implementation much easier to understand, by explicitly handling the various cases. It fixes rounding for `0.49999999999999994`, because no loss of precision happens by adding / subtracing `0.5` before turning the result into an integral float. Instead the fractional parts are explicitly compared. see phpGH-12143 (this fixes one of the reported cases) ClosesphpGH-12159 which was an alternative attempt to fix the rounding issue for `0.49999999999999994`
| if (fractional >= 0.5) { | ||
| return integral + copysign(1.0, integral); | ||
| } | ||
| return integral; |
There was a problem hiding this comment.
clang is able to compile this branchless as per: https://godbolt.org/z/5cb8W1416, which is likely a good thing.
@TimWolla Since |
Uh oh!
There was an error while loading. Please reload this page.
This is for visual consistency with the other modes.
bukka
commented
Sep 17, 2023
Are you able to do some perf tests to see if there's any difference? |
This makes the code even clearer to understand and also improves the assembly, allowing the compiler to use an actual jump table for the switch cases.
Uh oh!
There was an error while loading. Please reload this page.
TimWolla
commented
Sep 17, 2023
Using #include<math.h>#include<stdbool.h>#include<stdio.h>#include<stdint.h>#ifndefPHP_ROUND_HALF_UP#definePHP_ROUND_HALF_UP 0x01 /* Arithmetic rounding, up == away from zero */#endif#ifndefPHP_ROUND_HALF_DOWN#definePHP_ROUND_HALF_DOWN 0x02 /* Down == towards zero */#endif#ifndefPHP_ROUND_HALF_EVEN#definePHP_ROUND_HALF_EVEN 0x03 /* Banker's rounding */#endif#ifndefPHP_ROUND_HALF_ODD#definePHP_ROUND_HALF_ODD 0x04
#endifdoubleold(doublevalue, intmode) {
doubletmp_value;
if (value >= 0.0) {
tmp_value=floor(value+0.5);
if ((mode==PHP_ROUND_HALF_DOWN&&value== (-0.5+tmp_value)) ||
(mode==PHP_ROUND_HALF_EVEN&&value== (0.5+2*floor(tmp_value/2.0))) ||
(mode==PHP_ROUND_HALF_ODD&&value== (0.5+2*floor(tmp_value/2.0) -1.0)))
{
tmp_value=tmp_value-1.0;
}
} else {
tmp_value=ceil(value-0.5);
if ((mode==PHP_ROUND_HALF_DOWN&&value== (0.5+tmp_value)) ||
(mode==PHP_ROUND_HALF_EVEN&&value== (-0.5+2*ceil(tmp_value/2.0))) ||
(mode==PHP_ROUND_HALF_ODD&&value== (-0.5+2*ceil(tmp_value/2.0) +1.0)))
{
tmp_value=tmp_value+1.0;
}
}
returntmp_value;
}
doublenew_jorg(doublevalue, intmode) {
doubletmp_value;
if (value >= 0.0) {
tmp_value=floor(value+0.5);
if ((mode==PHP_ROUND_HALF_DOWN&&value== (-0.5+tmp_value)) ||
(mode==PHP_ROUND_HALF_EVEN&&value== (0.5+2*floor(tmp_value/2.0))) ||
(mode==PHP_ROUND_HALF_ODD&&value== (0.5+2*floor(tmp_value/2.0) -1.0)) ||value< (-0.5+tmp_value))
{
tmp_value=tmp_value-1.0;
}
} else {
tmp_value=ceil(value-0.5);
if ((mode==PHP_ROUND_HALF_DOWN&&value== (0.5+tmp_value)) ||
(mode==PHP_ROUND_HALF_EVEN&&value== (-0.5+2*ceil(tmp_value/2.0))) ||
(mode==PHP_ROUND_HALF_ODD&&value== (-0.5+2*ceil(tmp_value/2.0) +1.0)) ||value> (0.5+tmp_value))
{
tmp_value=tmp_value+1.0;
}
}
returntmp_value;
}
doublenew_tim(doublevalue, intmode) {
doubleintegral, fractional;
fractional=fabs(modf(value, &integral));
switch (mode) {
casePHP_ROUND_HALF_UP:
if (fractional >= 0.5) {
returnintegral+copysign(1.0, integral);
}
returnintegral;
casePHP_ROUND_HALF_DOWN:
if (fractional>0.5) {
returnintegral+copysign(1.0, integral);
}
returnintegral;
casePHP_ROUND_HALF_EVEN:
if (fractional>0.5) {
returnintegral+copysign(1.0, integral);
}
if (fractional==0.5) {
booleven= !fmod(integral, 2.0);
if (!even) {
returnintegral+copysign(1.0, integral);
}
}
returnintegral;
casePHP_ROUND_HALF_ODD:
if (fractional>0.5) {
returnintegral+copysign(1.0, integral);
}
if (fractional==0.5) {
booleven= !fmod(integral, 2.0);
if (even) {
returnintegral+copysign(1.0, integral);
}
}
returnintegral;
}
__builtin_unreachable();
}
staticinlineuint64_trotl(constuint64_tx, intk) {
return (x << k) | (x >> (64-k));
}
staticuint64_ts[4];
uint64_tnext(void) {
constuint64_tresult=s[0] +s[3];
constuint64_tt=s[1] << 17;
s[2] ^= s[0];
s[3] ^= s[1];
s[1] ^= s[2];
s[0] ^= s[3];
s[2] ^= t;
s[3] =rotl(s[3], 45);
returnresult;
}
intmain() {
s[0] =0xbe0abf86eeacfd2d;
s[1] =0x5212a180ba6c1136;
s[2] =0xbb1f87b46572ab77;
s[3] =0xab0fde1b8ab187da;
constdoublestep_size=1.0 / (1ULL << 53);
doublesum=0;
for (size_ti=0; i<100000000; i++) {
sum+=FUNC(step_size* (next() >> 11), PHP_ROUND_HALF_UP);
}
printf("%.17g\n", sum);
}with old being the unfixed implementation that returns incorrect results for Within a Debian Sid container: |
This comment was marked as resolved.
This comment was marked as resolved.
TimWolla
commented
Sep 17, 2023
This was on a Intel(R) Core(TM) i5-2430M. The implementation in this PR is indeed the slowest of all of them, with the majority of the time spent in |
TimWolla
commented
Sep 17, 2023
Differences become much smaller when changing Then all versions are competitive with gcc and Jorg's version is much slower with clang, because the branch misses skyrocket. |
TimWolla
commented
Sep 17, 2023
With main being: (i.e. |
TimWolla
commented
Sep 17, 2023
My conclusion from these tests is that the performance heavily depends on the compiler used, the input distribution, and the rounding mode (with my version benefiting from the I'd argue that the |
Girgias
commented
Sep 17, 2023
For reference, LLVM seems to have recently merged a faster version of |
SakiTakamachi
commented
Sep 17, 2023
@TimWolla 's implementation appears to be numerically stable. Regarding the case of slowness, isn't this within an acceptable range considering accuracy? |
TimWolla
commented
Sep 17, 2023
@Girgias Also the clang version is tested for the non |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
bukka
left a comment
There was a problem hiding this comment.
As discussed privately I agree that it's better to have more consistent results. The code is certainly more readable now. Also thanks for adding really good explaining comments.
TimWolla
commented
Sep 19, 2023
Now merged, thank you. |
Since phpGH-12220 the implementation of `php_round_helper()`, which performs rounding to an integral value, is easy to verify for correctness up to the floating point precision. If rounding to 0 places is desired, i.e. the userland `round()` function is called with `$precision = 0`, we bypass all logic for the decimal point adjustment and instead directly call `php_round_helper()`. This change fixes the remaining two cases of phpGH-12143 and likely guarantees correct rounding for all possible inputs and `$precision = 0`.
This change makes the implementation much easier to understand, by explicitly handling the various cases.
It fixes rounding for
0.49999999999999994, because no loss of precision happens by adding / subtracing0.5before turning the result into an integral float. Instead the fractional parts are explicitly compared.see GH-12143 (this fixes one of the reported cases)
ClosesGH-12159 which was an alternative attempt to fix the rounding issue for
0.49999999999999994/cc @jorgsowa