A strange div bug on Linux x86_64, (both dmd & ldc2): long -5000 / size_t 2 = 9223372036854773308

H. S. Teoh hsteoh at quickfur.ath.cx
Thu Aug 13 23:52:41 UTC 2020


On Thu, Aug 13, 2020 at 11:38:03PM +0000, Adam D. Ruppe via Digitalmars-d wrote:
> On Thursday, 13 August 2020 at 23:28:43 UTC, H. S. Teoh wrote:
> > And before somebody tells me this is too verbose: we already have to
> > do this for short ints, no thanks to the recent change that
> > arithmetic involving anything smaller than int will implicitly
> > promote to int first:
> 
> That's not actually a recent change; it has always been like that,
> inherited from C.
> 
> D's difference is that C will implicitly convert back to the small
> type and D won't without an explicit cast. Drives me nuts and very
> rarely actually catches real mistakes.
> 
> The most recent change here is even like `-a` will trigger the error.
> It always did the promotion but it used to cast right back and now
> will error instead. Ugh.

Ah, yeah, that last one was what really annoyed me, and it was pretty
recent.  I never understood the logic of that. I mean the starting value
is already `byte`, why does its negation have to auto-promote to int?!
But thus sez the rulez, so who am I to question it amirite? :-(

It annoyed me so much that I wrote a wrapper module just to work around
it. Basically you append a `.np` to any of your narrow int values, and
it wraps it in an infectious struct that auto-truncates arithmetic
operations. The presence of the `.np` is a kind of self-documentation
that we're flouting the usual int promotion rules, so I'm reasonably
happy with this hack. :-P

Now somebody just has to write a wrapper for signed/unsigned
conversions... ;-)


T

-- 
It is not the employer who pays the wages. Employers only handle the
money. It is the customer who pays the wages. -- Henry Ford


-----------------------------------snip---------------------------------------
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 mailing list