Thread overview
regarding what seems (to me) unnecessary casts on integer expressions
Jun 05, 2021
someone
Jun 05, 2021
Kyle Ingraham
Jun 05, 2021
someone
Jun 05, 2021
Adam D. Ruppe
Jun 05, 2021
someone
Jun 05, 2021
Adam D. Ruppe
Jun 07, 2021
russhy
Jun 07, 2021
someone
Jun 07, 2021
H. S. Teoh
Jun 08, 2021
someone
June 05, 2021

Consider the following code to keep track of rectangle positions on the screen; eg: (1,2)(3,2) ie: (left,top)(right,bottom), providing get/set properties for one-based positions but only get properties for zero-based positions:

public struct gudtPosition {

   final public void reset() { pintLeft1 = pintTop1 = pintRight1 = pintBottom1 = 0; }

   private ushort pintLeft1 = 0;
   private ushort pintTop1 = 0;
   private ushort pintRight1 = 0;
   private ushort pintBottom1 = 0;

   final public const ushort left1() { return this.pintLeft1; }
   final public const ushort top1() { return this.pintTop1; }
   final public const ushort right1() { return this.pintRight1; }
   final public const ushort bottom1() { return this.pintBottom1; }

   final public ushort left0() { return this.pintLeft1 = 0 ? 0 : cast(ushort)(this.pintLeft1 - 1); }
   final public ushort top0() { return this.pintTop1 = 0 ? 0 : cast(ushort)(this.pintTop1 - 1); }
   final public ushort right0() { return this.pintRight1 = 0 ? 0 : cast(ushort)(this.pintRight1 - 1); }
   final public ushort bottom0() { return this.pintBottom1 = 0 ? 0 : cast(ushort)(this.pintBottom1 - 1); }

   final public void left1(const ushort lintPosition) { this.pintLeft1 = lintPosition; }
   final public void top1(const ushort lintPosition) { this.pintTop1 = lintPosition; }
   final public void right1(const ushort lintPosition) { this.pintRight1 = lintPosition; }
   final public void bottom1(const ushort lintPosition) { this.pintBottom1 = lintPosition; }

   final public string caption0() { return /// eg: from (24,24) to (48,48)
      r"from ("c ~
      std.format.format(r"%d"c, this.left0) ~
      r","c ~
      std.format.format(r"%d"c, this.top0) ~
      r") to ("c ~
      std.format.format(r"%d"c, this.right0) ~
      r","c ~
      std.format.format(r"%d"c, this.bottom0) ~
      r")"c;
   }

   final public string caption1() { return /// eg: from (24,24) to (48,48)
      r"from ("c ~
      std.format.format(r"%d"c, this.pintLeft1) ~
      r","c ~
      std.format.format(r"%d"c, this.pintTop1) ~
      r") to ("c ~
      std.format.format(r"%d"c, this.pintRight1) ~
      r","c ~
      std.format.format(r"%d"c, this.pintBottom1) ~
      r")"c;
   }

}

The code should be, I guess, self-explanatory to read. I do have some things to learn here on the D-specifics:

  • is there a way to unambiguously specify short integers values like there is for long ones (eg: 1L means 1 long integer like: long pintSomething = 1L;) ?

  • that follows to what seems to be unnecessary castings in my code snippet; if I write: ushort a = 1; ushort b = 2; ushort c; why can't I write c = a + b; ? ... DMD refuses the last one unless I explicitly write: c = cast(ushort) a + b; ... if all the variables I defined are of the same type ... why should I do have to cast() them afterwards ?

June 05, 2021

On Saturday, 5 June 2021 at 00:24:01 UTC, someone wrote:

On Saturday, 5 June 2021 at 00:24:01 UTC, someone wrote:

>

? 0 : cast(ushort)(this.pintBottom1 - 1); }

It looks like you’re being caught by D’s arithmetic conversions: https://dlang.org/spec/type.html#usual-arithmetic-conversions

“If the signed type is larger than the unsigned type, the unsigned type is converted to the signed type.”

Your pint variables are ushorts but the 1 you subtract is an int literal so the result gets promoted to an int.

Also, I don’t believe there is an unambiguous way to write a ushort literal: https://dlang.org/spec/type.html#basic-data-types

You should be ok with “u” if the function’s return is ushort. The compiler should be able to figure it out from there. I’m hoping I’m not wrong there as I haven’t tried it myself.

June 05, 2021

On Saturday, 5 June 2021 at 01:34:06 UTC, Kyle Ingraham wrote:

>

It looks like you’re being caught by D’s arithmetic conversions: https://dlang.org/spec/type.html#usual-arithmetic-conversions

