www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - So why double to float conversion is implicit ?

reply NX <nightmarex1337 hotmail.com> writes:
I was working on some sort of math library for use in graphical 
computing and I wrote something like this:

const float PiOver2 = (atan(1.0) * 4) / 2;

Interestingly enough, I realized that atan() returns double (in 
this case) but wait, it's assigned to a float variable! Compiler 
didn't even emit warnings, let alone errors.

I see no reason as to why would this be legal in this century, 
especially in D.
So can someone tell me what's the argument against this?
Why type conversions differ between integral and floating types?
Why can't I assign a long to an int but it's fine when assigning 
double to float?

I think this is a serious topic and needs clarification.
Oct 21
next sibling parent user1234 <user1234 12.nl> writes:
On Saturday, 21 October 2017 at 20:17:12 UTC, NX wrote:
 I was working on some sort of math library for use in graphical 
 computing and I wrote something like this:

 const float PiOver2 = (atan(1.0) * 4) / 2;

 Interestingly enough, I realized that atan() returns double (in 
 this case) but wait, it's assigned to a float variable! 
 Compiler didn't even emit warnings, let alone errors.

 I see no reason as to why would this be legal in this century, 
 especially in D.
 So can someone tell me what's the argument against this?
 Why type conversions differ between integral and floating types?
 Why can't I assign a long to an int but it's fine when 
 assigning double to float?

 I think this is a serious topic and needs clarification.
D is compliant with C++ in this case (see http://en.cppreference.com/w/cpp/language/implicit_conversion) so the question is rather why does C++ allow this conversion. When you convert a double to a float you're more likely to have a precision loss while when you convert an ulong to an int you risk a most serious data loss.
Oct 21
prev sibling next sibling parent reply codephantom <me noyb.com> writes:
On Saturday, 21 October 2017 at 20:17:12 UTC, NX wrote:
 Interestingly enough, I realized that atan() returns double (in 
 this case) but wait, it's assigned to a float variable! 
 Compiler didn't even emit warnings, let alone errors.
There a few lessons here. (1) D is not Java ;-) (2) Know what types are being returned from your calls, before you call them. (3) Know what the language spec says about conversions (and their order): - https://dlang.org/spec/type.html (4) If unsure, test it: - https://dlang.org/phobos/std_traits.html#isImplicitlyConvertible Only then should you start coding ;-) oh...and... (5) Don't waste time arguing with the spec ;-) (6) Don't expect the compiler to not comply with the spec
Oct 21
next sibling parent reply NX <nightmarex1337 hotmail.com> writes:
On Sunday, 22 October 2017 at 02:25:44 UTC, codephantom wrote:
 On Saturday, 21 October 2017 at 20:17:12 UTC, NX wrote:
 Interestingly enough, I realized that atan() returns double 
 (in this case) but wait, it's assigned to a float variable! 
 Compiler didn't even emit warnings, let alone errors.
There a few lessons here. (1) D is not Java ;-)
D is not C/C++ either. I fail to see how does it help to be C++ compliant. It's absolutely trivial to tell the compiler that conversion is on purpose by explicitly casting and it immensely helps to reduce bugs (since we are humans after all).
 (2) Know what types are being returned from your calls, before 
 you call them.
 (3) Know what the language spec says about conversions (and 
 their order):
     - https://dlang.org/spec/type.html
 (4) If unsure, test it:
     - 
 https://dlang.org/phobos/std_traits.html#isImplicitlyConvertible

 Only then should you start coding ;-)
It never crossed my mind that D would allow such type conversion since I don't recall any language that markets itself as _modern_ allows it.
 oh...and...

 (5) Don't waste time arguing with the spec ;-)
 (6) Don't expect the compiler to not comply with the spec
I just think spec should be reviewed at this point.
Oct 22
next sibling parent Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= writes:
On Sunday, 22 October 2017 at 10:57:24 UTC, NX wrote:
 D is not C/C++ either. I fail to see how does it help to be C++ 
 compliant. It's absolutely trivial to tell the compiler that 
 conversion is on purpose by explicitly casting and it immensely 
 helps to reduce bugs (since we are humans after all).
