Search code examples
c#unsignedunsigned-integer

Storing deltas for Unsigned Longs


I am writing a class that has a bunch of metric properties of ulong data type

class Metrics {
  public ulong Memory
  public ulong Handles
  public ulong Calls
}

The reason I use ulong is because it's a fact that those values are unsigned and the capacity of signed will not be enough.

But I also initiate another instance of this class to store Deltas of those values, the change between the last check and the current.

The problem I am having is that sometimes the number can go down, thus the delta is a negative value, but I cannot store it on the ulong. Even if I declare the Delta as a regular LONG, if number goes up to the ulong max from 0 it will not fit and will throw an error.

How could I accomplish storing the delta value of a ulong knowing this?

class myData {
   public Metrics Deltas
   public Metrics Data

   ulong myValue = ulong.MaxValue;
   Deltas.Calls = (myValue - Data.Calls);
   Data.Calls = myValue;
   // Delta will be +MaxValue

   myValue = 0;
   Deltas.Calls = (myValue - Data.Calls);
   Data.Calls = myValue;
   // Delta will be -MaxValue, unsigned, cannot store it

}

Solution

  • Adding/subtracting two 64-bit numbers produces a 65-bit result, so just use Int128 if you use .NET Core 7.0 Preview 5 or newer. And don't use class if you just need to store data like this, a struct or record would be far better because they can live on stack

    struct Metrics {
      public Int128 Memory
      public Int128 Handles
      public Int128 Calls
    }
    
    class myData {
       public Metrics Deltas
       public Metrics Data
    
       Int128 myValue = ulong.MaxValue;
       Deltas.Calls = myValue - Data.Calls;
       Data.Calls = myValue;
    
       myValue = 0;
       Deltas.Calls = myValue - Data.Calls;
       Data.Calls = myValue;
    }
    

    If you're on an old .NET framework then the most efficient solution is to implement your own 65-bit data type. It should be very simple because for printing and sorting purposes multiplication and division won't be needed. You just need to implement +/- and comparison operators like this

    public readonly struct Delta
    {
        private readonly ulong magnitude;
        private readonly bool sign; // true: negative
    
        public Delta(ulong magn, bool sgn)
        {
            sign = sgn;
            magnitude = magn;
        }
        public Delta(ulong a)
        {
            sign = false;
            magnitude = a;
        }
    
        public static Delta operator +(Delta a) => a;
        public static Delta operator -(Delta a) => new Delta(a.magnitude, !a.sign);
    
        public static Delta operator +(Delta a, Delta b)
        {
            if (a.sign == b.sign)
            {
                var m = a.magnitude + b.magnitude;
                if (m < a.magnitude) // overflow
                {
                    sign = !sign;
                }
                return new Delta(m, a.sign);
            }
            var max = Math.Max(a.magnitude, b.magnitude);
            var min = Math.Min(a.magnitude, b.magnitude);
            var sign = a.sign;
            var m = max.magnitude - min.magnitude;
            if (m > max.magnitude) // overflow
            {
                sign = !sign;
            }
            
            return new Delta(max - min, sign);
        }
        public static Delta operator -(Delta a, Delta b) => a + (-b);
        
        public static bool operator ==(Delta a, Delta b)
        {
            return a.magnitude == b.magnitude && a.sign == b.sign;
        }
        public static bool operator !=(Delta a, Delta b) => !(a == b);
        
        public static bool operator >(Delta a, Delta b)
        {
            return a.sign == b.sign ? a.sign ^ (a.magnitude > b.magnitude) b.sign;
        }
        public static bool operator <(Delta a, Delta b) => !(a > b);
        
        public override string ToString()
        {
            return sign ? $"-{magnitude}" : $"{magnitude}";
        }
    }
    
     // Get delta of a and b
    public Delta GetDelta(ulong a, Delta ulong b)
    {
        return Delta(a) - Delta(b);
    }