“If the signed type is larger than the unsigned type, the unsigned type is converted to the signed type.”

Your pint variables are ushorts but the 1 you subtract is an int literal so the result gets promoted to an int.

As I am writing code today I am encountering a lot of these situations.

cast(ushort)(this.pintBottom1 - 1)

My first walkaround for this was intuitive:

this.pintBottom1 - cast(ushort) 1 /// I didn't know if eg: 1S was possible which should have been the proper way to handle it

... since pintBottom was already defined as ushort, but it didn't work also, so I ended with cast(ushort)(this.pintBottom1 - 1). I don't understand how to write typed code in D then. What's the point of declaring, for instance ushort's if then nothing will treat them as ushort's and I have to manually cast() everything to ushort() all the time ?

June 05, 2021

On Saturday, 5 June 2021 at 01:46:45 UTC, someone wrote:

>

What's the point of declaring, for instance ushort's if then nothing will treat them as ushort's and I have to manually cast() everything to ushort() all the time ?

Yeah, it totally sucks.

D inherited a silly rule from C - the promote rules actually come from there - but then added a well-intentioned but pretty annoying in practice rule that discarding bits from arithmetic require an implicit cast. (Unless it is the bits over 32... then who cares. lol)

The += operator is exempt from it so use it where you can. But otherwise your only real hope is to do a user defined type with op overloads and what an enormous hassle.

This design was a mistake.

June 05, 2021

On Saturday, 5 June 2021 at 02:07:03 UTC, Adam D. Ruppe wrote:

>

This design was a mistake.

Of all the great things I learned and read about it, this behavior was totally unexpected to me.

It is really difficult to code strong-typed apps this way -this behavior is really wonderful for writing loose scripts and the like and forgetting about types just to build prototypes or whatever, but, for really critical strong-typed apps is ... I'm not hurrying up to say game-over but, I can't see the way to go forward right now unless I am missing something.

Furthermore, if, at least, there was a way to clearly state what is short (eg 1S a la 1L) things will improve a lot, since any integer not having S or L could be assumed a normal integer, since byte has its own declaration. Writing code like:

ushort a; a = a - cast(ushort) 1; would be totally acceptable.

But writing code like:

ushort a; a = cast(ushort) a - cast(ushort) 1;

or

ushort a; a = cast(ushort)(a - 1);

would be totally silly and a waste of time I guess.

Right now I am totally immersed on Ali's book (and of course the tutorial and the dlang online reference) but one of the sections of Alexandrescu's book (have it in dead-tree-media) that I liked very much was the one depicting how to differentiate base types from one another with is flow charts.

At first sight I got the impression that any type could be unambiguously stated in D. Seems I was wrong.

One of the things that also immediately stuck me with D was the lack of proper nulls for all base types -meaning, for example for a string "" should not the same as null and 0 for a integer should not be the same as null, null (actually meaning dunno), and this is obviously really useful for database apps and a lot of other things.

June 05, 2021

On Saturday, 5 June 2021 at 02:38:50 UTC, someone wrote:

>

Furthermore, if, at least, there was a way to clearly state what is short (eg 1S a la 1L) things will improve a lot

That actually doesn't matter. The compiler actually will automatically type it to the most narrow thing it fits. The implementation there is actually quite nice - inside a single expression, the compiler tracks the possible range.

Try:

ubyte a = 5 + 3; // just works, it knows 5+3 = 8 which can be byte

ubyte b = a & 0xff; // also works, it knows the mask limits the range

And various combinations like that. It works as long as it is in one statement.

But if you cross them:

ubyte c = a + 1;

Now it assumes a might be the maximum of 255... and 255 + 1 doesn't fit in the ubyte, thus it will not allow the assignment back.

Masking operations are a possible alternative to casting btw... but still a hassle.

>

ushort a; a = cast(ushort) a - cast(ushort) 1;

This doesn't do anything since the promotion is done anyway. The compiler already knows both are ushort there, it is the arithmetic that assumes worst case scenario.

>

ushort a; a = cast(ushort)(a - 1);

this does work though.

a -= 1; // also works

>

At first sight I got the impression that any type could be unambiguously stated in D. Seems I was wrong.

Again, it is not the type of the literal that is the problem.

D follows the C rule that all arithmetic promotes. (Seriously, try it in C, it also converts to int first before doing an add or subtract.)

But C allows it to convert back without hassle, so it is easy to ignore this annoying promotion thing. D only allows the conversion back if the compiler can prove it fits.

>

for a string "" should not the same as null

its not. They're very similar but it is NOT the same. Check the .ptr property.

>

and 0 for a integer should not be the same as null

Its not. The type system won't allow those to cross.

>

and this is obviously really useful for database apps and a lot of other things.

