digitalmars.D - So why double to float conversion is implicit ?
- NX (13/13) Oct 21 2017 I was working on some sort of math library for use in graphical
- user1234 (7/20) Oct 21 2017 D is compliant with C++ in this case (see
- codephantom (15/18) Oct 21 2017 There a few lessons here.
- NX (9/28) Oct 22 2017 D is not C/C++ either. I fail to see how does it help to be C++
- Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= (7/11) Oct 22 2017 Right, which is why C++ compilers will warn you about conversions
- Basile B. (23/55) Oct 22 2017 Fortunately D provides enough to simplify self-discipline:
- Basile B. (2/26) Oct 22 2017 Oh, I meant to write "FP!float PiOver2 = (atan(1.0) * 4) / 2;"
- codephantom (11/12) Oct 22 2017 Well, as Andrei famously once said, don't argue with the
- Jerry (10/24) Oct 22 2017 It is pretty close, I converted a C++ project by copying and
- User (6/6) Oct 22 2017 Is there a list of such quirks or gotchas in dlang?
- jmh530 (4/10) Oct 22 2017 #2 is common for statically typed languages.
- Andrei Alexandrescu (4/12) Oct 22 2017 These are not gotchas, as TDPL explains. One unpleasant related gotcha
- aberba (3/15) Oct 28 2017 Shouldn't all these be documented?
- codephantom (44/45) Oct 23 2017 Just out of interest, as opposed to wanting to debate the
- Atila Neves (8/21) Oct 23 2017 I think an issue here is specifying `float` to begin with. I'd
- Basile B. (56/69) Oct 23 2017 In the meantime:
- codephantom (5/16) Oct 23 2017 Want to hammer in a nail.. just go ahead and use a bulldozer ;-)
- Basile B. (18/35) Oct 24 2017 I'm gonna propose the change in a PR. But I already see
- Fool (6/11) Oct 24 2017 There is no paradox. That's comparing apples with oranges. No
- Andrei Alexandrescu (16/29) Oct 24 2017 For the record, I remember the points made:
- Fool (14/31) Oct 25 2017 Thank you for the explanation. I understand the reasoning. It
- codephantom (10/13) Oct 24 2017 Consensus within groups is kind of important actually...it
- codephantom (8/11) Oct 24 2017 if only we could edit our post to correct it...when's that going
- Fool (2/3) Oct 25 2017 Great read, thank you for sharing!
- Basile B. (7/20) Oct 24 2017 I think that the rationale for allowing that is that you can feed
- Basile B. (30/55) Oct 27 2017 I'm currently using the compiler version that includes the
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 2017
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 2017
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 2017
On Sunday, 22 October 2017 at 02:25:44 UTC, codephantom wrote:On Saturday, 21 October 2017 at 20:17:12 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).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 ;-)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 specI just think spec should be reviewed at this point.
Oct 22 2017
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 2017
On Sunday, 22 October 2017 at 10:57:24 UTC, NX wrote:On Sunday, 22 October 2017 at 02:25:44 UTC, codephantom 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; } ---On Saturday, 21 October 2017 at 20:17:12 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).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 ;-)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 specI just think spec should be reviewed at this point.
Oct 22 2017
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:Oh, I meant to write "FP!float PiOver2 = (atan(1.0) * 4) / 2;"[...]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 2017
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 2017
On Sunday, 22 October 2017 at 10:57:24 UTC, NX wrote:On Sunday, 22 October 2017 at 02:25:44 UTC, codephantom wrote: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.On Saturday, 21 October 2017 at 20:17:12 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).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 ;-)
Oct 22 2017
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 2017
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. ??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 2017
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 2017
On Sunday, 22 October 2017 at 14:59:41 UTC, Andrei Alexandrescu wrote:On 10/22/17 9:41 AM, User wrote:Shouldn't all these be documented?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 28 2017
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 ---- double d = 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 2017
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 2017
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 2017
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 2017
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: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.--- /** * 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 24 2017
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 2017
On 10/24/2017 12:28 PM, Fool wrote:On Tuesday, 24 October 2017 at 14:28:20 UTC, Basile B. 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. Andreifloat 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 2017
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 2017
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 2017
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 2017
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.pdfGreat read, thank you for sharing!
Oct 25 2017
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 2017
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'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.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 27 2017