www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Bug?

reply "deed" <none none.none> writes:
// DMD v2.066.0
// All asserts pass (!)

import std.math : sin, cos, tan, asin, acos, atan,
                   sinh, cosh, tanh, asinh, acosh, atanh;

alias F = double;

immutable F a = 3, b = 5;

F fmul (F a) pure { return a * b;  }
F fsin (F a) pure { return sin(a); }

struct Smul { F value; this (F a) { value = a * b;  } alias value 
this; }
struct Ssin { F value; this (F a) { value = sin(a); } alias value 
this; }

F ans_mul = fmul(a);
F ans_sin = fsin(a);

Smul smul = Smul(a);
Ssin ssin = Ssin(a);


// All combinations of a*b, fmul(a), Smul(a), ans_mul and smul 
pass
// the equality test. E.g.:
assert (a*b == a*b);
assert (a*b == fmul(a));
assert (a*b == Smul(a));
assert (a*b == ans_mul);
assert (a*b == smul);

assert (fmul(a) == fmul(a));
assert (fmul(a) == Smul(a));
assert (fmul(a) == ans_mul);
assert (fmul(a) == smul);


// However, for std.math.sin it's different:
assert (sin(a) == fsin(a));        // But not in 2.065
assert (sin(a) != Ssin(a));        // ?
assert (sin(a) != ans_sin);        // ?
assert (sin(a) != ssin);           // ?

assert (fsin(a) != fsin(a));       // ?
assert (fsin(a) != Ssin(a));       // ?
assert (fsin(a) != ans_sin);       // ?
assert (fsin(a) != ssin);          // ?

// Same goes for cos, tan, asin, acos, atan:
F fcos  (F a) { return cos(a);  }
F ftan  (F a) { return tan(a);  }
F fasin (F a) { return asin(a); }
F facos (F a) { return acos(a); }
F fatan (F a) { return atan(a); }

assert (fcos(a)  != fcos(a));      // ?
assert (ftan(a)  != ftan(a));      // ?
assert (fasin(a) != fasin(a));     // ?
assert (facos(a) != facos(a));     // ?
assert (fatan(a) != fatan(a));     // ?


// And then it goes only downhill for
// sinh, cosh, tanh, asinh, acosh and atanh:
assert (sinh(a)    != sinh(a));    // ?
assert (cosh(a)    != cosh(a));    // ?
assert (tanh(a)    != tanh(a));    // ?
assert (asinh(a)   != asinh(a));   // ?
assert (acosh(a)   != acosh(a));   // ?
assert (atanh(0.5) != atanh(0.5)); // ?

--

Why bother?

import std.algorithm : max;

F fun (F a, F b) { return max(a,b) + 1.; }
unittest { assert (gun(1, 2) == gun(2, 1)); } // Passes

F pun (F a, F b) { return sin(max(a,b)); }
unittest { assert (fun(1, 2) == fun(2, 1)); } // Fails
Oct 23 2014
next sibling parent "deed" <none none.none> writes:
 --

 Why bother?

 import std.algorithm : max;

 F fun (F a, F b) { return max(a,b) + 1.; }
 unittest { assert (gun(1, 2) == gun(2, 1)); } // Passes

 F pun (F a, F b) { return sin(max(a,b)); }
 unittest { assert (fun(1, 2) == fun(2, 1)); } // Fails
// Fun, gun, pun... unittest { assert (fun(1, 2) == fun(2, 1)); } // Passes unittest { assert (pun(1, 2) == pun(2, 1)); } // Fails
Oct 23 2014
prev sibling next sibling parent reply Steven Schveighoffer <schveiguy yahoo.com> writes:
On 10/23/14 1:08 PM, deed wrote:
 // DMD v2.066.0
 // All asserts pass (!)
Using equality is not a good idea with floating point. The compiler will on a whim, or depending on whether it can inline or not, use higher precision floats, changing the outcome slightly. I cannot say for certain whether this explains all the issues you have, the very last one seems troubling to me at least. -Steve
Oct 23 2014
parent reply "deed" <none none.none> writes:
 Using equality is not a good idea with floating point.

 The compiler will on a whim, or depending on whether it can 
 inline or not, use higher precision floats, changing the 
 outcome slightly.

 I cannot say for certain whether this explains all the issues 
 you have, the very last one seems troubling to me at least.

 -Steve
