GDC adds intrinsic support for core.checkedint

Dominikus Dittes Scherkl via Digitalmars-d digitalmars-d at puremagic.com
Fri Jul 3 02:57:31 PDT 2015


Hmm. Your solution is rather longish.

I had once started something along this lines (but only for 
signed types):

alias sbyte = SafeSigned!byte;
alias sword = SafeSigned!short;
alias sint  = SafeSigned!int;
alias slong = SafeSigned!long;
alias scent = SafeSigned!cent;

template isSafeSigned(T)
{
    enum bool isSafeSigned = is(Unqual!T == sbyte) || is(Unqual!T 
== sword)
    || is(Unqual!T == sint) || is(Unqual!T == slong) || 
is(Unqual!T == scent);
}

template isNumericExt(T)
{
    enum bool isNumericExt = isNumeric!T || isSafeSigned!T;
}

struct SafeSigned(S) if(isIntegral!S && isSigned!S) // one of 
byte, short, int, long or cent
{
pure:
@safe:
@nogc:
nothrow:
    alias ST = this;
    enum S nan = S.min;

    static @property S min() { return -S.max; }
    static @property S max() { return S.max; }
    static @property S init() { return nan; }
    static @property S invalid() { return nan; }
    @property bool isNaN() { return val == nan; }
    @property string stringof() { return isNaN ? "NaN" : 
val.stringof; }

    this(T)(const(T) x) if(isSafeSigned!T)
    {
       val = safeCast!S(x.val);
    }

    this(T)(const(T) x) if(isNumeric!T)
    {
       val = safeCast!S(x);
    }

    ST opAssign(T)(const(T) x) if(isNumericExt!T)
    {
       return this(x);
    }

    bool opEquals(T)(const(T) x) const if(isNumericExt!T)
    {
       static if(isSafeSigned!T)
          return (isNaN || x.isNaN) ? false : val == x.val;
       else static if(isFloatingPoint!T)
          return (isNaN || x.isNaN) ? false : val == x;
       else static if(T.sizeof < S.sizeof)
          return isNaN ? false : val == cast(S)x;
       else static if(isSigned!T) // T has bigger range
          return isNaN ? false : cast(T)val == x;
       else // overlap
          return (val < 0 || x > max) ? false : (cast(T)val == x);
    }

    ssign_t opCmp(T)(const(T) x) const if(isNumericExt!T)
    {
       static if(isSafeSigned!T)
          return (isNaN || x.isNaN) ? NaN : val.opCmp(x.val);
       else static if(isFloatingPoint!T)
          return (isNaN || x.isNaN) ? NaN : val.opCmp(x);
       else static if(T.sizeof < S.sizeof)
          return isNaN ? NaN : val.opCmp(cast(S)x);
       else static if(isSigned!T) // T has bigger range
          return isNaN ? NaN : (cast(T)val).opCmp(x);
       else // overlap
          return isNaN ? NaN : (val < 0) ? -1 : (x > max) ? 1 : 
(cast(T)val).opCmp(x);
    }

    T opCast(T)() const if(isNumericExt!T)
    {
       static if(Unqual!T == Unqual!S)
          return val;
       else static if(is(Unqual!T == Unqual!ST))
          return this; // not really a cast
       else static if(is(Unqual!T == bool))
          return (isNaN || val == 0) ? false : true;
       else static if(isFloatingPoint!T)
          return isNaN ? invalid!T : cast(T)val;
       else // SafeSigned or Integral
          return (isNaN || val < T.min || val > T.max) ? invalid!T 
: cast(T)val;
    }

    ST opOpAssign(string op, T)(const(T) x) if(isNumericExt!T &&
      (op == "+" || op == "-" || op == "*" || op == "/" || op == 
"%" || op == "^^"
    || op == "&" || op == "|" || op == "^" || op == "<<" || op == 
">>" || op == ">>>"))
    {
       if(isNaN || x.isNaN) return this;
       static if(isSafeSigned!T)
       {
          return mixin("this "~op~"= x.val");
       }
       else static if(op == "^^")
       {
          if(x < 2)
          {
             if(x==0) val = 1;
             else static if(isSigned!T)
             {
                if(x < 0 && abs(val) != 1) val = nan; // no 
inverse is integral
                else if(even(x)) val = 1; // x is negative and 
abs(val) == 1
             }
          }
          else
          {
             auto r = abs(val);
             if(r > 1)
             {
                if(x > byte.max || r >= (S(1)<<(S.sizeof<<2)) || x 
 > (maxpow(r)>>(5-bitlen(S.sizeof))))
                   val = nan;
                else
                {
                   r ^^= x;
                   if(r > max) val = nan;
                   else val = (val < 0 && odd(exp)) ? -cast(S)r : 
cast(S)r;
                }
             }
             else if(val == -1 && even(x)) val = 1;
          }
          return this;
       }
       else static if(op == "/" || op == "%")
       {
          if(!x) val = nan;
          else mixin("val "~op~"= x");
          return this;
       }
       else static if(op == "&" || op == ">>" || op == ">>>")
       {
          mixin("val "~op~"= x");
          return this;
       }
       else static if(Unqual!T == Unqual!S || T.sizeof < S.sizeof)
       {
          static if(op == "|" || op == "^")
          {
             mixin("val "~op~"= x");
          }
          else static if(op == "+" || op == "-" || op == "<<")
          {
             mixin("val "~op~"= x");
             asm { jno ok; }
             val = nan; // overflow, set to NaN
          }
          else static if(op == "*")
          {
             val *= x;
             asm { jnc ok; }
             val = nan; // overflow, set to NaN
          }
          ok:
          return this;
       }
       else // T has bigger range
       {
          if(x >= min && x <= max) return mixin("val "~op~"= 
cast(S)x");
          static if(op == "*" || op == "|" || op == "^" || op == 
"<<")
          {
             // will be definitely out of range
             val = nan;
          }
          else static if(isSigned!T)
          {
             static if(op == "+" || op == "-")
             {
                mixin("val = cast(ST)( cast(SafeSigned!T)val 
"~op~" x )");
             }
          }
          else static if(op == "+")
          {
             val = (val<0) ? cast(ST)(x + cast(T)(-val)) : nan;
          }
          else static if(op == "-")
          {
             val = (val>0) ? -cast(ST)(x - cast(T)val) : nan;
          }
          return this;
       }
    }

    // for non-commutative operations overload only the left side 
operator:
    ST opBinary(string op, T)(const(T) x) if((isNumericExt!T) &&
      (op == "/" || op == "%" || op == "^^" || op == "<<" || op == 
">>" || op == ">>>"))
    {
       static if(isFloatingpoint!T)
       {
          T r = this;
          mixin("r "~op~"= x");
          return cast(ST)r;
       }
       ST r = this;
       return mixin("r "~op~"= x");
    }

    // for commutative operations use the bigger type as result 
type
    ST opBinary(string op, T)(const(T) x) if(((isSafeSigned!T && 
T.sizeof <= S.sizeof) || isSuperset(S, T)) &&
      (op == "+" || op == "*" || op == "&" || op == "|" || op == 
"^"))
    {
       ST r = this;
       return mixin("r "~op~"= x");
    }
    ST opBinaryRight(string op, T)(const(T) x) if((isSafeSigned!T 
|| isIntegral!T) && (T.sizeof < S.sizeof) &&
      (op == "+" || op == "*" || op == "&" || op == "|" || op == 
"^"))
    {
       ST r = this;
       return mixin("r "~op~"= x");
    }

    ST opUnary(string op)() if(op == "++" || op == "--")
    {
       if(!isNaN) mixin(op~"val"); // ST.max+1 == ST.min-1 == NaN, 
so no extra conditions are necessary
       return this;
    }
private:
    S val = nan;
}

/// calculate the maximal power of the value that would fit in an 
ucent
/// for smaller types simply shift down the result
/// (e.g. divide by 4 to calc the maxpower that would fit in an 
uint)
ubyte maxpow(const ulong a) pure @safe @nogc nothrow
{
    assert(a>1); // no useful maxpower exists for 0 and 1
    static immutable ubyte[14] mp = [ 127, 80, 63, 55, 49, 45, 43, 
40, 38, 37, 35, 34, 33, 32 ];
    return (a<139) ? ((a<31) ? ((a<20) ? ((a<16) ? 
mp[cast(ubyte)a-2]
                                                 : ((a<18) ? 31 : 
30))
                                       : ((a<24) ? ((a<22) ? 29 : 
28)
                                                 : ((a<27) ? 27 : 
26)))
                             : ((a<57) ? ((a<41) ? ((a<35) ? 25 : 
24)
                                                 : ((a<48) ? 23 : 
22))
                                       : ((a<85) ? ((a<69) ? 21 : 
20)
                                                : ((a<107) ? 19 : 
18))))
              : ((a<7132) ? ((a<566) ? ((a<256) ? ((a<185) ? 17 : 
16)
                                                : ((a<371) ? 15 : 
14))
                                    : ((a<1626) ? ((a<921) ? 13 : 
12)
                                               : ((a<3184) ? 11 : 
10)))
                  : ((a<2642246) ? ((a<65536) ? ((a<19113) ?  9 : 
8)
                                             : ((a<319558) ?  7 : 
6))
                         : ((a<4294967296) ? ((a<50859009) ?  5 : 
4)
                                      : ((a<6981463658332) ?  3 : 
2))));
}