I've done tons of database apps in D, I've been doing web stuff with D since 2008. There's various ways to do nullable sql, it isn't a big problem.

June 07, 2021

Everything is public by default, and you can't overload and derive from structs so final has no effect

there less visual noise:

struct gudtPosition {

   void reset() { pintLeft1 = pintTop1 = pintRight1 = pintBottom1 = 0; }

   private ushort pintLeft1 = 0;
   private ushort pintTop1 = 0;
   private ushort pintRight1 = 0;
   private ushort pintBottom1 = 0;

   const ushort left1() { return this.pintLeft1; }
   const ushort top1() { return this.pintTop1; }
   const ushort right1() { return this.pintRight1; }
   const ushort bottom1() { return this.pintBottom1; }

   ushort left0() { return this.pintLeft1 = 0 ? 0 : cast(ushort)(this.pintLeft1 - 1); }
   ushort top0() { return this.pintTop1 = 0 ? 0 : cast(ushort)(this.pintTop1 - 1); }
   ushort right0() { return this.pintRight1 = 0 ? 0 : cast(ushort)(this.pintRight1 - 1); }
   ushort bottom0() { return this.pintBottom1 = 0 ? 0 : cast(ushort)(this.pintBottom1 - 1); }

   void left1(const ushort lintPosition) { this.pintLeft1 = lintPosition; }
   void top1(const ushort lintPosition) { this.pintTop1 = lintPosition; }
   void right1(const ushort lintPosition) { this.pintRight1 = lintPosition; }
   void bottom1(const ushort lintPosition) { this.pintBottom1 = lintPosition; }

   string caption0() { return /// eg: from (24,24) to (48,48)
      r"from ("c ~
      std.format.format(r"%d"c, this.left0) ~
      r","c ~
      std.format.format(r"%d"c, this.top0) ~
      r") to ("c ~
      std.format.format(r"%d"c, this.right0) ~
      r","c ~
      std.format.format(r"%d"c, this.bottom0) ~
      r")"c;
   }

   string caption1() { return /// eg: from (24,24) to (48,48)
      r"from ("c ~
      std.format.format(r"%d"c, this.pintLeft1) ~
      r","c ~
      std.format.format(r"%d"c, this.pintTop1) ~
      r") to ("c ~
      std.format.format(r"%d"c, this.pintRight1) ~
      r","c ~
      std.format.format(r"%d"c, this.pintBottom1) ~
      r")"c;
   }

}
June 07, 2021
On Sat, Jun 05, 2021 at 01:46:45AM +0000, someone via Digitalmars-d-learn wrote: [...]
> As I am writing code today I am encountering a lot of these situations.
> 
> cast(ushort)(this.pintBottom1 - 1)
> 
> My first walkaround for this was intuitive:
> 
> this.pintBottom1 - cast(ushort) 1 /// I didn't know if eg: 1S was possible which should have been the proper way to handle it
> 
> ... since pintBottom was already defined as ushort, but it didn't work
> also, so I ended with cast(ushort)(this.pintBottom1 - 1). I don't
> understand how to write typed code in D then. What's the point of
> declaring, for instance ushort's if then nothing will treat them as
> ushort's and I have to manually cast() everything to ushort() all the
> time ?

Here's my solution to D's short integer cast-madness: abstract it away in an infectious wrapper type.  Save the following code into a file called nopromote.d, import it, and whenever you have an expression involving shorts, bytes, etc., that you don't want to write a cast for, just insert .np (for "no-promote") somewhere in the expression (see unittests below for concrete examples).

--------
/**
 * 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);
    }
}

// vim:set ai sw=4 ts=4 et:
--------


T

-- 
The two rules of success: 1. Don't tell everything you know. -- YHL
June 07, 2021

On Monday, 7 June 2021 at 12:53:29 UTC, russhy wrote:

>

Everything is public by default, and you can't overload and derive from structs so final has no effect. There less visual noise.

Gotcha !

I started using final (and the like) on classes a couple of days ago and I suppose I unconsciously dragged them to the structures, which, as you say, they can't be derived so it is non-sense.

Also: trying to suit my coding style to D code -a work in progress.

June 08, 2021

On Monday, 7 June 2021 at 14:12:14 UTC, H. S. Teoh wrote:

>

Here's my solution to D's short integer cast-madness: abstract it away in an infectious wrapper type.

Your workaround seems interesting. Will look further into the concept in the coming days. I am learning the D-ways and there's so much to gather that it will take me some time to get started -I mean, evidently D has many strong-points as being highlighted in the D gems section in the tutorial, but, what really comes to mind after a week-or-so coding with it, is its flexibility: there are a lot of ways to do x stuff.