Sure, in many cases it's a bad idea. While I understand that sin(PI) != 0.0, but approxEqual(sin(PI), 0.0) == true, I would expect the following to pass: assert (0.0 == 0.0); assert (1.2345 == 1.2345); F a = 1.2345, b = 9.8765; assert (a+b == b+a); assert (a*b == b*a); F fun (F a) pure; assert (fun(a) + fun(b) == fun(b) + fun(a)); assert (fun(a) * fun(b) == fun(b) * fun(a)); auto a = fun(100); auto b = fun(100); assert (a == b); assert (fun(100) == fun(100)); Now, if fun's body is { return sin(a); }, the behaviour changes to: auto c = fun(100); auto d = fun(100); assert (c == d); // Ok assert (fun(100) != fun(100)) // I have a hard time understanding // this is correct behaviour
Oct 23 2014
parent reply Steven Schveighoffer <schveiguy yahoo.com> writes:
On 10/23/14 2:18 PM, deed wrote:
 Using equality is not a good idea with floating point.

 The compiler will on a whim, or depending on whether it can inline or
 not, use higher precision floats, changing the outcome slightly.

 I cannot say for certain whether this explains all the issues you
 have, the very last one seems troubling to me at least.
Sure, in many cases it's a bad idea. While I understand that sin(PI) != 0.0, but approxEqual(sin(PI), 0.0) == true, I would expect the following to pass: assert (0.0 == 0.0); assert (1.2345 == 1.2345); F a = 1.2345, b = 9.8765; assert (a+b == b+a); assert (a*b == b*a);
None of these fail on my system
 F fun (F a) pure;

 assert (fun(a) + fun(b) == fun(b) + fun(a));
 assert (fun(a) * fun(b) == fun(b) * fun(a));

 auto a = fun(100);
 auto b = fun(100);

 assert (a == b);
 assert (fun(100) == fun(100));
Not sure what body of fun is, so I cannot test this.
 Now, if fun's body is { return sin(a); }, the behaviour changes to:

 auto c = fun(100);
 auto d = fun(100);

 assert (c == d);              // Ok
 assert (fun(100) != fun(100)) // I have a hard time understanding
                                // this is correct behaviour
Tried that out, it does not fail on my machine. Can you be more specific on your testing? What compiler/platform? Stock compiler, or did you build it yourself? -Steve
Oct 23 2014
next sibling parent reply "deed" <none none.none> writes:
On Thursday, 23 October 2014 at 18:26:53 UTC, Steven 
Schveighoffer wrote:
 On 10/23/14 2:18 PM, deed wrote:
 Using equality is not a good idea with floating point.

 The compiler will on a whim, or depending on whether it can 
 inline or
 not, use higher precision floats, changing the outcome 
 slightly.

 I cannot say for certain whether this explains all the issues 
 you
 have, the very last one seems troubling to me at least.
Sure, in many cases it's a bad idea. While I understand that sin(PI) != 0.0, but approxEqual(sin(PI), 0.0) == true, I would expect the following to pass: assert (0.0 == 0.0); assert (1.2345 == 1.2345); F a = 1.2345, b = 9.8765; assert (a+b == b+a); assert (a*b == b*a);
None of these fail on my system
Same for me, that's the point; it's perfectly valid to compare floating points.
 F fun (F a) pure;

 assert (fun(a) + fun(b) == fun(b) + fun(a));
 assert (fun(a) * fun(b) == fun(b) * fun(a));

 auto a = fun(100);
 auto b = fun(100);

 assert (a == b);
 assert (fun(100) == fun(100));
Not sure what body of fun is, so I cannot test this.
Could be anything taking a floating point and returning a floating point. You would expect to get the same back for the same input, especially when it's pure. If so, the asserts above are expected to hold.
 Now, if fun's body is { return sin(a); }, the behaviour 
 changes to:

 auto c = fun(100);
 auto d = fun(100);

 assert (c == d);              // Ok
 assert (fun(100) != fun(100)) // I have a hard time 
 understanding
                               // this is correct behaviour
Tried that out, it does not fail on my machine. Can you be more specific on your testing? What compiler/platform? Stock compiler, or did you build it yourself?
Right. It doesn't fail, and that's the problem. assert (fun(100) == fun(100)); // Should pass, and does with // body { return a + 1; } // but not with // body { return sin(a); } assert (fun(100) != fun(100)); // Shouldn't pass, but passes with // body { return sin(a);} Compiler: DMD32 D Compiler v2.066.0 Also tried dpaste.dzfl.pl with 2.065.0 and DMD 2.x Git (cfb5842b49), which had slightly different behaviour; more of the sin tests which should pass in my opinion did with those two. Hence, it appears to be regressions.
 -Steve
