www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Can we drop casts from float to bool?

reply Don <nospam nospam.com> writes:
This issue is inspired by bug 3918.

float f;
if (f) ...

This currently compiles, but if the condition is true, what can you 
conclude about f?
Obviously it's not zero, but can it be NaN?
If it's just translated to:  if (f!=0) ..., then f could be NaN.
Likewise, if (!f) ... is NOT triggered if f is NaN.

I find that rather unintuitive, and I can't easily invent a case where 
it is useful.

As bug 3918 shows, this conversion has never worked properly. Most 
existing code that makes use of it is probably broken.

`cast(bool)f` has the same problem.  I don't think it should compile. 
And compared to `f != 0`, it's very unclear.

This conversion seems to be confusing, bug prone, and not useful. Can we 
just get rid if it?
Mar 22 2010
next sibling parent Daniel Keep <daniel.keep.lists gmail.com> writes:
Don wrote:
 This issue is inspired by bug 3918.
 
 float f;
 if (f) ...
 
 This currently compiles, but if the condition is true, what can you
 conclude about f?
 Obviously it's not zero, but can it be NaN?
 If it's just translated to:  if (f!=0) ..., then f could be NaN.
 Likewise, if (!f) ... is NOT triggered if f is NaN.
 
 I find that rather unintuitive, and I can't easily invent a case where
 it is useful.
 
 As bug 3918 shows, this conversion has never worked properly. Most
 existing code that makes use of it is probably broken.
 
 `cast(bool)f` has the same problem.  I don't think it should compile.
 And compared to `f != 0`, it's very unclear.
 
 This conversion seems to be confusing, bug prone, and not useful. Can we
 just get rid if it?
I dealt with this in my first ever blog post... largely because I named the blog `while(nan)`. That said, it is pretty strange behaviour.
Mar 22 2010
prev sibling next sibling parent Marianne Gagnon <auria.mg gmail.com> writes:
Don Wrote:

 This issue is inspired by bug 3918.
 
 float f;
 if (f) ...
 
 This currently compiles, but if the condition is true, what can you 
 conclude about f?
 Obviously it's not zero, but can it be NaN?
 If it's just translated to:  if (f!=0) ..., then f could be NaN.
 Likewise, if (!f) ... is NOT triggered if f is NaN.
 
 I find that rather unintuitive, and I can't easily invent a case where 
 it is useful.
 
 As bug 3918 shows, this conversion has never worked properly. Most 
 existing code that makes use of it is probably broken.
 
 `cast(bool)f` has the same problem.  I don't think it should compile. 
 And compared to `f != 0`, it's very unclear.
 
 This conversion seems to be confusing, bug prone, and not useful. Can we 
 just get rid if it?
I never used such feature in thousands of lines of code. I think it can safely go away (and I'd welcome that change too, /me never liked implicit conversion from numeric types to bool anyway)
Mar 22 2010
prev sibling next sibling parent reply bearophile <bearophileHUGS lycos.com> writes:
Don:
 This conversion seems to be confusing, bug prone, and not useful. Can we 
 just get rid if it?
So let me understand better the situation. FP can be: various kinds of NaN, +0.0, -0.0, +infinite, -infinite, and normal numbers. The language designers have to decide what to do for those various values in an instruction like if(x). At the moment in D NaNs are true, +0.0 and -0.0 are false, and the other values are true. Considering NaN as true looks like an arbitrary decision, but in general NaN means not a value, so it's not zero nor different from zero. So probably if(NaN) has to be a runtime error. I think that's what happens when NaNs are set as signalling. Your solution is to forbid implicit and explicit casts of floating points to boolean values, because the choice of the true/false values is arbitrary, and because I think you are saying that packing normal values and NaNs as true is not meaningful in real programs. Disabling conversions from FP to bools seems like able to break generic code. If I have a function template that can accept an int or FP and contains an if(x) it can't compile if the type of x is a FP. This can be solved writing if(x==0) that works with both ints and FPs (and it's true for +0.0 and -0.0). In D some people have proposed to change the semantics of the "is" operator, to make it more useful and tidy, so if you want to know if x is a NaN you can then write if(x is nan). Writing if(x==0) is probably a little more explicit anyway, and Pascal/Java programmers are able to survive with it. In conclusion I don't know if your idea can be a little bad for generic code, but overall I don't think it can hurt my code. Bye, bearophile
Mar 22 2010
parent reply Don <nospam nospam.com> writes:
bearophile wrote:
 Don:
 This conversion seems to be confusing, bug prone, and not useful. Can we 
 just get rid if it?
So let me understand better the situation. FP can be: various kinds of NaN, +0.0, -0.0, +infinite, -infinite, and normal numbers. The language designers have to decide what to do for those various values in an instruction like if(x). At the moment in D NaNs are true, +0.0 and -0.0 are false, and the other values are true.
Even that's not completely clear. Read the bug report. Sometimes NaNs are true, sometimes not!
 Considering NaN as true looks like an arbitrary decision, but in general NaN
