sgorsten / linalg

linalg.h is a single header, public domain, short vector math library for C++
The Unlicense
849 stars 68 forks source link

Mixed-mode arithmetic #1

Closed sgorsten closed 7 years ago

sgorsten commented 8 years ago

The C standard allows for arithmetic operators to act on different data types, for instance, int + short, float * double, or int + float. In these cases, type promotion rules come into effect to determine the resulting type.

We currently allow integer promotion on arithmetic operators, for instance, short2() + short2() == int2().

Should we allow mixed-mode operations, such as short2() + int2(), float3() * double3(), etc? The result of doing so seems fairly well defined, and could make it easier to convert existing scalar algorithms to use linalg vector types.

If so, should we also permit scalars to participate, such as short2() + int(), float3() * double()? This seems a little bit more problematic. In the expression float() * 0.5, 0.5 is treated by the compiler as a floating point constant, but in the expression float3() * 0.5, 0.5 would be treated as a double and cause the result of the expression to be promoted to double3.

fatto commented 8 years ago

Why short2() + short2() become int2()? Or is only valid in comparison? By the way float()*0.5 does actually generate a warning because it does involve type promotion.

sgorsten commented 8 years ago

It's a tricky call. In C, types smaller than int are "promoted" to int during arithmetic operations, even unary operations. For instance: decltype(+char()) is equal to int. This appears to mostly stem from efficiency concerns, as this rule allows values to be loaded into machine registers and all arithmetic to be done in registers without the need for intermediate flushing/masking to discard the additional bits.

I've chosen to follow this convention in linalg.h, but if it is inconvenient for you, you can easily disable it by changing the line: template<class A, class B=A> using arith_result_t = typename traits<A,B>::arith_result; to: template<class A, class B=A> using arith_result_t = typename traits<A,B>::result;

As for the second part, you are correct, and I misspoke. I was actually referring more to this phenomenon:

void function_a(float) {}
template<class T> void function_b(T) {}
function_a(0.5); // calls function_a with float value 0.5f
function_b(0.5); // calls function_b with DOUBLE value 0.5

This has a ton of tiny, aggravating implications for generic programming, and you'll note that float3{1,2,3} * 0.5 currently refuses to compile because the compile sees the 0.5 as a double instead of a valid float constant.

sgorsten commented 7 years ago

Closing this issue out due to utter lack of interest.