Oct 23 2014
parent reply Steven Schveighoffer <schveiguy yahoo.com> writes:
On 10/23/14 2:46 PM, deed wrote:
 On Thursday, 23 October 2014 at 18:26:53 UTC, Steven Schveighoffer wrote:
 On 10/23/14 2:18 PM, deed wrote:
 Using equality is not a good idea with floating point.

 The compiler will on a whim, or depending on whether it can inline or
 not, use higher precision floats, changing the outcome slightly.

 I cannot say for certain whether this explains all the issues you
 have, the very last one seems troubling to me at least.
Sure, in many cases it's a bad idea. While I understand that sin(PI) != 0.0, but approxEqual(sin(PI), 0.0) == true, I would expect the following to pass: assert (0.0 == 0.0); assert (1.2345 == 1.2345); F a = 1.2345, b = 9.8765; assert (a+b == b+a); assert (a*b == b*a);
None of these fail on my system
Same for me, that's the point; it's perfectly valid to compare floating points.
OK. I think to be frank, your explanation by example needs to be accompanied with what the result is (you do this below, thanks). From the above, it sounds like they did not pass, but you expected them to. As to your assertion that it's perfectly valid to compare floating points, I think examples where they compare equal do not prove all equivalent floating point calculations should compare equal. Is it valid? Yes. Is it wise? No.
 F fun (F a) pure;

 assert (fun(a) + fun(b) == fun(b) + fun(a));
 assert (fun(a) * fun(b) == fun(b) * fun(a));

 auto a = fun(100);
 auto b = fun(100);

 assert (a == b);
 assert (fun(100) == fun(100));
Not sure what body of fun is, so I cannot test this.
Could be anything taking a floating point and returning a floating point. You would expect to get the same back for the same input, especially when it's pure. If so, the asserts above are expected to hold.
OK, I get that. I agree, it should be equal.
 Now, if fun's body is { return sin(a); }, the behaviour changes to:

 auto c = fun(100);
 auto d = fun(100);

 assert (c == d);              // Ok
 assert (fun(100) != fun(100)) // I have a hard time understanding
                               // this is correct behaviour
Tried that out, it does not fail on my machine. Can you be more specific on your testing? What compiler/platform? Stock compiler, or did you build it yourself?
Right. It doesn't fail, and that's the problem.
Sorry, I said this wrong. I tried out fun(100) == fun(100) and the assert passed.
 assert (fun(100) == fun(100));  // Should pass, and does with
                                  // body { return a + 1; }
                                  // but not with
                                  // body { return sin(a); }
Does not fail for me for sin(a) case.
 Compiler: DMD32 D Compiler v2.066.0
OK, I tried with OSX 64-bit compiler. Perhaps 32 bit would not fare as well. What platform are you testing on? -Steve
Oct 24 2014
parent "deed" <none none.none> writes:
 OK, I tried with OSX 64-bit compiler. Perhaps 32 bit would not 
 fare as well. What platform are you testing on?
Have tried Linux and Windows 64-bit and it seems to be an issue when compiled with -m32. Tests are provided here http://dpaste.dzfl.pl/5f55f4152aa8. I agree that one cannot compare double and real and expect equality, so the ones marked with [1] are not incorrect behaviour, while [2] and [4] seem to be bugs.
Oct 24 2014
prev sibling parent reply "H. S. Teoh via Digitalmars-d-learn" <digitalmars-d-learn puremagic.com> writes:
On Thu, Oct 23, 2014 at 02:26:52PM -0400, Steven Schveighoffer via
Digitalmars-d-learn wrote:
 On 10/23/14 2:18 PM, deed wrote:
[...]
Now, if fun's body is { return sin(a); }, the behaviour changes to:

auto c = fun(100);
auto d = fun(100);

assert (c == d);              // Ok
assert (fun(100) != fun(100)) // I have a hard time understanding
                               // this is correct behaviour
