August 24, 2015 Casting double to ulong weirdness | ||||
---|---|---|---|---|
| ||||
I'm posting this here for visibility. This was silently corrupting our data, and might be doing the same for others as well. import std.stdio; void main() { double x = 1.2; writeln(cast(ulong)(x * 10.0)); double y = 1.2 * 10.0; writeln(cast(ulong)y); } Output: 11 12 to!ulong instead of the cast does the right thing, and is a viable work-around. Issue: https://issues.dlang.org/show_bug.cgi?id=14958) |
August 24, 2015 Re: Casting double to ulong weirdness | ||||
---|---|---|---|---|
| ||||
Posted in reply to Márcio Martins | On Monday 24 August 2015 18:52, wrote:
> import std.stdio;
> void main() {
> double x = 1.2;
> writeln(cast(ulong)(x * 10.0));
> double y = 1.2 * 10.0;
> writeln(cast(ulong)y);
> }
>
> Output:
> 11
> 12
>
>
> to!ulong instead of the cast does the right thing, and is a viable work-around.
>
> Issue: https://issues.dlang.org/show_bug.cgi?id=14958)
1.2 is not representable exactly in binary. Try printing it with a lot of decimal places:
writefln("%.20f", x); /* prints "1.19999999999999995559" */
Multiply that by 10: ~11.999; cast to ulong: 11.
Interestingly, printing x * 10.0 that way shows exactly 12:
writefln("%.20f", x * 10.0); /* 12.00000000000000000000 */
But cast one operand to real and you're back at 11.9...:
writefln("%.20f", cast(real)x * 10.0); /* 11.99999999999999955591 */
So, apparently, real precision is used in your code. This is not unexpected; compilers are allowed to use higher precision than requested for floating point operations. I think people have argued against it in the past, but so far Walter has been adamant about it being the right choice.
|
August 24, 2015 Re: Casting double to ulong weirdness | ||||
---|---|---|---|---|
| ||||
Posted in reply to Márcio Martins | On 8/24/15 12:52 PM, "=?UTF-8?B?Ik3DoXJjaW8=?= Martins\" <marcioapm@gmail.com>\"" wrote: > I'm posting this here for visibility. This was silently corrupting our > data, and might be doing the same for others as well. > > import std.stdio; > void main() { > double x = 1.2; > writeln(cast(ulong)(x * 10.0)); > double y = 1.2 * 10.0; > writeln(cast(ulong)y); > } > > Output: > 11 > 12 Yes. This is part of the issue of floating point. 1.2 cannot be represented accurately. The second case is done via real, not double, and at compile time (i.e. constant folding). There may be other reasons why this works. You are better off adding a small epsilon: writeln(cast(ulong)(x * 10.0 + 0.1)); > to!ulong instead of the cast does the right thing, and is a viable > work-around. to!ulong likely adds the epsilon, but you'd have to look to be sure. Note, this is NOT a D problem, this is a problem with floating point. And by problem, I mean feature-that-you-should-avoid :) -Steve |
August 24, 2015 Re: Casting double to ulong weirdness | ||||
---|---|---|---|---|
| ||||
Posted in reply to Márcio Martins | On Monday, 24 August 2015 at 16:52:54 UTC, Márcio Martins wrote:
> I'm posting this here for visibility. This was silently corrupting our data, and might be doing the same for others as well.
>
> import std.stdio;
> void main() {
> double x = 1.2;
> writeln(cast(ulong)(x * 10.0));
> double y = 1.2 * 10.0;
> writeln(cast(ulong)y);
> }
>
> Output:
> 11
> 12
>
>
> to!ulong instead of the cast does the right thing, and is a viable work-around.
>
> Issue: https://issues.dlang.org/show_bug.cgi?id=14958)
I would not describe to!ulong as a "work-around". You just discovered one of the reasons to! exists: it is the right way to do it and cast(ulong) is the wrong way. As the others have noted, floating point is tricky business, and you need to use the right tools for the job.
std.math.round also works.
|
August 24, 2015 Re: Casting double to ulong weirdness | ||||
---|---|---|---|---|
| ||||
Posted in reply to Steven Schveighoffer | On Monday, 24 August 2015 at 17:26:12 UTC, Steven Schveighoffer wrote:
> On 8/24/15 12:52 PM, "=?UTF-8?B?Ik3DoXJjaW8=?= Martins\" <marcioapm@gmail.com>\"" wrote:
>> I'm posting this here for visibility. This was silently corrupting our
>> data, and might be doing the same for others as well.
>>
>> import std.stdio;
>> void main() {
>> double x = 1.2;
>> writeln(cast(ulong)(x * 10.0));
>> double y = 1.2 * 10.0;
>> writeln(cast(ulong)y);
>> }
>>
>> Output:
>> 11
>> 12
>
> Yes. This is part of the issue of floating point. 1.2 cannot be represented accurately.
>
> The second case is done via real, not double, and at compile time (i.e. constant folding). There may be other reasons why this works.
>
> You are better off adding a small epsilon:
>
> writeln(cast(ulong)(x * 10.0 + 0.1));
>
>> to!ulong instead of the cast does the right thing, and is a viable
>> work-around.
>
> to!ulong likely adds the epsilon, but you'd have to look to be sure.
>
> Note, this is NOT a D problem, this is a problem with floating point. And by problem, I mean feature-that-you-should-avoid :)
>
> -Steve
I am familiar with floating-point representations and their pitfalls, and I think that is not the issue here.
The issue I am trying to illustrate is the fact that the same exact operation returns different results.
Both operations are x * 10.0, except one of them passes through the stack before the cast.
I would expect this to be consistent, as I believe is the case in C/C++.
|
August 24, 2015 Re: Casting double to ulong weirdness | ||||
---|---|---|---|---|
| ||||
Posted in reply to Steven Schveighoffer | On Monday, 24 August 2015 at 17:26:12 UTC, Steven Schveighoffer wrote:
>
> Note, this is NOT a D problem, this is a problem with floating ponit. And by problem, I mean feature-that-you-should-avoid :)
>
> -Steve
Visual C++ 19.00.23026, x86, x64:
int _tmain(int argc, _TCHAR* argv[])
{
double x = 1.2;
printf("%d\r\n", (unsigned long long)(x * 10.0));
double y = 1.2 * 10.0;
printf("%d\r\n", ((unsigned long long)y));
return 0;
}
Output:
12
12
Same output in debugger for an ARM Windows App.
C# 6.0:
static void Main(string[] args)
{
double x = 1.2;
WriteLine((ulong)(x * 10.0));
double y = 1.2 * 10.0;
WriteLine((ulong)y);
}
Output:
12
12
Same output in debugger for ARM in all flavours (Android, iOS, Windows)
It seems like a D problem.
|
August 24, 2015 Re: Casting double to ulong weirdness | ||||
---|---|---|---|---|
| ||||
Posted in reply to rumbu | BTW, 1.2 and 12.0 are directly representable as double In C++: printf("%.20f\r\n", 1.2); printf("%.20f\r\n", 12.0); will output: 1.20000000000000000000 12.00000000000000000000 Either upcasting to real is the wrong decision here, either the writeln string conversion is wrong. |
August 24, 2015 Re: Casting double to ulong weirdness | ||||
---|---|---|---|---|
| ||||
Posted in reply to rumbu | On Mon, 24 Aug 2015 18:06:07 +0000, rumbu wrote: > BTW, 1.2 and 12.0 are directly representable as double > > In C++: > > printf("%.20f\r\n", 1.2); > printf("%.20f\r\n", 12.0); > > will output: > > 1.20000000000000000000 12.00000000000000000000 > > Either upcasting to real is the wrong decision here, either the writeln string conversion is wrong. No it's not, this must be some sort of constant-folding or precision increase. $ cat test.c #include "stdio.h" int main(int nargs, char** args) { double x = 1.2; printf("%.20f\n", x); } $ clang test.c && ./a.out 1.19999999999999995559 |
August 24, 2015 Re: Casting double to ulong weirdness | ||||
---|---|---|---|---|
| ||||
Posted in reply to rumbu | On 08/24/2015 11:06 AM, rumbu wrote: > BTW, 1.2 and 12.0 are directly representable as double 12 is but 1.2 is not. > In C++: > > printf("%.20f\r\n", 1.2); > printf("%.20f\r\n", 12.0); > > will output: > > 1.20000000000000000000 > 12.00000000000000000000 > > Either upcasting to real is the wrong decision here, either the writeln > string conversion is wrong. Output is one thing. The issue is with the representation of 1.2. You need infinite digits. D's %a helps with visualizing it: import std.stdio; void main() { writefln("%a", 1.2); writefln("%a", 12.0); } Outputs 0x1.3333333333333p+0 0x1.8p+3 Ali |
August 24, 2015 Re: Casting double to ulong weirdness | ||||
---|---|---|---|---|
| ||||
Posted in reply to rumbu | On Monday, 24 August 2015 at 18:06:08 UTC, rumbu wrote:
> BTW, 1.2 and 12.0 are directly representable as double
12.0 is representable, but I'm pretty sure, if you work it out, 1.2 isn't.
|
Copyright © 1999-2021 by the D Language Foundation