Thread overview
Floating point in the type system
Sep 12, 2015
Robert
Sep 12, 2015
Atila Neves
Sep 12, 2015
Robert
Sep 12, 2015
Xinok
Sep 12, 2015
anonymous
Sep 12, 2015
Robert
September 12, 2015
Hi all,

I came across this example, and wondered what your thoughts on it are:


```
void main(string[] args)
{
    struct Foo(float f) {
        alias VAL = f;
        float getF() {
            return f;
        }
    }

    Foo!(float.nan) f;
    Foo!(float.nan) f2;

    // This will fail at compile time
    static assert(f.VAL == f2.VAL);

    // This will fail at run time
    assert(f.getF() == f2.getF());

    // But this is ok
    f = f2;
}
```

It seems a little unusual to me.

Robert
September 12, 2015
On Saturday, 12 September 2015 at 15:13:27 UTC, Robert wrote:
> Hi all,
>
> I came across this example, and wondered what your thoughts on it are:
>
>
> ```
> void main(string[] args)
> {
>     struct Foo(float f) {
>         alias VAL = f;
>         float getF() {
>             return f;
>         }
>     }
>
>     Foo!(float.nan) f;
>     Foo!(float.nan) f2;
>
>     // This will fail at compile time
>     static assert(f.VAL == f2.VAL);
>
>     // This will fail at run time
>     assert(f.getF() == f2.getF());
>
>     // But this is ok
>     f = f2;
> }
> ```
>
> It seems a little unusual to me.
>
> Robert

What do think is unusual?

Atila

September 12, 2015
On Saturday, 12 September 2015 at 15:49:23 UTC, Atila Neves wrote:
> On Saturday, 12 September 2015 at 15:13:27 UTC, Robert wrote:
>> Hi all,
>>
>> I came across this example, and wondered what your thoughts on it are:
>>
>>
>> ```
>> void main(string[] args)
>> {
>>     struct Foo(float f) {
>>         alias VAL = f;
>>         float getF() {
>>             return f;
>>         }
>>     }
>>
>>     Foo!(float.nan) f;
>>     Foo!(float.nan) f2;
>>
>>     // This will fail at compile time
>>     static assert(f.VAL == f2.VAL);
>>
>>     // This will fail at run time
>>     assert(f.getF() == f2.getF());
>>
>>     // But this is ok
>>     f = f2;
>> }
>> ```
>>
>> It seems a little unusual to me.
>>
>> Robert
>
> What do think is unusual?
>
> Atila

It's unusual, because `float.nan != float.nan`, so one might expect that `typeof(Foo!(float.nan) != Foo!(float.nan))`, whereas this is clearly not the case, even with both the static assert and runtime assert failing. I'm just curious to understand the reasoning behind this, whether it's intentional, and whether it matters at all.
September 12, 2015
On Saturday, 12 September 2015 at 16:08:31 UTC, Robert wrote:
> On Saturday, 12 September 2015 at 15:49:23 UTC, Atila Neves wrote:
>> What do think is unusual?
>>
>> Atila
>
> It's unusual, because `float.nan != float.nan`, so one might expect that `typeof(Foo!(float.nan) != Foo!(float.nan))`, whereas this is clearly not the case, even with both the static assert and runtime assert failing. I'm just curious to understand the reasoning behind this, whether it's intentional, and whether it matters at all.

(1) f = f2; // This is assignment, not comparison
(2) alias VAL = f; // This is not a data member so is not involved in comparisons or assignments

change "alias VAL" to "float VAL" and then you might see the behavior you expect.
September 12, 2015
On Saturday 12 September 2015 18:08, Robert wrote:

> It's unusual, because `float.nan != float.nan`, so one might expect that `typeof(Foo!(float.nan) != Foo!(float.nan))`, whereas this is clearly not the case, even with both the static assert and runtime assert failing. I'm just curious to understand the reasoning behind this, whether it's intentional, and whether it matters at all.

I don't know what the compiler actually does, but it looks like the comparison of template value arguments doesn't use equality, but something more akin to `is` instead (bitwise equality).

If that's right, then `is(Foo!(float.nan) == Foo!(float.nan))` holds because `float.nan is float.nan` holds.

Same behavior with a struct instead of float:
----
struct S
{
    bool opEquals(S other) {return false;}
}
struct Foo(S s)
{
}
static assert(S.init != S.init); /* not equal */
static assert(S.init is S.init); /* but bit for bit identical */
static assert(is(Foo!(S.init) == Foo!(S.init)));
----

September 12, 2015
On Saturday, 12 September 2015 at 16:08:31 UTC, Robert wrote:
> assert and runtime assert failing. I'm just curious to understand the reasoning behind this, whether it's intentional, and whether it matters at all.

Types need to mangle to the same name, but using floats in a type name is usually not a good idea. Try pi... How many decimals? A roundoff error and you get a new type.
September 12, 2015
On Saturday, 12 September 2015 at 15:13:27 UTC, Robert wrote:
> Hi all,
>
> I came across this example, and wondered what your thoughts on it are:
>
>
> <snip>
>
> It seems a little unusual to me.
>
> Robert

For what it's worth, I was investigating this initially as a discussion about adding type-level values in Rust, and how to handle unusual cases like this. In the process we've managed to break the Idris type system: https://github.com/idris-lang/Idris-dev/issues/2609. There's been quite a lot of interesting discussion about it on the IRC channels for all three languages :) I'd be interested to know how other languages handle this, if anyone knows.
September 12, 2015
On Saturday, 12 September 2015 at 19:02:16 UTC, Robert wrote:
> For what it's worth, I was investigating this initially as a discussion about adding type-level values in Rust, and how to handle unusual cases like this.

C++ does not allow it. And frankly, having more than a single integer value type as a value parameter in C++ templates is a pointless PITA.

I think Go got literal constant values right: make them untyped until bound.

September 12, 2015
On Saturday, 12 September 2015 at 19:02:16 UTC, Robert wrote:
> handle unusual cases like this. In the process we've managed to break the Idris type system: https://github.com/idris-lang/Idris-dev/issues/2609.

I don't know Idris, but you can't easily unify over floats, because they are samples on an interval, and don't behave like real numbers, but more like underspecified intervals from interval arithmetics.

If you want to unify over reals, you probably should do it symbolically.

Or just treat float values as literal symbols in the type system. It is useful for configuration: Frequency!342.234

Sometimes it is worthwhile to have an unsound type system. Both D and Dart have somewhat unsound type systems. It puts a burden on the programmer, but can be useful.