Что происходит, когда вы выполняете преобразование из short в byte в C#?

У меня есть следующий код:

short myShort = 23948;
byte myByte = (byte)myShort;

Теперь я не ожидал, что myByte будет содержать значение 23948. Я бы предположил, что оно будет содержать 255 (я полагаю, что это самое большое значение для байта).

Однако он содержит 140, и это заставило меня задуматься, почему; что на самом деле происходит за кулисами?

Обратите внимание, что я не ищу кого-то, кто решит проблему, связанную с тем, что 23948 не помещается в байт, я просто интересуюсь базовой реализацией


person Fiona - myaccessible.website    schedule 27.09.2011    source источник


Ответы (10)


Short — это 2-байтовый тип, а byte — это один байт. Когда вы преобразуете два байта в один, вы заставляете систему подстраиваться, и один из исходных байтов (наиболее значимый) отбрасывается, а данные теряются. То, что осталось от значения 23948 (двоичное: 0101 1101 1000 1100), равно 140, что в двоичном переводе равно 1000 1100. Итак, вы исходите из:

0101 1101 1000 1100 (2 byte decimal value 23948)

to:

          1000 1100 (1 byte decimal value 140)

Вы можете сделать это только с явным приведением. Если вы попытаетесь назначить шорт байту без приведения, компилятор выдаст ошибку из-за возможности потери данных:

Невозможно неявно преобразовать тип "short" в "byte". Существует явное преобразование (вам не хватает приведения?)

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

using System;
public class MyClass
{
    public static void Main()
    {
        short myShort = 23948;
        byte myByte = (byte)myShort; // ok
        myByte = myShort; // error: 

        Console.WriteLine("Short: " + myShort);
        Console.WriteLine("Byte:  " + myByte);

        myShort = myByte; // ok

        Console.WriteLine("Short: " + myShort);
    }
}

С арифметическим переполнением и непроверенным контекстом:

using System;
public class MyClass {
    public static void Main() {
        unchecked {
            short myShort = 23948;
            byte myByte = (byte)myShort; // ok
            myByte = myShort; // still an error
            int x = 2147483647 * 2; // ok since unchecked
        }   
    }
}
person Paul Sasik    schedule 27.09.2011
comment
Здесь есть важный пробел... даже с вашим законченным образцом он может вести себя по-другому. Это зависит от того, является ли контекст checked или unchecked, что зависит от кода, а также от настройки компилятора (или флажка «Проверить арифметическое переполнение/опустошение» в VS). - person Marc Gravell; 28.09.2011
comment
Марк: Я тестировал с проверенным/непроверенным, и поведение для кастинга и арифметического переполнения кажется другим. В непроверяемом контексте разрешены переполнения, но приведение по-прежнему должно быть явным. Смотрите мой добавленный фрагмент. (Я запускаю Snippet Compiler с .NET 3.5) Итак... un/checked context, похоже, не влияет на приведение... - person Paul Sasik; 28.09.2011
comment
unchecked — значение по умолчанию; теперь сделайте это checked вместо этого, чтобы увидеть разницу - person Marc Gravell; 28.09.2011
comment
Я пробовал оба. Результат для приведенной части кода myByte = myShort; // still an error одинаков независимо от того, указано ли checked или unchedked. Ошибка компилятора всегда одна и та же: Cannot implicitly convert type 'short' to 'byte'. An explicit conversion exists (are you missing a cast?) - person Paul Sasik; 28.09.2011
comment
единственная строка, о которой я говорю, - это та, что из поста ОП: byte myByte = (byte)myShort; - person Marc Gravell; 28.09.2011

По сути, это просто занимает последние 8 бит... но в целом, когда вы обнаружите какое-то поведение, которое вас удивляет, следующим шагом должно быть обращение к спецификации. Из раздела 6.2.1, с дополнительным акцентом мой, для ситуации, которая уместна в данном случае.