Right, which is why C++ compilers will warn you about conversions that can be lossy. A lot of stuff that is valid C++ will be detected and complained about in typical production setups. The D community thinks that all warnings should be errors, so I really don't think the argument that C++ allows something is a good one.
Oct 22
prev sibling next sibling parent reply Basile B. <b2.temp gmx.com> writes:
On Sunday, 22 October 2017 at 10:57:24 UTC, NX wrote:
 On Sunday, 22 October 2017 at 02:25:44 UTC, codephantom wrote:
 On Saturday, 21 October 2017 at 20:17:12 UTC, NX wrote:
 Interestingly enough, I realized that atan() returns double 
 (in this case) but wait, it's assigned to a float variable! 
 Compiler didn't even emit warnings, let alone errors.
There a few lessons here. (1) D is not Java ;-)
D is not C/C++ either. I fail to see how does it help to be C++ compliant. It's absolutely trivial to tell the compiler that conversion is on purpose by explicitly casting and it immensely helps to reduce bugs (since we are humans after all).
 (2) Know what types are being returned from your calls, before 
 you call them.
 (3) Know what the language spec says about conversions (and 
 their order):
     - https://dlang.org/spec/type.html
 (4) If unsure, test it:
     - 
 https://dlang.org/phobos/std_traits.html#isImplicitlyConvertible

 Only then should you start coding ;-)
It never crossed my mind that D would allow such type conversion since I don't recall any language that markets itself as _modern_ allows it.
 oh...and...

 (5) Don't waste time arguing with the spec ;-)
 (6) Don't expect the compiler to not comply with the spec
I just think spec should be reviewed at this point.
Fortunately D provides enough to simplify self-discipline: --- import std.traits; struct FP(T) if (isFloatingPoint!T) { T _value; alias _value this; void antiCoercion(V)() { static assert(V.sizeof <= T.sizeof, "C++ float coercion is not allowed"); } this(V)(V v) {antiCoercion!V; _value = v;} void opAssign(V)(V v) {antiCoercion!V; _value = v;} } void main() { import std.math; FP!single PiOver2 = (atan(1.0) * 4) / 2; } ---
Oct 22
parent Basile B. <b2.temp gmx.com> writes:
On Sunday, 22 October 2017 at 12:17:49 UTC, Basile B. wrote:
 On Sunday, 22 October 2017 at 10:57:24 UTC, NX wrote:
 [...]
Fortunately D provides enough to simplify self-discipline: --- import std.traits; struct FP(T) if (isFloatingPoint!T) { T _value; alias _value this; void antiCoercion(V)() { static assert(V.sizeof <= T.sizeof, "C++ float coercion is not allowed"); } this(V)(V v) {antiCoercion!V; _value = v;} void opAssign(V)(V v) {antiCoercion!V; _value = v;} } void main() { import std.math; FP!single PiOver2 = (atan(1.0) * 4) / 2; } ---
Oh, I meant to write "FP!float PiOver2 = (atan(1.0) * 4) / 2;"
Oct 22
prev sibling next sibling parent codephantom <me noyb.com> writes:
On Sunday, 22 October 2017 at 10:57:24 UTC, NX wrote:
 I just think spec should be reviewed at this point.
Well, as Andrei famously once said, don't argue with the language, just build stuff. So in that spirit, lets not argue with the language specification, but find a solution that meets your needs too... In C/C++, Clang already has a compile time option ( -Wconversion) to warn of implicit conversions. I cannot see such an option in dmd or ldc (and I don't use gdc, so don't know about that one). I think such an option would be ideal, and perhaps really should already be there.
Oct 22
prev sibling parent Jerry <hurricane hereiam.com> writes:
On Sunday, 22 October 2017 at 10:57:24 UTC, NX wrote:
 On Sunday, 22 October 2017 at 02:25:44 UTC, codephantom wrote:
 On Saturday, 21 October 2017 at 20:17:12 UTC, NX wrote:
 Interestingly enough, I realized that atan() returns double 
 (in this case) but wait, it's assigned to a float variable! 
 Compiler didn't even emit warnings, let alone errors.
