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