October 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.

October 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:
>> 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.


October 23, 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


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



October 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
October 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.
October 24, 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.

October 24, 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:
>> ---
>> /**
>>  * 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.





October 24, 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.
October 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.
October 24, 2017
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