There a few lessons here. (1) D is not Java ;-)
D is not C/C++ either. I fail to see how does it help to be C++ compliant. It's absolutely trivial to tell the compiler that conversion is on purpose by explicitly casting and it immensely helps to reduce bugs (since we are humans after all).
It is pretty close, I converted a C++ project by copying and pasting most of the code. A bigger issue is how CTFE uses "real" for everything, even at times you specify it not to use it. If you use an enum and specify "double" or "float", it might very well store it as a "real" in memory. That's the more troubling implicit conversion happening with D that has no real benefit or purpose, other than someone thought it was a good idea cause real's have more precision so they should be the default.
Oct 22
prev sibling parent reply User <u u.com> writes:
Is there a list of such quirks or gotchas in dlang?

The ones I know of are

1. Implicit conversion from double to float

2. Integer division results in integer result truncation the 
fractional part.

3. ??
Oct 22
next sibling parent jmh530 <john.michael.hall gmail.com> writes:
On Sunday, 22 October 2017 at 13:41:23 UTC, User wrote:
 Is there a list of such quirks or gotchas in dlang?

 The ones I know of are

 1. Implicit conversion from double to float

 2. Integer division results in integer result truncation the 
 fractional part.

 3. ??
#2 is common for statically typed languages. I find implicit conversions to be the most common gotchas I've experienced, probably a few other things, but I can't recall.
Oct 22
prev sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 10/22/17 9:41 AM, User wrote:
 Is there a list of such quirks or gotchas in dlang?
 
 The ones I know of are
 
 1. Implicit conversion from double to float
 
 2. Integer division results in integer result truncation the fractional 
 part.
These are not gotchas, as TDPL explains. One unpleasant related gotcha is implicit conversions across signedness and comparison of integral types with different signedness. -- Andrei
Oct 22
parent aberba <karabutaworld gmail.com> writes:
On Sunday, 22 October 2017 at 14:59:41 UTC, Andrei Alexandrescu 
wrote:
 On 10/22/17 9:41 AM, User wrote:
 Is there a list of such quirks or gotchas in dlang?
 
 The ones I know of are
 
 1. Implicit conversion from double to float
 
 2. Integer division results in integer result truncation the 
 fractional part.
These are not gotchas, as TDPL explains. One unpleasant related gotcha is implicit conversions across signedness and comparison of integral types with different signedness. -- Andrei
Shouldn't all these be documented?
Oct 28
prev sibling next sibling parent codephantom <me noyb.com> writes:
On Saturday, 21 October 2017 at 20:17:12 UTC, NX wrote:
 I think this is a serious topic and needs clarification.
Just out of interest, as opposed to wanting to debate the merits... I did a little investigation of how various languages deal with floating point precision (by default) for standard output. It is certainly interesting that there are differences - mostly one way or the other, rather than huge differences. C ------- double d = 1.12345678912345; printf("%lf\n", d); // C writes -> 1.123457 (some rounding here) C++ ---- double d = 1.12345678912345; std::cout << d << std::endl; // c++ writes -> 1.123456 Rust ---- let d = 1.12345678912345; println!("{}", d); // Rust writes -> 1.1234568 (some rounding here) D --- double d = 1.12345678912345; writeln(d); // D writes -> 1.12346 Java ---- double d = 1.12345678912345; System.out.println(d); // java writes -> 1.12345678912345 C# ---- double d = 1.12345678912345; System.Console.WriteLine(d); // C# writes -> 1.12345678912345 Python ------ d = 1.12345678912345; print(d); // python writes -> 1.12345678912345 Go --- d := 1.12345678912345; fmt.Println(d); // Go prints -> 1.12345678912345 Swift ----- let d = 1.12345678912345; print(d); // Swift prints -> 1.12345678912345
Oct 23
prev sibling next sibling parent Atila Neves <atila.neves gmail.com> writes:
On Saturday, 21 October 2017 at 20:17:12 UTC, NX wrote:
 I was working on some sort of math library for use in graphical 
 computing and I wrote something like this:

 const float PiOver2 = (atan(1.0) * 4) / 2;

 Interestingly enough, I realized that atan() returns double (in 
 this case) but wait, it's assigned to a float variable! 
 Compiler didn't even emit warnings, let alone errors.

 I see no reason as to why would this be legal in this century, 
 especially in D.
 So can someone tell me what's the argument against this?
 Why type conversions differ between integral and floating types?
 Why can't I assign a long to an int but it's fine when 
 assigning double to float?

 I think this is a serious topic and needs clarification.