means not a value, so it's not zero nor different from zero. So probably
if(NaN) has to be a runtime error. I think that's what happens when NaNs are
set as signalling.
 Your solution is to forbid implicit and explicit casts of floating points to
boolean values, because the choice of the true/false values is arbitrary, 
Yes. if(x) normally checks to see if x is "null". But for FP, there are two different forms of "null", and checking only one of them makes little sense. and because I think you are saying that packing normal values and NaNs as true is not meaningful in real programs. No, I'm not saying that at all. I'm saying that this operation is extremely subtle, and the syntax gives you a false sense of security. BTW, once you change if(x) into if (x == 0), you're may ask a second question: "do I mean exact equality, or should this really be: if (fabs(x)<= TOLERANCE)" ?
 Disabling conversions from FP to bools seems like able to break generic code.
If I have a function template that can accept an int or FP and contains an
if(x) it can't compile if the type of x is a FP. 
The current behaviour breaks generic code, too. Then if x is a class, then if (x) is true if x has been initialized. But if x is floating point, if (x) is true if x hasn't been initialized! This is arguably the main use for `if (x)`. Note that writing generic code that is correct for both FP and ints is very difficult. There are so many cases where the same syntax has different semantics.
 This can be solved writing if(x==0) that works with both ints and FPs (and
it's true for +0.0 and -0.0). 
Exactly. I think this is almost always superior.
 In D some people have proposed to change the semantics of the "is" operator,
to make it more useful and tidy, so if you want to know if x is a NaN you can
then write if(x is nan).
That won't work in this case, unless you make nan a keyword. There are 2^^62 different NaNs, so a bitwise comparison will not work.
Mar 23 2010
parent reply grauzone <none example.net> writes:
Don wrote:
 In D some people have proposed to change the semantics of the "is" 
 operator, to make it more useful and tidy, so if you want to know if x 
 is a NaN you can then write if(x is nan).
That won't work in this case, unless you make nan a keyword. There are 2^^62 different NaNs, so a bitwise comparison will not work.
There are other cases where if and is seem to do non-obvious things, that can lead to subtle bugs. If "x" is an array, if(x) checks if the array descriptor is null, and not whether x has length 0. To be consistent, I see two choices how this could be fixed: 1. "if" is "intelligent", then both the float and the array case should be fixed to do what one would expect 2. just turn if(x) into if(x is x.init) and make "is" a bitwise comparison even for floats If way 2. is chosen, I don't think multiple NaN would be a problem. The programmer just has to be aware that NaN isn't a single value. "is" yields true if the value is exactly the same, and nothing else. In conclusion, if(x) would be about as useless for floats as it is for arrays. In any way, the situation wouldn't be worse than before, but at least more consistent.
Mar 23 2010
parent reply Don <nospam nospam.com> writes:
grauzone wrote:
 Don wrote:
 In D some people have proposed to change the semantics of the "is" 
 operator, to make it more useful and tidy, so if you want to know if 
 x is a NaN you can then write if(x is nan).
That won't work in this case, unless you make nan a keyword. There are 2^^62 different NaNs, so a bitwise comparison will not work.
There are other cases where if and is seem to do non-obvious things, that can lead to subtle bugs. If "x" is an array, if(x) checks if the array descriptor is null, and not whether x has length 0. To be consistent, I see two choices how this could be fixed: 1. "if" is "intelligent", then both the float and the array case should be fixed to do what one would expect
What would you expect? For floats, I don't think there's ANY choice that makes sense.
 2. just turn if(x) into if(x is x.init) and make "is" a bitwise 
 comparison even for floats
 
 If way 2. is chosen, I don't think multiple NaN would be a problem. The 
 programmer just has to be aware that NaN isn't a single value. 
Surely you mean if (x !is x.init) ? Won't work for structs. But why bother? If you know that if (x) is true, how does that help? It's not float.init, but it still might be float.nan. You still need a seperate nan test! "is" yields true if the value is exactly the same, and nothing else. In
 conclusion, if(x) would be about as useless for floats as it is for 
 arrays. In any way, the situation wouldn't be worse than before, but at 
 least more consistent.
I really don't think there's a solution. The only thing that makes sense is to disallow it completely. But more importantly, I cannot see any reason to try. I cannot see any reason why the if (x) syntax is desirable for floats. I can see many downsides and opportunities for bugs, but I cannot see ANYTHING good about it at all. It's a silly thing to have in the core language. We don't need it. Get rid of it.
Mar 23 2010
parent bearophile <bearophileHUGS lycos.com> writes:
Don:
 But more importantly, I cannot see any reason to try. I cannot see any 
 reason why the if (x) syntax is desirable for floats. I can see many 
 downsides and opportunities for bugs, but I cannot see ANYTHING good 
 about it at all. It's a silly thing to have in the core language.
 
 We don't need it. Get rid of it.
OK :-) Thank you. Bye, bearophile
Mar 24 2010
prev sibling parent reply bearophile <bearophileHUGS lycos.com> writes:
Don:
Sometimes NaNs are true, sometimes not!<
Urg.
 That won't work in this case, unless you make nan a keyword. There are 
 2^^62 different NaNs, so a bitwise comparison will not work.
