Question

Just noticed that the unchecked context doesn't work when working with a BigInteger, for instance:

unchecked
{
    // no exception, long1 assigned to -1 as expected
    var long1 = (long)ulong.Parse(ulong.MaxValue.ToString());
}

unchecked
{
    var bigInt = BigInteger.Parse(ulong.MaxValue.ToString());

    // throws overflow exception
    var long2 = (long)bigInt;
}

Any idea why that's the case? Is there something special with the way big integers are converted to other primitive integer types?

Thanks,

Was it helpful?

Solution

The C# compiler has no idea whatsoever that a BigInteger is logically an "integral type". It just sees a user-defined type with a user-defined explicit conversion to long. From the compiler's point of view,

long long2 = (long)bigInt;

is exactly the same as:

long long2 = someObject.SomeMethodWithAFunnyNameThatReturnsALong();

It has no ability to reach inside that method and tell it to stop throwing exceptions.

But when the compiler sees

int x = (int) someLong;

the compiler is generating the code doing the conversion, so it can choose to generate checked or unchecked code as it sees fit.

Remember, "checked" and "unchecked" have no effect at runtime; it's not like the CLR goes into "unchecked mode" when control enters an unchecked context. "checked" and "unchecked" are instructions to the compiler about what sort of code to generate inside the block. They only have an effect at compile time, and the compilation of the BigInt conversion to long has already happened. Its behaviour is fixed.

OTHER TIPS

The OverflowException is actually being thrown by the explicit cast operator defined on BigInteger. It looks like this:

int num = BigInteger.Length(value._bits);
if (num > 2)
{
    throw new OverflowException(SR.GetString("Overflow_Int64"));
}

In other words, it handles overflows this way regardless of the checked or unchecked context. The docs actually say so.

Update: Of course, Eric is the final word on this. Please go read his post :)

The documentation explicitly states that it will throw OverflowException in this situation. The checked context only makes a difference to "native" arithmetic operations that the C# compiler emits - which doesn't include invoking explicit conversion operators.

To perform the conversion "safely" you'd have to compare it with long.MaxValue and long.MinValue first to check whether or not it's in range. To get the overflow-to-negative effect, I suspect you'd have to perform use bitwise operators within BigInteger first. For example:

using System;
using System.Numerics;

class Program
{
    static void Main(string[] args)
    {
        BigInteger bigValue = new BigInteger(ulong.MaxValue);

        long x = ConvertToInt64Unchecked(bigValue);
        Console.WriteLine(x);
    }

    private static readonly BigInteger MaxUInt64AsBigInteger
        = ulong.MaxValue;

    private static long ConvertToInt64Unchecked(BigInteger input)
    {
        unchecked
        {
            return (long) (ulong) (input & MaxUInt64AsBigInteger);
        }
    }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top