Tried that out, it does not fail on my machine. Can you be more specific on your testing? What compiler/platform? Stock compiler, or did you build it yourself?
[...] A similar problem was recently (about 2-3 weeks ago IIRC) seen in one of the Phobos PR's. It appears to be related to the autoextension of float to double (or double to real, I forget which) in certain contexts on Windows. deed Could you please try to reduce the failing test to a minimal code example, and post a disassembly of the concerned function(s)? This could either be a subtle codegen bug, or something more fundamentally broken with 80-bit real support. T -- Making non-nullable pointers is just plugging one hole in a cheese grater. -- Walter Bright
Oct 23 2014
parent reply "deed" <none none.none> writes:
 A similar problem was recently (about 2-3 weeks ago IIRC) seen 
 in one of
 the Phobos PR's. It appears to be related to the autoextension 
 of float
 to double (or double to real, I forget which) in certain 
 contexts on
 Windows.   deed Could you please try to reduce the failing test 
 to a
 minimal code example, and post a disassembly of the concerned
 function(s)? This could either be a subtle codegen bug, or 
 something
 more fundamentally broken with 80-bit real support.


 T
Some testing can be found on http://dpaste.dzfl.pl/5f55f4152aa8 for both Windows and Linux. This just illustrates the sin function.
Oct 23 2014
next sibling parent "deed" <none none.none> writes:
 Some testing can be found on http://dpaste.dzfl.pl/5f55f4152aa8
 for both Windows and Linux. This just illustrates the sin 
 function.
Replacing double with real makes everything pass on Linux Mint 16 with -m32 and -m64. Replacing double with float seems to give the same problems as before, but hasn't been extensively tested. It seems very likely to be a conversion issue, as H. S. Teoh mentioned.
Oct 23 2014
prev sibling parent reply "anonymous" <anonymous example.com> writes:
On Thursday, 23 October 2014 at 21:17:25 UTC, deed wrote:
 Some testing can be found on http://dpaste.dzfl.pl/5f55f4152aa8
 for both Windows and Linux. This just illustrates the sin 
 function.
I think the tests marked "[1]" are expected to fail. They involve converting one operand of the comparison to double, but not the other. The comparison is done using real precision. So, one operand goes through a real->double->real conversion, which changes the value.
Oct 23 2014
next sibling parent "anonymous" <anonymous example.com> writes:
On Thursday, 23 October 2014 at 21:42:46 UTC, anonymous wrote:
 On Thursday, 23 October 2014 at 21:17:25 UTC, deed wrote:
 Some testing can be found on http://dpaste.dzfl.pl/5f55f4152aa8
 for both Windows and Linux. This just illustrates the sin 
 function.
I think the tests marked "[1]" are expected to fail. They involve converting one operand of the comparison to double, but not the other. The comparison is done using real precision. So, one operand goes through a real->double->real conversion, which changes the value.
Then again, I guess they're not guaranteed to fail. I remember that the compiler is free to use higher than specified precision, and that Walter feels strongly that this is the right thing to do. So, the compiler could skip the conversion to double, making the operands equal again. And this is presumably what happens in the other failing tests, where both operands are nominally doubles. One operand is indeed converted to double. But the other is not, because the compiler is free to do either.
Oct 23 2014
prev sibling parent "deed" <none none.none> writes:
On Thursday, 23 October 2014 at 21:42:46 UTC, anonymous wrote:
 On Thursday, 23 October 2014 at 21:17:25 UTC, deed wrote:
 Some testing can be found on http://dpaste.dzfl.pl/5f55f4152aa8
 for both Windows and Linux. This just illustrates the sin 
 function.
I think the tests marked "[1]" are expected to fail. They involve converting one operand of the comparison to double, but not the other. The comparison is done using real precision. So, one operand goes through a real->double->real conversion, which changes the value.
You're right about those marked [1]; sin returns real and shouldn't be expected to equal the same value truncated to double or float and then extended back to real. Converting the sin to double and compare is expected to work for those, and it does when compiled with -m64, but not with -m32, on Linux: -- import std.math : sin; import std.conv : to; double fun (double a) { return sin(a); } immutable double a = 3. assert (fun(a) == sin(a).to!double); // Works when compiled with -m64 -- The behaviour of those only marked [2] and [4] (the 32-bit versions) seems to be a bug.
Oct 24 2014
prev sibling parent "deed" <none none.none> writes:
 assert (fasin(a) != fasin(a));     // ?
 assert (facos(a) != facos(a));     // ?
Too quick there.. But: assert (fasin(0.5) != fasin(0.5)); // ? assert (facos(0.5) != facos(0.5)); // ?
Oct 23 2014