I think an issue here is specifying `float` to begin with. I'd only do that if I wanted it to be `float`, knowing that the expression is a different type, and even then I'd add a comment explaining why I felt the need to be explicity. i.e. I'd have written const PiOver2 = (atan(1.0) * 4) / 2; Atila
Oct 23
prev sibling next sibling parent reply Basile B. <b2.temp gmx.com> writes:
On Saturday, 21 October 2017 at 20:17:12 UTC, NX wrote:
 I was working on some sort of math library for use in graphical 
 computing and I wrote something like this:

 const float PiOver2 = (atan(1.0) * 4) / 2;

 Interestingly enough, I realized that atan() returns double (in 
 this case) but wait, it's assigned to a float variable! 
 Compiler didn't even emit warnings, let alone errors.

 I see no reason as to why would this be legal in this century, 
 especially in D.
 So can someone tell me what's the argument against this?
 Why type conversions differ between integral and floating types?
 Why can't I assign a long to an int but it's fine when 
 assigning double to float?

 I think this is a serious topic and needs clarification.
In the meantime: --- /** * Wraps a floating point type that doesn't follow D permissive float conversion * rules. * * In D, as in C++, implicit conversion from $(D double) to $(D float) is allowed, * leading to a possible precision loss. This can't happen when using this wrapper. */ struct CoercionSafeFloat(T) if (isFloatingPoint!T) { private T _value; alias _value this; enum antiCoercion = q{ static assert(V.sizeof <= T.sizeof, "coercion from " ~ V.stringof ~ " to " ~ T.stringof ~ " is not allowed"); }; /// Prevent Coercion from construction. this(V)(V v) {mixin(antiCoercion); _value = v;} /// Prevent Coercion from assignation. void opAssign(V)(V v) {mixin(antiCoercion); _value = v;} /// Prevent Coercion from operator assignation. void opOpAssign(string op, V)(V v) { mixin(antiCoercion); mixin("_value " ~ op ~ "= v;"); } } /// unittest { alias Float = CoercionSafeFloat!float; alias Double = CoercionSafeFloat!double; alias Real = CoercionSafeFloat!real; Float f; Double d; Real r; import std.math; static assert(!__traits(compiles, f = (atan(1.0) * 4) / 2)); static assert( __traits(compiles, f = (atan(1.0f) * 4f) / 2f)); static assert(!__traits(compiles, d = (atan(1.0L) * 4L) / 2L)); static assert(__traits(compiles, d = f)); static assert(!__traits(compiles, f = d)); static assert(!__traits(compiles, d = r)); static assert(!__traits(compiles, d += r)); static assert(__traits(compiles, r *= d)); } --- you can get a safer float type if this is a concern.
Oct 23
parent reply codephantom <me noyb.com> writes:
On Monday, 23 October 2017 at 21:51:24 UTC, Basile B. wrote:
 ---
 /**
  * Wraps a floating point type that doesn't follow D permissive 
 float conversion
  * rules.
  *
  * In D, as in C++, implicit conversion from $(D double) to $(D 
 float) is allowed,
  * leading to a possible precision loss. This can't happen when 
 using this wrapper.
  */
Want to hammer in a nail.. just go ahead and use a bulldozer ;-) Simple things should be simple. I cannot see how the simplest solution would be any other than to have a compiler option for warning you of implicit conversions.
Oct 23
parent reply Basile B. <b2.temp gmx.com> writes:
On Tuesday, 24 October 2017 at 01:22:57 UTC, codephantom wrote:
 On Monday, 23 October 2017 at 21:51:24 UTC, Basile B. wrote:
 ---
 /**
  * Wraps a floating point type that doesn't follow D 
 permissive float conversion
  * rules.
  *
  * In D, as in C++, implicit conversion from $(D double) to 
 $(D float) is allowed,
  * leading to a possible precision loss. This can't happen 
 when using this wrapper.
  */
