July 02, 2015
On Thursday, 2 July 2015 at 20:24:55 UTC, tsbockman wrote:
>
> I don't see why the closed-ness of the integers under an operation should determine whether it makes the cut or not, but if that's the standard:

I wanted to have an Integral type. And Math Integrals only have the base operations add and mul. Shift just feels wrong to me.

>
> The built-in integer types are truly closed under the bitwise operators `~`, `&`, `|`, and `^`, with no overflow, underflow, or undefined combinations.

Bitwise are no math operations, these are CS operations

In the end it is a design decision. And that's how I chose.


July 03, 2015
On 07/02/2015 11:22 PM, Robert burner Schadek wrote:
> On Thursday, 2 July 2015 at 20:24:55 UTC, tsbockman wrote:
>>
>> I don't see why the closed-ness of the integers under an operation
>> should determine whether it makes the cut or not, but if that's the
>> standard:
>
> I wanted to have an Integral type. And Math Integrals only have the base
> operations add and mul. Shift just feels wrong to me.
> ...

You're going to need more justification than that.

>>
>> The built-in integer types are truly closed under the bitwise
>> operators `~`, `&`, `|`, and `^`, with no overflow, underflow, or
>> undefined combinations.
>
> Bitwise are no math operations, these are CS operations
>
> In the end it is a design decision. And that's how I chose.
>

Some designs are better than others.

July 03, 2015
On 07/03/2015 04:17 AM, Timon Gehr wrote:
>
> Bitwise are no math operations, these are CS operations

What does that even mean?
July 03, 2015
On Friday, 3 July 2015 at 02:17:59 UTC, Timon Gehr wrote:
> On 07/03/2015 04:17 AM, Timon Gehr wrote:
>>
>> Bitwise are no math operations, these are CS operations
>
> What does that even mean?

That there are no bitwise operations in math.
July 03, 2015
On Friday, 3 July 2015 at 08:09:11 UTC, Robert burner Schadek wrote:
> On Friday, 3 July 2015 at 02:17:59 UTC, Timon Gehr wrote:
>> On 07/03/2015 04:17 AM, Timon Gehr wrote:
>>>
>>> Bitwise are no math operations, these are CS operations
>>
>> What does that even mean?
>
> That there are no bitwise operations in math.

https://en.wikipedia.org/wiki/Bitwise_operation#Mathematical_equivalents
July 03, 2015
On Friday, 3 July 2015 at 08:09:11 UTC, Robert burner Schadek wrote:
> On Friday, 3 July 2015 at 02:17:59 UTC, Timon Gehr wrote:
>> On 07/03/2015 04:17 AM, Timon Gehr wrote:
>>>
>>> Bitwise are no math operations, these are CS operations
>>
>> What does that even mean?
>
> That there are no bitwise operations in math.

boolean algebra
the math came first.
July 03, 2015
On Friday, 3 July 2015 at 08:09:11 UTC, Robert burner Schadek wrote:
> On Friday, 3 July 2015 at 02:17:59 UTC, Timon Gehr wrote:
>> On 07/03/2015 04:17 AM, Timon Gehr wrote:
>>>
>>> Bitwise are no math operations, these are CS operations
>>
>> What does that even mean?
>
> That there are no bitwise operations in math.

You imply math == arithmetic, which is false. If you take a look at e.g. Algebraic Coding Theory, it basically uses bitwise operations to model polynominal arithmetic.

If you want a mathematically correct representation of the integers modulo 2^n (which is what most closely resembles an int in math), you'll also have to disallow the less than and greater than operators, as they cannot be defined sensibly on a cyclic group.

July 03, 2015
On Tuesday, 30 June 2015 at 20:24:38 UTC, tsbockman wrote:
>     Robert's version: https://github.com/D-Programming-Language/phobos/pull/3389
>     My version:       https://github.com/tsbockman/CheckedInt
>
>     Some comparative benchmarks (note: these predate Iain's update to GDC):
> https://github.com/D-Programming-Language/phobos/pull/3389#issuecomment-115997507
>
> I don't think either of our versions is ready to be pulled - for one thing, mine has no documentation yet. However, we could use some feedback from anyone interested in making use of checked integer arithmetic:
>
>     1. What name do you prefer? SafeInt!T? CheckedInt!T? Something else?
I would prefer SafeInt!T

>
>     2. Regardless of which name is selected, it will still be rather verbose. I myself intend to use aliases like these to streamline things:
>
> alias cint = CheckedInt!int;
> alias cuint = CheckedInt!uint;
Yup. For my personal taste, safe unsigned int is rather useless
- if I use unsigned, I'm almost always about to do something including bit-operations for which overflow check is distracting at the best.

For the signed types I use the abbreviations with "s" in front, and 16bit
I still call a word, so the list beeing:

sbyte, sword, sint, slong (, scent)

I love to swing my sword!

>        (These aliases could easily be placed in a separate module, or in a mixin template, to prevent namespace pollution when they aren't wanted.)
>
>        Would anyone else like to see aliases like these included in Phobos?

Yes! I would always recommend to use those instead of the buildin signed types, because signed types should be the first thing everybody uses and should protect you from shooting in your foot the best - they will be heavily used in D-scripts, where speed is not the main goal and not used for bit-operations anyway.

>
>     3. I know D is on a big @nogc nothrow kick right now, but this seems like something where exceptions could be really useful. It would be easy to forget to manually check .isNaN.

Maybe, but if you print out the end result and see a "NaN", you are immediately sure there has happened an overflow somewhere and searching the bug will be very easy. If you want to throw an exception, do it yourself!
I don't want a safe type to do it for me - it destroys the reason for having a NaN value.

>     4. Robert has suggested that a SafeInt type should disable bitwise operations like ~, &, |, ^, but I don't understand why.

I do - for signed types they make very little sence and are likely to destroy the bit pattern - so either you need to check if the pattern becomes NaN or was NaN from the start, or you allow that those operations create a number from a NaN input - both very bad.
And as already mentioned: I will never use unsigned safe int, because I need the bit operations for unsigned types, and they ought to be fast!
Because I use unsigned types only if I plan to do something outside standard math and than I have to do all checks myself anyway.
>
>        It seems highly desirable to me to have NaN values propagate sensibly through all standard integer "math"
Yes!
especially for ^^

> not only the operators, but also things like core.bitop and the integer operations in std.math.
May be, but I don't think using them for signed types make much sense in the first place.

>
>        Ideally, I would like to have a sufficiently complete set of CheckedInt!T-aware math operations, that the implicit conversion to T could be removed so that the compiler would remind people to check isNaN before passing the wrapped value to non-CheckedInt!T-aware functions.

sounds nice, but I think it's not an easy goal.

>        What features do you want?
>
>     5. For anyone who cares to take a look at our work-in-progress code, which version looks more promising to you at this point? Robert's, or mine?

I'm currently taking a deeper look, get my answer later!
July 03, 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
}


July 03, 2015
On 07/03/2015 10:09 AM, Robert burner Schadek wrote:
> On Friday, 3 July 2015 at 02:17:59 UTC, Timon Gehr wrote:
>> On 07/03/2015 04:17 AM, Timon Gehr wrote:
>>>
>>> Bitwise are no math operations, these are CS operations
>>
>> What does that even mean?
>
> That there are no bitwise operations in math.

One obvious counterexample: https://en.wikipedia.org/wiki/Nimber

(Also, clearly, bitwise operations are mathematical objects.)
1 2
Next ›   Last »