/// return true if T can represent every possible value of U.
/// e.g. isSuperset!(real, long) is false on systems with 
real.mant_dig < 63, else true
template isSuperset(T, U) if(isNumeric!T && isNumeric!U)
{
    static if(isIntegral!T)
    {
       static if(isIntegral!U) // unsigned >= unsigned or signed > 
unsigned or signed >= signed
          enum bool isSuperset = is(Unqual!T == Unqual!U) || 
((isSigned!T || isUnsigned!U) && (T.sizeof > U.sizeof));
       else // floatingpoint types can't be represented by 
integrals in general
          enum bool isSuperset = false;
    }
    else // T is floatingpoint
    {
       static if(isUnsigned!U)
          enum bool isSuperset = (T.mant_dig > U.sizeof<<3);
       else static if(isIntegral!U)
          enum bool isSuperset = (T.mant_dig >= U.sizeof<<3);
       else
          enum bool isSuperset = (T.mant_dig >= U.mant_dig);
    }
}

/// returns the number of the highest set bit +1 in the given 
value or 0 if no bit is set
size_t bitlen(T)(const(T) a) pure @safe @nogc nothrow 
if(isUnsigned!T)
{
    static if(T.sizeof <= size_t.sizeof) // ubyte, ushort or uint, 
maybe even ulong
    {
       return a ? bsr(a)+1 : 0;
    }
    else static if(T.sizeof == 8) // ulong if size_t == uint
    {
       return a ? a>>32 ? bsr(a>>32)+33 : bsr(a)+1 : 0;
    }
}

