Jump to page: 1 2
Thread overview
Integer promotion issue
Jan 25, 2020
Evan
Jan 26, 2020
Evan
Jan 26, 2020
Adam D. Ruppe
Jan 26, 2020
Guillaume Piolat
Jan 26, 2020
Adam D. Ruppe
Jan 28, 2020
NaN
Jan 26, 2020
Walter Bright
Sep 08, 2021
HuskyNator
Sep 08, 2021
Walter Bright
Sep 09, 2021
Basile B.
Sep 09, 2021
bauss
Jan 26, 2020
H. S. Teoh
Feb 01, 2020
Shachar Shemesh
Feb 06, 2020
Jonathan M Davis
January 25, 2020
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
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
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
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
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
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
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
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
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
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.
« First   ‹ Prev
1 2