Для преобразования из целочисленного типа в другой целочисленный тип обработка зависит от контекста проверки переполнения (§7.6.12), в котором происходит преобразование:

  • В проверенном контексте преобразование завершается успешно, если значение исходного операнда находится в пределах диапазона целевого типа, но создает исключение System.OverflowException, если значение исходного операнда выходит за пределы диапазона целевого типа.
  • In an unchecked context, the conversion always succeeds, and proceeds as follows.
    • If the source type is larger than the destination type, then the source value is truncated by discarding its “extra” most significant bits. The result is then treated as a value of the destination type.
    • Если исходный тип меньше целевого типа, то исходное значение либо дополняется знаком, либо дополняется нулями, так что оно имеет тот же размер, что и конечный тип. Sign-extension используется, если исходный тип является подписанным; нулевое расширение используется, если исходный тип не имеет знака. Затем результат обрабатывается как значение целевого типа.
    • Если исходный тип имеет тот же размер, что и целевой тип, то исходное значение рассматривается как значение целевого типа.
person Jon Skeet    schedule 27.09.2011

Это зависит; в контексте checked вы получите большое исключение; в контексте unchecked (по умолчанию) вы можете сохранить данные из последнего байта, как если бы вы это сделали:

byte b = (byte)(value & 255);
person Marc Gravell    schedule 27.09.2011

В вашем конкретном случае поведение довольно резкое и сухое, когда вы смотрите на биты для значения:

short myShort = 0x5D8C; // 23948
byte myByte = (byte)myShort; // myShort & 0xFF

Console.WriteLine("0x{0:X}", myByte); // 0x8C or 140
person user7116    schedule 27.09.2011
comment
Поскольку исключение не было выбрано, я предположил, что оно было в непроверенном контексте. - person user7116; 28.09.2011

Сохраняются только последние 8 бит. 23948 в двоичном формате равно 101110110001100b. Последние 8 бит 10001100b, что равно 140.

person Jeffrey Sax    schedule 27.09.2011

Когда вы приводите целочисленный тип к «меньшему» целочисленному типу, учитываются только биты с меньшим весом. Математически это как если бы вы использовали операцию по модулю. Таким образом, вы получаете значение 140, потому что 23948 по модулю 256 равно 140.

Приведение long к int будет использовать тот же механизм.

person Falanwe    schedule 27.09.2011
comment
Это не всегда правда! Попробуйте это: короткая s = 35000; Если вы делаете по модулю 32767, он оценивается как 2233, но вместо этого компилятор присваивает s -30536, что является значением в соответствии со спецификацией, и, таким образом, это правильное значение. - person xdevel2000; 26.11.2015
comment
Это потому, что приведение к шорту похоже не на выполнение по модулю 32768 (short.MaxValue + 1), а на по модулю 65536 (количество различных значений для шорта). -30536 соответствует 35000 по модулю 65536 и находится в коротком диапазоне значений. - person Falanwe; 26.11.2015
comment
Нет, я не думаю, что это правильно. Во-первых, короткая не является короткой, поэтому максимальное значение равно 32767; во-вторых, 35000 по модулю 65535 (не 65536) это 35000. Не знаю, может я что-то не так понял? - person xdevel2000; 26.11.2015
comment
short имеет 65536 возможных значений: 32767 положительных, 32768 отрицательных и 0. Следовательно, каждое непроверенное вычисление с использованием короткого замыкания выполняется так, как если бы оно выполнялось по модулю 65536. Когда вы выполняете приведение, среда выполнения учитывает только младшие биты (как объясняет принятый ответ довольно хорошо). Математически это то же самое, что найти конгруэнтное значение, которое находится в диапазоне. В таком случае -30536, потому что 35000 слишком много. (Обратите внимание, что -30536 + 65536 = 35000). - person Falanwe; 26.11.2015

Результат тот же, когда вы делаете:

byte myByte = (byte)(myShort & 0xFF);

Все, что выше восьмибитного, просто выбрасывается. Младшие восемь битов 23948 (0x5D8C) равны 140 (0x8C).

person Guffa    schedule 27.09.2011

Хм... потому что, когда вы переводите короткий (2 байта) в байт (1 байт), он получает только первый байт, а первый байт 23948 представляет 140.

person marcoaoteixeira    schedule 27.09.2011

23948 % 256 = 140, самые значащие байты были потеряны после преобразования, поэтому результат равен 140.

person Łukasz Wiatrak    schedule 27.09.2011

Это как когда у вас есть двузначное число «97» и вы конвертируете его в однозначное число, вы теряете 9 и сохраняете только «7».

person MatteKarla    schedule 27.09.2011