Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Add a long and ulong in C#

Tags:

c#

math

An api I am using has a ulong field (I can't change this). I need to adjust this field by the a long amount (could be an int too). However I can't add these togeather: Operator '+' is ambiguous on operands of type 'ulong' and 'long'.

I can't convert the long to a ulong because I will lose the sign, I can't convert the ulong to a long because I could lose part of the value.

Ultimately I want to attempt to adjust the value, if it would cause a wrap I want to return false and not finish the adjustment. If it doesn't wrap I want to finish the adjustment and return true; I can do this part, but only if I can figure out a a way to add the two fields together in the first place.

public ulong Value;
public bool AdjustValue(long amount)
{
    // Operator '+' is ambiguous on operands of type 'ulong' and 'long'
    ulong adjustValue = Value + amount; 

    if (amount < 0 && adjustValue > Value)
        return false;
    if (amount > 0 && adjustValue < Value)
        return false;

    Value = adjustValue;
    return true;
}

How can I solve this?

like image 269
Anthony Nichols Avatar asked Feb 14 '17 22:02

Anthony Nichols


People also ask

How do I get long long in C?

To take input " long long int " and output " long long int " in C is : long long int n; scanf("%lld", &n); printf("%lld", n);

What is the difference between long and Ulong?

You can store a bigger value in ulong than long , but no negative numbers allowed. A long value is stored in 64-bit,with its first digit to show if it's a positive/negative number. while ulong is also 64-bit, with all 64 bit to store the number. so the maximum of ulong is 2(64)-1, while long is 2(63)-1.

What is %lu in printf?

%lu. Unsigned int or unsigned long. %lli or %lld.

How do you declare an unsigned long long int?

Simply write long long int for a signed integer, or unsigned long long int for an unsigned integer. To make an integer constant of type long long int , add the suffix ` LL ' to the integer. To make an integer constant of type unsigned long long int , add the suffix ` ULL ' to the integer.


2 Answers

For readability, you may prefer the suggestion in the comment:

adjustValue = Value;
if (amount < 0)
    adjustValue -= (ulong)(-amount);
else
    adjustValue += (ulong)amount;

This makes clear how you expect the math to work.

However…

The representation of integers in practically every modern hardware CPU is two's complement. One of the reasons is that math using two's complement binary numbers has a very nice property: you get the same binary representation for addition and subtraction whether you're dealing with signed or unsigned values.

This means that you can just cast the adjustment value to ulong to do your addition. According to the C# language rules, the cast will simply reinterpret the signed value's binary representation as unsigned. And adding the unsigned value will have exactly the same effect as if you'd added a signed value with that same binary representation.

Here's a code example that shows this working:

static void Main(string[] args)
{
    ulong[] values = { ulong.MinValue, ulong.MaxValue, long.MaxValue };
    long[] adjust = { 0, 1, -1, long.MinValue, long.MaxValue };

    for (int i = 0; i < values.Length; i++)
    {
        for (int j = 0; j < adjust.Length; j++)
        {
            ulong value = values[i];

            bool result = AdjustValue(adjust[j], ref value);
            Console.WriteLine($"{values[i]} + {adjust[j]} = {values} (overflow: {!result})");
        }
    }
}

static bool AdjustValue(long amount, ref ulong value)
{
    ulong adjustValue = value + (ulong)amount;

    if (amount < 0 && adjustValue > value)
        return false;
    if (amount > 0 && adjustValue < value)
        return false;

    value = adjustValue;
    return true;
}

Note: by default, math operations are runtime are unchecked. However, there is a compiler switch that can change this. If you are compiling your code with that switch enabled, forcing checked arithmetic operations, you will need to wrap the above in an unchecked block to avoid getting overflow exceptions.

like image 114
Peter Duniho Avatar answered Sep 26 '22 02:09

Peter Duniho


Someone's going to hate this answer, but you could use the decimal data type, since it is capable of storing 28-29 significant digits and 2^64 only has 20 digits.

Naturally using a floating point data type incurs a bit of overhead in terms of performance, but on the other hand this code is really easy to read, and may have better branch prediction/pipelining than a solution that requires if.

public static ulong Add(this ulong baseValue, long offset)
{
    decimal adjustedValue = (decimal)baseValue + offset;
    return (ulong)adjustedValue;
}

If you overflow, the system will throw a decent enough ArgumentOutOfRangeException, but you could also roll your own:

public static ulong Add(this ulong baseValue, long offset)
{
    decimal adjustedValue = (decimal)baseValue + offset;
    if (adjustedValue > ulong.MaxValue) throw new ArgumentOutOfRangeException("Too big to fit!");
    if (adjustedValue < ulong.MinValue) throw new ArgumentOutOfRangeException("Too small to fit!");
    return (ulong)adjustedValue;
}
like image 26
John Wu Avatar answered Sep 26 '22 02:09

John Wu