Want to hammer in a nail.. just go ahead and use a bulldozer ;-) Simple things should be simple. I cannot see how the simplest solution would be any other than to have a compiler option for warning you of implicit conversions.
I'm gonna propose the change in a PR. But I already see problems... One may call a trigo function with the highest precision possible so that the FP coprocessor get more accurate internally. The result, even if converted from real to float is better. This is exactly what happens here: https://github.com/dlang/phobos/pull/5804/files#diff-f127f38af25baf8333b65fa51824b92fR3037 To be clear, w/ the patch a warning is emitted for the first cos: void main() { import std.math; float f0 = cos(2 * PI / 1.123548789545545646452154L); float f1 = cos(cast(float)(2 * PI / 1.123548789545545646452154L)); writefln("%.8g - %.8g", f0, f1); } but paradoxically the first is more accurate.
Oct 24
parent reply Fool <fool dlang.org> writes:
On Tuesday, 24 October 2017 at 14:28:20 UTC, Basile B. wrote:
     float f0 = cos(2 * PI /  1.123548789545545646452154L);
     float f1 = cos(cast(float)(2 * PI / 
 1.123548789545545646452154L));

 [...]

 but paradoxically the first is more accurate.
There is no paradox. That's comparing apples with oranges. No offense. all: It is sad to see how parts of the community are losing their distance to the project and even put a gloss on completely absurd design decisions.
Oct 24
next sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 10/24/2017 12:28 PM, Fool wrote:
 On Tuesday, 24 October 2017 at 14:28:20 UTC, Basile B. wrote:
     float f0 = cos(2 * PI /  1.123548789545545646452154L);
     float f1 = cos(cast(float)(2 * PI / 1.123548789545545646452154L));

 [...]

 but paradoxically the first is more accurate.
There is no paradox. That's comparing apples with oranges. No offense. all: It is sad to see how parts of the community are losing their distance to the project and even put a gloss on completely absurd design decisions.
For the record, I remember the points made: * When converting across integral types, any failed coercion yields a value essentially unrelated to the source. So we deemed these unacceptable. * When converting int to float or long to double, there is a loss of accuracy - a float can represent all ints, but some of them will be represented with approximation. But here there _is_ a notion of "I'm okay with an approximation". So we deemed these acceptable. * When converting double to float, again there is a loss of accuracy and also the risk of too large values. But there's never a gross mistake - too large values are converted to infinity, and otherwise an approximation occurs. This was also deemed acceptable. Folks with other design sensibilities might choose differently. Actually I wasn't on board with this decision, but I accepted it. But deeming it absurd is a bit of a stretch. Andrei
Oct 24
parent Fool <fool dlang.org> writes:
On Tuesday, 24 October 2017 at 17:54:55 UTC, Andrei Alexandrescu 
wrote:
 For the record, I remember the points made:

 * When converting across integral types, any failed coercion 
 yields a value essentially unrelated to the source. So we 
 deemed these unacceptable.

 * When converting int to float or long to double, there is a 
 loss of accuracy - a float can represent all ints, but some of 
 them will be represented with approximation. But here there 
 _is_ a notion of "I'm okay with an approximation". So we deemed 
 these acceptable.

 * When converting double to float, again there is a loss of 
 accuracy and also the risk of too large values. But there's 
 never a gross mistake - too large values are converted to 
 infinity, and otherwise an approximation occurs. This was also 
 deemed acceptable.

 Folks with other design sensibilities might choose differently. 
 Actually I wasn't on board with this decision, but I accepted 
 it. But deeming it absurd is a bit of a stretch.
Thank you for the explanation. I understand the reasoning. It seems strange to me that D is aiming at security concerning memory management and integer arithmetic but not with respect to the processing of floating-point entities. To clarify: this topic was not the reason but only the occasion for my ranting. It's just a pointed expression of my personal impression that I got from a few discussions here. I would like to stress that there are also people in the community who do not reflexively dismiss every critical objection from the outside. In this context, "problem realized, won't fix nevertheless" seems to be a much better answer than denying existence of problems brought up by new or less known people.
Oct 25
prev sibling parent reply codephantom <me noyb.com> writes:
On Tuesday, 24 October 2017 at 16:28:03 UTC, Fool wrote:
  all: It is sad to see how parts of the community are losing 
 their distance to the project and even put a gloss on 
 completely absurd design decisions.