/// get the absolute value of x as unsigned type. always 
succeeds, even for T.min
Unsigned!T abs(T)(const(T) x) pure @safe @nogc nothrow 
if(isIntegral!T)
{
    static if(isSigned!T) if(x < 0) return -x;
    return x;
}

/// test if the lowest bit is set (also commonly used on signed 
values)
bool odd(T)(const(T) a) pure @safe @nogc nothrow if(isIntegral!T)
{ return !!(a&1); }

/// consider T.max to be NaN of unsigned types
/// and T.min to be NaN of signed types
@property bool isNaN(T)(const(T) x) pure @safe @nogc nothrow 
if(isIntegral!T)
{
    static if(isSigned!T)
       return x == T.min;
    else // unsigned
       return x == T.max;
}

/// add a property to numeric types that can be used as return 
value if a result is out of bounds
template invalid(T) if(isNumeric!T)
{
    static if(isFloatingPoint!T)
       @property enum invalid = T.init;
    else static if(isSigned!T)
       @property enum invalid = T.min;
    else // unsigned
       @property enum invalid = T.max;
}

/// returns the save (not invalid) minimum value for the given 
type
template smin(T) if(isNumeric!T)
{
    static if(isFloatingPoint!T)
       @property enum smin = -T.max; // T.min is the smallest 
representable positive value!!
    else static if(isSigned!T)
       @property enum smin = T.min+1;
    else // unsigned
       @property enum smin = T.min;
}

/// returns the save (not invalid) maximum value for the given 
type
template smax(T) if(isNumeric!T)
{
    static if(isUnsigned!T)
       @property enum smax = T.max-1u;
    else
       @property enum smax = T.max;
}

/// return true if the numeric value x is in the range of the 
target type
/// - no matter if low bits get lost in the process
bool fitIn(T, U)(const(U) x) pure @safe @nogc nothrow 
if(isNumeric!T && isNumeric!U)
{
    static if(isSuperset!(T, U)) return true;
    else static if(isIntegral!T)
    {
       static if(isIntegral!U)
       {
          static if(T.min > U.min) if(x < T.min) return false;
          static if(T.max < U.max) if(x > T.max) return false;
          return true;
       }
       else return x >= T.min && x <= T.max;
    }
    else // T is floatingpoint
    {
       static if(isIntegral!U) return true;
       else return x.isNan || x.isInv || (x >= -T.max && x <= 
T.max); // return false if a value would be converted to infinity
    }
}

/// returns x if it fits in the target types range, else invalid!T
T safeCast(T, U)(const(U) x) pure @safe @nogc nothrow 
if(isNumeric!T && isNumeric!U)
{
    return (!x.isNaN && x.fitIn!T) ? cast(T)x : invalid!T;
}

unittest
{
    int a = -128;
    auto b = saveCast!byte(a);
    assert(is(typeof(b) == byte));
    assert(b == -128);
    auto c = saveCast!ushort(a);
    assert(is(typeof(c) == ushort));
    assert(c.isNaN);
    real d = 1.0L;
    assert(saveCast!float(d) == 1.0f);
    d /= 3;
    assert(saveCast!float(d).isNaN); // would lose accuracy
}




More information about the Digitalmars-d mailing list