You are right, I did forget that here. So I have modified a little my request: http://d.puremagic.com/issues/show_bug.cgi?id=3981 "x is double.init" can be used to detect uninitialized variables, because in this proposal "is" performs a bitwise comparison (this makes "is" not that useful with structs). The syntax "x == nan" can be used to perform the smart comparison with all possible nans. But this is an uncommon operation, so isnan(x) is good enough. Bye and thank you for your insights, bearophile
Mar 24 2010
parent reply Paul D. Anderson <paul.d.removethis.anderson comcast.andthis.net> writes:
bearophile Wrote:

 Don:
Sometimes NaNs are true, sometimes not!<
Urg.
 That won't work in this case, unless you make nan a keyword. There are 
 2^^62 different NaNs, so a bitwise comparison will not work.
You are right, I did forget that here. So I have modified a little my request: http://d.puremagic.com/issues/show_bug.cgi?id=3981 "x is double.init" can be used to detect uninitialized variables, because in this proposal "is" performs a bitwise comparison (this makes "is" not that useful with structs). The syntax "x == nan" can be used to perform the smart comparison with all possible nans. But this is an uncommon operation, so isnan(x) is good enough. Bye and thank you for your insights, bearophile
There are a finite number of NaNs for a fixed-length floating point number, but there are a (conceptually) infinite number for an arbitrary precision float. The sign, coefficient and exponent are undefined for a NaN, although the coefficient and sign fields can be used as a "payload" for the NaN, containing diagnostic information, etc. And, according to the General Decimal Arithmetic Specification (http://speleotrove.com/decimal) comparing NaN with any number, including another NaN, will never return true. The operation raises an "Invalid Operation" exception. Paul
Mar 24 2010
parent reply bearophile <bearophileHUGS lycos.com> writes:
Paul D. Anderson:

the coefficient and sign fields can be used as a "payload" for the NaN,
containing diagnostic information, etc.<
They are the tagged NaNs. Do you know of a possible usage of them? :-)
 And, according to the General Decimal Arithmetic Specification
(http://speleotrove.com/decimal) comparing NaN with any number, including
another NaN, will never return true.<
(Note: I am not proposing of introducing "== nan" in D, that enhancement request is just about the "is" operator). There are situations where you want to know if a number is any NaN (or if it's specifically the NaN used by D to initialize floats/reals/doubles). The std.math.isNaN() function is useful. Bye, bearophile
Mar 24 2010
parent Paul D. Anderson <paul.d.removethis.anderson comcast.andthis.net> writes:
bearophile Wrote:

 Paul D. Anderson:
 
the coefficient and sign fields can be used as a "payload" for the NaN,
containing diagnostic information, etc.<
They are the tagged NaNs. Do you know of a possible usage of them? :-)
I believe Tango uses them. I haven't looked at their implementation but a reasonable use would be to give information about why the exception was raised; e.g. there are a lot of ways an "Invalid Operation" flag can be raised and the tag can distinguish them. 1 = invalid operand, 2 = trying to add -infinity to infinity, etc. The other issue is that it is a valid implementation to leave the fields unchanged when the number is converted to a NaN, so that they could have any content at all. (The specification states that the fields are undefined.) This would defeat a bitwise comparison.
 
 And, according to the General Decimal Arithmetic Specification
(http://speleotrove.com/decimal) comparing NaN with any number, including
another NaN, will never return true.<
(Note: I am not proposing of introducing "== nan" in D, that enhancement request is just about the "is" operator). There are situations where you want to know if a number is any NaN (or if it's specifically the NaN used by D to initialize floats/reals/doubles). The std.math.isNaN() function is useful. Bye, bearophile
I hear you, but I'm leary of changing the meaning of the "is" keyword for this one case. The general decimal spec also requires a "compare-total" operation which always returns (-1, 0, 1) for numbers and Nans. From the spec: "The total ordering is defined as follows. 1.The following items describe the ordering for representations whose sign is 0. If the sign is 1, the order is reversed. A representation with a sign of 1 is always lower in the ordering than one with a sign of 0. 2.Numbers (representations which are not NaNs) are ordered such that a larger numerical value is higher in the ordering. If two representations have the same numerical value then the exponent is taken into account; larger (more positive) exponents are higher in the ordering. 3.All quiet NaNs are higher in the total ordering than all signaling NaNs. 4.Quiet NaNs and signaling NaNs are ordered according to their payload; a larger payload is higher in the ordering." That seems complete, but it doesn't make sense (to me) to implement it as "is" (totalCompare (x,y) == 0) Paul
Mar 24 2010