Bug 23856 – The problem of accuracy loss in double division

Status
NEW
Severity
normal
Priority
P1
Component
dmd
Product
D
Version
D2
Platform
x86_64
OS
All
Creation time
2023-04-24T13:14:12Z
Last change time
2024-12-13T19:28:25Z
Keywords
backend
Assigned to
No Owner
Creator
mzfhhhh
Moved to GitHub: dmd#20264 →

Comments

Comment #0 by mzfhhhh — 2023-04-24T13:14:12Z
import std; void main() { double v = 0.0025; ulong a = cast(ulong)((cast(double) 50) / v); double t = (cast(double) 50) / v; ulong b = cast(ulong)(t); ulong c = cast(ulong)((cast(double) 50) / cast(double)0.0025); // dmd 2.103.0 a = 19999 , accuracy loss // ldc 1.32.0 a = 20000 info(a); // b= 20000 info(b); // c= 20000 info(c); }
Comment #1 by default_357-line — 2023-04-24T13:34:10Z
Are you using the 32-bit version of dmd? The same thing happens with gcc if you -m32. I can't reproduce with the 64-bit version.
Comment #2 by mzfhhhh — 2023-04-24T13:40:33Z
yes, i test again, dmd -m32 err.d output: err.d:15:main 19999 err.d:18:main 20000 err.d:21:main 20000 dmd -m64 err.d output: err.d:15:main 20000 err.d:18:main 20000 err.d:21:main 20000
Comment #3 by mzfhhhh — 2023-04-24T14:44:57Z
Test again, there are issues with both 32-bit and 64-bit import std; static void conv_err(double r) { ulong tmp = cast(ulong) ((cast(double) 50) / r); //dmd -m32 err.d , tmp = 19999 //dmd -m64 err.d , tmp = 19999 //ldc2 err err.d , tmp = 20000 info(tmp); } static void conv_ok(double r) { double v = ((cast(double) 50) / r); ulong tmp = cast(ulong) v; //dmd -m32 err.d , tmp = 20000 //dmd -m64 err.d , tmp = 20000 //ldc2 err err.d , tmp = 20000 info(tmp); } void main() { double r = 0.0025; conv_err(r); conv_ok(r); }
Comment #4 by mzfhhhh — 2023-04-24T14:49:32Z
(In reply to FeepingCreature from comment #1) > Are you using the 32-bit version of dmd? The same thing happens with gcc if > you -m32. I can't reproduce with the 64-bit version. Test again, there are issues with both 32-bit and 64-bit import std; static void conv_err(double r) { ulong tmp = cast(ulong) ((cast(double) 50) / r); //dmd -m32 err.d , tmp = 19999 //dmd -m64 err.d , tmp = 19999 //ldc2 err err.d , tmp = 20000 info(tmp); } static void conv_ok(double r) { double v = ((cast(double) 50) / r); ulong tmp = cast(ulong) v; //dmd -m32 err.d , tmp = 20000 //dmd -m64 err.d , tmp = 20000 //ldc2 err err.d , tmp = 20000 info(tmp); } void main() { double r = 0.0025; conv_err(r); conv_ok(r); }
Comment #5 by b2.temp — 2023-10-14T03:59:23Z
I suspect that this is not a bug and, even better, that `1999` would actually be the most accurate result. 1. `0.0025` is not exactly representable[0] as a double, it's actually is *slightly* bigger. 2. The division - DMD code will always use the FPU, so SDIV and have a 80 bit internal precision - LDC code will use a SSE instruction, with a 64 bit precision 3. the trunc - DMD - in conv_err FISTP is used on the internal 80 bit value - in conv_ok the 80 bit value goes back to a 64 bits local then is reloaded in the FPY, with 16 bit of precision loss, and finally the trunc happens (still FISTP) - LDC behaves the same in both conv_err and conv_ok because the 80 bit intermediate value has never existed. See disasm here[1]. Note: in the assembly you can see that the rounding mode is set/saved/restored at several places so maybe i'm completely wrong and that would be a DMD backend bug related to that. [0]: https://www.binaryconvert.com/result_double.html?decimal=048046048048050053 [1]: https://godbolt.org/z/3969TsPG1
Comment #6 by b2.temp — 2023-10-14T10:47:19Z
> `1999` would actually be the most accurate result Sorry for this non-sense; I meant "considering the real value of 0.0025", i.e a bit greater.
Comment #7 by robert.schadek — 2024-12-13T19:28:25Z
THIS ISSUE HAS BEEN MOVED TO GITHUB https://github.com/dlang/dmd/issues/20264 DO NOT COMMENT HERE ANYMORE, NOBODY WILL SEE IT, THIS ISSUE HAS BEEN MOVED TO GITHUB