byte + byte = int: why?
H. S. Teoh
hsteoh at quickfur.ath.cx
Fri Jan 18 17:33:59 UTC 2019
On Fri, Jan 18, 2019 at 05:09:52PM +0000, Mek101 via Digitalmars-d-learn wrote:
> I have the following line of code:
>
> temp[fowardI] = temp[fowardI] + temp[backwardI];
>
> Where temp is a byte array (byte[]). When I try to compile it dmd
> gives me this error:
>
> source/hash.d(11,25): Error: cannot implicitly convert expression
> `cast(int)temp[fowardI] + cast(int)temp[backwardI]` of type `int` to `byte`
> /usr/bin/dmd failed with exit code 1.
>
> Meaning that the byte type doesn't have a + operator. I know by
> experience that the pitfall came from C# (which I also use), and the
> absence of the operator was justified because the CLI didn't support
> addition between values smaller than an int, and also byte wasn't
> enough "numberish" to be used as such, and I should have used int
> instead.
>
> But why the D language doesn't implement the operator on byte?
For much the same reasons, and also that both C# and C inherited (to
various extents) C's integer promotion rules. Personally, I hate it,
but it's what we have, and changing it now would break too many things,
so we're stuck with it.
Basically, arithmetic on everything smaller than an int will implicitly
promote to int first. Which means the result will be int. But unlike C,
where you can implicitly narrow it back to a byte, in D the narrowing
must be explicit (to avoid accidental overflow when a narrow int type
like byte or short is too small to store the result), resulting in the
annoying situation where byte + byte != byte.
To work around this, you can use my nopromote module (code included
below), that implements a wrapper type that automatically casts back to
the narrow int type:
ubyte x = 1;
ubyte y = 2;
auto z = x.np + y; // np stands for "no promote"
static assert(is(typeof(x) == ubyte));
assert(z == 3);
x = 255;
z = x.np + y; // don't promote to int, so this will wrap
assert(z == 1); // wrapped around to 1
Check the unittests for more examples.
OT1H, having to write .np everywhere is annoying, but OTOH it does
document intent in the code, that you're expecting wraparound and have
presumably taken the necessary precautions to mitigate any ill-effects
that wrapping may have, so in that sense it's a good thing.
T
--
It is widely believed that reinventing the wheel is a waste of time; but I disagree: without wheel reinventers, we would be still be stuck with wooden horse-cart wheels.
-----------------------------------snip----------------------------------
/**
* Truncating wrapper around built-in narrow ints to work around stupid casts.
*/
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);
}
}
-----------------------------------snip----------------------------------
More information about the Digitalmars-d-learn
mailing list