Thread overview | |||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
January 25, 2020 Integer promotion issue | ||||
---|---|---|---|---|
| ||||
I am writing an 8 bit emulator that uses a lot of small numbers and running into a problem where the compiler will automatically promote smaller operands up to an int which causes the result of math calculations to not fit into smaller types. //compiled with dmd 2.090.0 void main() { ubyte a = 0x01; ubyte b = 0x01; ubyte c = a + b; //Error: cannot implicitly convert expression cast(int)a + cast(int)b of type int to ubyte writeln(c); } Is there a way to stop the compiler from up casting automatically? or have it down cast it back once it's done? |
January 25, 2020 Re: Integer promotion issue | ||||
---|---|---|---|---|
| ||||
Posted in reply to Evan | On 1/25/20 6:46 PM, Evan wrote:
> I am writing an 8 bit emulator that uses a lot of small numbers and running into a problem where the compiler will automatically promote smaller operands up to an int which causes the result of math calculations to not fit into smaller types.
>
> //compiled with dmd 2.090.0
> void main()
> {
> ubyte a = 0x01;
> ubyte b = 0x01;
> ubyte c = a + b; //Error: cannot implicitly convert expression cast(int)a + cast(int)b of type int to ubyte
> writeln(c);
> }
>
> Is there a way to stop the compiler from up casting automatically? or have it down cast it back once it's done?
Not really. You can cast yourself, write your own type that does it, or use masking to have the compiler accept the result (i.e. (a + b) & 0xff).
I'd recommend your own type, as it will look a lot cleaner.
-Steve
|
January 26, 2020 Re: Integer promotion issue | ||||
---|---|---|---|---|
| ||||
Posted in reply to Steven Schveighoffer | On Saturday, 25 January 2020 at 23:58:00 UTC, Steven Schveighoffer wrote:
> On 1/25/20 6:46 PM, Evan wrote:
>> [...]
>
> Not really. You can cast yourself, write your own type that does it, or use masking to have the compiler accept the result (i.e. (a + b) & 0xff).
>
> I'd recommend your own type, as it will look a lot cleaner.
>
> -Steve
If I make my own type then I have to write more casts from my type to built in types. I want to create less work for myself not more.
|
January 26, 2020 Re: Integer promotion issue | ||||
---|---|---|---|---|
| ||||
Posted in reply to Evan | On Sunday, 26 January 2020 at 00:07:47 UTC, Evan wrote:
> I want to create less work for myself not more.
Yeah, there's basically no winning :(
D inherited a silly rule from C (all arithmetic yields int or above) then it combined poorly with the (decent in isolation) idea to make lossy conversions stand out.
It can sometimes help to try sticking to += operators which aren't as picky.
|
January 25, 2020 Re: Integer promotion issue | ||||
---|---|---|---|---|
| ||||
Posted in reply to Evan | On Sun, Jan 26, 2020 at 12:07:47AM +0000, Evan via Digitalmars-d wrote: > On Saturday, 25 January 2020 at 23:58:00 UTC, Steven Schveighoffer wrote: > > On 1/25/20 6:46 PM, Evan wrote: > > > [...] > > > > Not really. You can cast yourself, write your own type that does it, > > or use masking to have the compiler accept the result (i.e. (a + b) > > & 0xff). > > > > I'd recommend your own type, as it will look a lot cleaner. > > > > -Steve > > If I make my own type then I have to write more casts from my type to built in types. I want to create less work for myself not more. No you don't. Check this out (put it in a file called "nopromote.d" and import it): module nopromote; enum isNarrowInt(T) = is(T : int) || is(T : uint); /** * A wrapper around a built-in narrow int that truncates the * result of arithmetic operations to the narrow type, * overriding built-in int promotion rules. */ struct Np(T) if (isNarrowInt!T) { T impl; alias impl this; /** * Truncating binary operator. */ Np opBinary(string op, U)(U u) if (is(typeof((T x, U y) => mixin("x " ~ op ~ " y")))) { return Np(cast(T) mixin("this.impl " ~ op ~ " u")); } /** * Truncating unary operator. */ Np opUnary(string op)() if (is(typeof((T x) => mixin(op ~ "cast(int) x")))) { return Np(cast(T) mixin(op ~ " cast(int) this.impl")); } /** * Infectiousness: any expression containing Np should * automatically use Np operator semantics. */ Np opBinaryRight(string op, U)(U u) if (is(typeof((T x, U y) => mixin("x " ~ op ~ " y")))) { return Np(cast(T) mixin("u " ~ op ~ " this.impl")); } } /** * Returns: A lightweight wrapped type that overrides built-in * arithmetic operators to always truncate to the given type * without promoting to int or uint. */ auto np(T)(T t) if (isNarrowInt!T) { return Np!T(t); } // Test binary ops @safe unittest { ubyte x = 1; ubyte y = 2; auto z = x.np + y; static assert(is(typeof(z) : ubyte)); assert(z == 3); byte zz = x.np + y; assert(zz == 3); x = 255; z = x.np + y; assert(z == 1); } @safe unittest { byte x = 123; byte y = 5; auto z = x.np + y; static assert(is(typeof(z) : byte)); assert(z == byte.min); byte zz = x.np + y; assert(zz == byte.min); } @safe unittest { import std.random; short x = cast(short) uniform(0, 10); short y = 10; auto z = x.np + y; static assert(is(typeof(z) : short)); assert(z == x + 10); short s = x.np + y; assert(s == x + 10); } // Test unary ops @safe unittest { byte b = 10; auto c = -b.np; static assert(is(typeof(c) : byte)); assert(c == -10); ubyte ub = 16; auto uc = -ub.np; static assert(is(typeof(uc) : ubyte)); assert(uc == 0xF0); } version(unittest) { // These tests are put here as actual module functions, to // force optimizer not to discard calls to these functions, // so that we can see the actual generated code. byte byteNegate(byte b) { return -b.np; } ubyte ubyteNegate(ubyte b) { return -b.np; } byte byteTest1(int choice, byte a, byte b) { if (choice == 1) return a.np + b; if (choice == 2) return a.np / b; assert(0); } short shortAdd(short a, short b) { return a.np + b; } // Test opBinaryRight byte byteRightTest(byte a, byte c) { auto result = a + c.np; static assert(is(typeof(result) : byte)); return result; } unittest { assert(byteRightTest(127, 1) == byte.min); } short multiTest1(short x, short y) { return short(2) + 2*(x - y.np); } unittest { // Test wraparound semantics. assert(multiTest1(32767, 16384) == short.min); } short multiTest2(short a, short b) { short x = a; short y = b; return (2*x + 1) * (y.np/2 - 1); } unittest { assert(multiTest2(1, 4) == 3); } } As the unittests show, all you need to do is to insert .np somewhere in your arithmetic expression, and it automatically "taints" the expression to be the given narrow type and returns a narrow type as result. No casts are needed in user code. I use this module to work around silly archaic C/C++ integer promotion rules that Walter insists D must have. Hope this helps. T -- Дерево держится корнями, а человек - друзьями. |
January 25, 2020 Re: Integer promotion issue | ||||
---|---|---|---|---|
| ||||
Posted in reply to Evan | On 1/25/20 7:07 PM, Evan wrote:
> On Saturday, 25 January 2020 at 23:58:00 UTC, Steven Schveighoffer wrote:
>> On 1/25/20 6:46 PM, Evan wrote:
>>> [...]
>>
>> Not really. You can cast yourself, write your own type that does it, or use masking to have the compiler accept the result (i.e. (a + b) & 0xff).
>>
>> I'd recommend your own type, as it will look a lot cleaner.
>>
>
> If I make my own type then I have to write more casts from my type to built in types. I want to create less work for myself not more.
I'm not sure what you mean. You should be able to alias-this the type to reduce the casting. Whipped up in a couple minutes:
struct U8
{
ubyte _val;
alias _val this;
this(ubyte x)
{
_val = x;
}
U8 opBinary(string op, T)(T other)
if ((is(T == U8) || is(T == ubyte)) && (op == "+" || op == "-" /* || op == ... */))
{
return mixin("U8(cast(ubyte)(_val " ~ op ~ " cast(ubyte)other))");
}
// opBinaryRight etc...
}
void main()
{
import std.stdio;
U8 a = 0x01;
U8 b = 0x01;
ubyte c = a + b; //Error: cannot implicitly convert expression cast(int)a + cast(int)b of type int to ubyte
writeln(c); // 2
ubyte d = b - a;
writeln(d); // 0
writeln(a); // 1 (uses alias this)
}
-Steve
|
January 25, 2020 Re: Integer promotion issue | ||||
---|---|---|---|---|
| ||||
Posted in reply to Steven Schveighoffer | On 1/25/20 7:31 PM, Steven Schveighoffer wrote:
> //Error: cannot implicitly convert expression cast(int)a + cast(int)b of type int to u
Ugh, I keep forgetting to adjust the comments. This error doesn't happen.
-Steve
|
January 26, 2020 Re: Integer promotion issue | ||||
---|---|---|---|---|
| ||||
Posted in reply to Adam D. Ruppe | On Sunday, 26 January 2020 at 00:22:14 UTC, Adam D. Ruppe wrote: > D inherited a silly rule from C (all arithmetic yields int or above) It seems silly however in the wild this C int promotion rule make stuff work without the author knowledge. Example: -------------------------------------------------------- void computeMean(short* ) { // let's compute the mean of two arrays foreach(n; 0..N) { short a = input[n]; short b = input2[n]; int meanOfTwo = (a + b) >> 1; // BOOM } } -------------------------------------------------------- Pretty sure a LOT of code would break if C++ or D would abandon this rule (well, except you introduce traps for integer overflow). Doing it without the rule introduce a lot of casts... |
January 26, 2020 Re: Integer promotion issue | ||||
---|---|---|---|---|
| ||||
Posted in reply to Guillaume Piolat | On Sunday, 26 January 2020 at 13:01:58 UTC, Guillaume Piolat wrote:
> It seems silly however in the wild this C int promotion rule make stuff work without the author knowledge.
indeed.
Though if that rule wasn't there we'd probably have more author knowledge anyway. But it is too late now.
For D though maybe we could change our truncating rule to make it more relaxed if all the inputs are literals or the same type as the assignment. So it still promotes, just allows implicit cast back to the original.
short a;
short b = a + 1; // implicitly does cast(short)(cast(int) a + 1)
int a;
short b;
short c = b + a; // error: cannot implicitly cast because the `int a` triggers more strictness
ubyte a;
ubyte b;
ubyte c = a + b; // allowed
ushort d;
ubyte c = a + d; // prohibited
So it will follow the C promotion rules and then implicitly cast back to the largest integral type in the original expression.
ubyte a = 255 + 5; // literals are excluded from the new implicit rule loosening, so this does what it does now = int(int(255) + int(5)) => VRP -> too large for ubyte, error.
Cuz I just think if we are explicitly specifying a particular smaller type throughout it indicates a pretty clear intent already.
|
January 26, 2020 Re: Integer promotion issue | ||||
---|---|---|---|---|
| ||||
Posted in reply to Guillaume Piolat | On 1/26/2020 5:01 AM, Guillaume Piolat wrote:
> Pretty sure a LOT of code would break if C++ or D would abandon this rule
Worst of all, the breakage would be silent.
|
Copyright © 1999-2021 by the D Language Foundation