С#: результат литья отрицательного целого числа в байт

Я смотрел исходный код проекта, и я заметил следующее выражение ( оба keyByte и codedByte имеют тип byte):

return (byte)(keyByte - codedByte);

Теперь я пытаюсь понять, что будет результатом в случаях, когда keyByte меньше, чем codeodedByte, что приводит к отрицательному целому числу.

После некоторых экспериментов, чтобы понять результат литья отрицательного целого числа, имеющего значение в диапазоне [-255: -1], я получил следующие результаты:

byte result = (byte) (-6);  // result = 250
byte result = (byte) (-50); // result = 206
byte result = (byte) (-17); // result = 239
byte result = (byte) (-20); // result = 236

Итак, при условии, что -256 < a < 0, я смог определить результат:

result = 256 + a;

Мой вопрос: должен ли я всегда ожидать, что это так?

Ответ 1

Да, это всегда будет иметь место (т.е. оно не просто зависит от вашей среды или компилятора, но определяется как часть спецификации языка С#). См. http://msdn.microsoft.com/en-us/library/aa691349(v=vs.71).aspx:

В контексте unchecked результат усекает, отбрасывая любые старшие биты, которые не соответствуют типу назначения.

Следующий вопрос: если вы отбираете старшие биты отрицательного int между -256 и -1 и читаете его как байт, что вы получаете? Это то, что вы уже обнаружили с помощью экспериментов: это 256 + x.

Обратите внимание, что endianness не имеет значения, потому что мы отбрасываем старшие (или наиболее значимые) биты, а не "первые" 24 бита. Поэтому, независимо от того, с какого конца мы его взяли, у нас остался младший байт, который составил этот int.

Ответ 2

Да. Помните, что в домене .Net "Byte" нет такой вещи, как "-":

http://msdn.microsoft.com/en-us/library/e2ayt412.aspx

Поскольку Byte является неподписанным типом, он не может представлять отрицательный номер. Если вы используете унарный оператор минус (-) в выражении, которое вычисляет значение типа Byte, Visual Basic преобразует выражение в Short первый. (Примечание: замените язык CLR/.Net на "Visual Basic" )

ДОПОЛНЕНИЕ: Вот пример приложения:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace TestByte
{
    class Program
    {
        static void Main(string[] args)
        {
            for (int i = -255; i < 256; i++)
            {
                byte b = (byte)i;
                System.Console.WriteLine("i={0}, b={1}", i, b);
            }
        }
    }
}

И вот результат:

testbyte|more
i=-255, b=1
i=-254, b=2
i=-253, b=3
i=-252, b=4
i=-251, b=5
...
i=-2, b=254
i=-1, b=255
i=0, b=0
i=1, b=1
...
i=254, b=254
i=255, b=255

Ответ 3

Вот алгоритм, который выполняет ту же логику, что и приведение в байт, чтобы помочь вам понять:

Для положительных результатов:

byte bNum = iNum % 256;

Для негативов:

byte bNum = 256 + (iNum % 256);

Это похоже на поиск любого k, который вызывает x + 255k в диапазоне 0 ... 255. Может быть только один k, который дает результат с этим диапазоном, и результат будет результатом кастинга в байт.

Другой способ взглянуть на это так, как будто он "циклически перемещается вокруг диапазона значений байтов":

Давайте снова используем iNum = -712 и определим a bNum = 0.

Мы будем делать iNum++; bNum--; до iNum == 0:

iNum = -712;
bNum = 0;

iNum++; // -711
bNum--; // 255 (cycles to the maximum value)

iNum++; // -710
bNum--; // 254

... // And so on, as if the iNum value is being *consumed* within the byte value range cycle.

Это, конечно, просто иллюстрация, чтобы увидеть, насколько логически это работает.

Ответ 4

Это то, что происходит в контексте unchecked. Можно сказать, что время выполнения (или компилятор, если Int32, который вы передали в Byte, известно в compiletime) добавляет или вычитает 256 столько раз, сколько необходимо, пока не найдет отображаемое значение.

В контексте checked возникает исключение (или ошибка компиляции). См. http://msdn.microsoft.com/en-us/library/khy08726.aspx

Ответ 5

Да - если вы не получите исключение.

.NET определяет все арифметические операции только с 4 байтами и большими типами данных. Таким образом, единственная неочевидная точка заключается в том, как работает преобразование int в byte.

Для преобразования от интегрального типа к другому интегральному типу результат преобразования зависит от контекста проверки переполнения (см. стандарт ECMA 334, раздел 13.2.1).

Итак, в следующем контексте

checked
{
    return (byte)(keyByte - codedByte);
}

вы увидите System.OverflowException. Если в следующем контексте:

unchecked
{
    return (byte)(keyByte - codedByte);
}

вам гарантировано всегда видеть результаты, которые вы ожидаете, независимо от того, используете ли вы или не добавляете кратное 256 разнице; например, 2 - 255 = 3.

Это верно, независимо от того, как аппаратное обеспечение представляет значения со знаком. Стандарт CLR (ECMA 335) указывает в разделе 12.1, что тип Int32 представляет собой "32-битное двоичное значение с дополнением". (Ну, это также соответствует всем платформам, на которых в настоящее время доступна .NET или моно, поэтому можно почти догадаться, что это будет работать в любом случае, но хорошо знать, что эта практика поддерживается стандартом языка и переносимым.)

Некоторые команды не хотят явно указывать контексты проверки переполнения, потому что у них есть политика проверки переполнения в начале цикла разработки, но не в выпущенном коде. В этих случаях вы можете смело выполнять байтовую арифметику следующим образом:

return (byte)((keyByte - codedByte) % 256);