непроверенный блок не работает с BigInteger?

Только что заметил, что контекст unchecked не не работает при работе с BigInteger, например:

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;
}

Любая идея, почему это так? Есть ли что-то особенное в том, как большие целые числа преобразуются в другие примитивные целочисленные типы?

Спасибо,


person theburningmonk    schedule 27.08.2011    source источник


Ответы (3)


Компилятор C# понятия не имеет, что BigInteger логически является "интегральным типом". Он просто видит определенный пользователем тип с определенным пользователем явным преобразованием в long. С точки зрения компилятора,

long long2 = (long)bigInt;

точно так же, как:

long long2 = someObject.SomeMethodWithAFunnyNameThatReturnsALong();

У него нет возможности проникнуть внутрь этого метода и сказать ему прекратить генерировать исключения.

Но когда компилятор видит

int x = (int) someLong;

компилятор генерирует код, выполняющий преобразование, поэтому он может выбирать, генерировать проверенный или непроверенный код по своему усмотрению.

Помните, что "checked" и "unchecked" не действуют во время выполнения; это не похоже на то, что CLR переходит в «непроверенный режим», когда управление входит в непроверенный контекст. «checked» и «unchecked» — это инструкции для компилятора о том, какой код генерировать внутри блока. Они действуют только во время компиляции, а компиляция преобразования BigInt в long уже произошла. Его поведение фиксировано.

person Eric Lippert    schedule 27.08.2011
comment
Так или иначе, есть ли способ явно проверить или не проверить переполнение в методах пользовательского типа? Кроме попытки/поймать (OverflowException). - person Lessneek; 14.12.2013
comment
@alexander вам придется спросить человека, который написал рассматриваемый метод. - person Eric Lippert; 14.12.2013

OverflowException на самом деле вызывается явным оператором приведения, определенным для BigInteger. Это выглядит так:

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

Другими словами, он обрабатывает переполнения таким образом независимо от контекста checked или unchecked. В документах действительно так сказано.

Обновление. Конечно, последнее слово здесь за Эриком. Пожалуйста, прочитайте его пост :)

person dlev    schedule 27.08.2011

В документации прямо указано, что в этой ситуации будет выдано OverflowException. Проверенный контекст имеет значение только для «собственных» арифметических операций, которые выдает компилятор C#, что не включает вызов явных операторов преобразования.

Чтобы выполнить преобразование «безопасно», вам нужно сначала сравнить его с long.MaxValue и long.MinValue, чтобы проверить, находится ли он в диапазоне. Чтобы получить эффект переполнения к отрицательному, я подозреваю, что вам придется сначала использовать побитовые операторы в пределах BigInteger. Например:

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);
        }
    }
}
person Jon Skeet    schedule 27.08.2011