Consensus within groups is kind of important actually...it encourages harmony and coherence..with which.. there is no group. But just as important, is being aware of the danger of groupthink... Many (implicitly) understand the former, few understand (or are even aware of) the latter.. https://www.jstor.org/stable/3791464?seq=1#page_scan_tab_contents Not that I'm suggesting anything here other than... don't become the victim of groupthink.
Oct 24
parent reply codephantom <me noyb.com> writes:
On Wednesday, 25 October 2017 at 01:06:40 UTC, codephantom wrote:
 Consensus within groups is kind of important actually...it 
 encourages harmony and coherence..with which.. there is no 
 group.
if only we could edit our post to correct it...when's that going to happen btw. ;-) of course I meant .. 'without which' ...not 'with which' ... btw. further below is a good study in group dynamics - I like the phrase "Stupidity certainly is not the explanation". (second paragraph) http://web.mit.edu/curhan/www/docs/Articles/15341_Readings/Group_Dynamics/Janis_Groupthink_from_Psych_Today.pdf
Oct 24
parent Fool <fool dlang.org> writes:
On Wednesday, 25 October 2017 at 01:24:01 UTC, codephantom wrote:
 http://web.mit.edu/curhan/www/docs/Articles/15341_Readings/Group_Dynamics/Janis_Groupthink_from_Psych_Today.pdf
Great read, thank you for sharing!
Oct 25
prev sibling parent reply Basile B. <b2.temp gmx.com> writes:
On Saturday, 21 October 2017 at 20:17:12 UTC, NX wrote:
 I was working on some sort of math library for use in graphical 
 computing and I wrote something like this:

 const float PiOver2 = (atan(1.0) * 4) / 2;

 Interestingly enough, I realized that atan() returns double (in 
 this case) but wait, it's assigned to a float variable! 
 Compiler didn't even emit warnings, let alone errors.

 I see no reason as to why would this be legal in this century, 
 especially in D.
 So can someone tell me what's the argument against this?
 Why type conversions differ between integral and floating types?
 Why can't I assign a long to an int but it's fine when 
 assigning double to float?

 I think this is a serious topic and needs clarification.
I think that the rationale for allowing that is that you can feed the FPU with high precision values to get a better internal accuracy (temp values stored in ST0-ST7). At the end, even if there's a truncation the result is more accurate that if you would have feed the FPU with parameters of the same size as the result.
Oct 24
parent Basile B. <b2.temp gmx.com> writes:
On Tuesday, 24 October 2017 at 15:29:38 UTC, Basile B. wrote:
 On Saturday, 21 October 2017 at 20:17:12 UTC, NX wrote:
 I was working on some sort of math library for use in 
 graphical computing and I wrote something like this:

 const float PiOver2 = (atan(1.0) * 4) / 2;

 Interestingly enough, I realized that atan() returns double 
 (in this case) but wait, it's assigned to a float variable! 
 Compiler didn't even emit warnings, let alone errors.

 I see no reason as to why would this be legal in this century, 
 especially in D.
 So can someone tell me what's the argument against this?
 Why type conversions differ between integral and floating 
 types?
 Why can't I assign a long to an int but it's fine when 
 assigning double to float?

 I think this is a serious topic and needs clarification.
I think that the rationale for allowing that is that you can feed the FPU with high precision values to get a better internal accuracy (temp values stored in ST0-ST7). At the end, even if there's a truncation the result is more accurate that if you would have feed the FPU with parameters of the same size as the result.
I'm currently using the compiler version that includes the warning: https://github.com/dlang/dmd/pull/7240 and discovered that it's actually not only a problem of truncation. For ``` float foo(float f) { f *= 0.5; return f; } ``` DMD generates ``` push rbp mov rbp, rsp cvtss2sd xmm1, xmm0 ; promotion movsd xmm2, qword ptr [<addr of constant>] mulsd xmm1, xmm2 cvtsd2ss xmm0, xmm1 ; truncation pop rbp ret ``` as you can see, the parameter is promoted to double because of the constant and then the result is converted back to single. The two instructions obviously disappear when the constant get the float suffix, and the compiler warning helped you to save a few cycles.
Oct 27