byte + byte = int... почему?
Глядя на этот код C#:
byte x = 1;
byte y = 2;
byte z = x + y; // ERROR: Cannot implicitly convert type 'int' to 'byte'
Результат любой математики, выполненной на byte
(или же short
) типов неявно приводится к целому числу. Решением является явное приведение результата обратно к байту:
byte z = (byte)(x + y); // this works
Что мне интересно, почему? Это архитектурно? Философская?
У нас есть:
int
+int
знак равноint
long
+long
знак равноlong
float
+float
знак равноfloat
double
+double
знак равноdouble
Так почему не:
byte
+byte
знак равноbyte
short
+short
знак равноshort
?
Немного предыстории: я выполняю длинный список вычислений для "малых чисел" (то есть < 8) и сохраняю промежуточные результаты в большом массиве. Использование байтового массива (вместо массива int) происходит быстрее (из-за попаданий в кэш). Но обширные броски байтов, распространяемые по коду, делают его намного более нечитаемым.
16 ответов
Третья строка вашего кода:
byte z = x + y;
на самом деле означает
byte z = (int) x + (int) y;
Таким образом, в байтах нет операции +, байты сначала приводятся к целым числам, а результатом сложения двух целых чисел является (32-разрядное) целое число.
С точки зрения "почему это вообще происходит", это потому, что в C# нет никаких операторов, определенных для арифметики с байтами, sbyte, short или ushort, как уже говорили другие. Этот ответ о том, почему эти операторы не определены.
Я считаю, что это в основном ради производительности. Процессоры имеют собственные операции для выполнения арифметики с 32 битами очень быстро. Можно автоматически выполнить преобразование обратно из результата в байт, но это приведет к снижению производительности в том случае, если вы на самом деле не хотите такого поведения.
Я думаю, что это упоминается в одном из аннотированных стандартов C#. Ищу...
РЕДАКТИРОВАТЬ: досадно, я сейчас просмотрел аннотированную спецификацию ECMA C# 2, аннотированную спецификацию MS C# 3 и аннотацию CLI, и ни один из них не упомянул это, насколько я вижу. Я уверен, что видел причину, приведенную выше, но я ошарашен, если знаю, где. Извиняюсь, отзыв фанатов:(
Я думал, что видел это где-то раньше. Из этой статьи, The Old New Thing:
Предположим, мы жили в фантастическом мире, где операции с байтом приводили к байту.
byte b = 32;
byte c = 240;
int i = b + c; // what is i?
В этом фэнтезийном мире ценность меня будет 16! Зачем? Поскольку оба операнда оператора + являются байтами, сумма "b+c" вычисляется как байт, что приводит к 16 из-за целочисленного переполнения. (И, как я отмечал ранее, целочисленное переполнение является новым вектором атаки безопасности.)
РЕДАКТИРОВАТЬ: Раймонд, по сути, защищает подход C и C++ изначально. В комментариях он защищает тот факт, что C# использует тот же подход на основе обратной совместимости языка.
C#
ECMA-334 утверждает, что сложение определяется только как допустимое для int+int, uint+uint, long+long и ulong+ulong (ECMA-334 14.7.4). Как таковые, это возможные операции, которые необходимо учитывать в отношении 14.4.2. Поскольку существуют неявные приведения от байта к int, uint, long и ulong, все члены функции сложения являются применимыми членами функции согласно 14.4.2.1. Мы должны найти лучшее неявное приведение по правилам в 14.4.2.3:
Приведение (C1) к int (T1) лучше, чем приведение (C2) к uint (T2) или ulong (T2), потому что:
- Если T1 - int, а T2 - uint или ulong, C1 - лучшее преобразование.
Преобразование (C1) в int (T1) лучше, чем приведение (C2) к long (T2), потому что существует неявное приведение от int к long:
- Если неявное преобразование из T1 в T2 существует, и неявное преобразование из T2 в T1 не существует, C1 - лучшее преобразование.
Следовательно, используется функция int + int, которая возвращает int.
Это очень долгий путь, чтобы сказать, что он очень глубоко скрыт в спецификации C#.
CLI
CLI работает только на 6 типах (int32, native int, int64, F, O и &). (ECMA-335, раздел 3, раздел 1.5)
Байт (int8) не является одним из этих типов и автоматически передается в int32 перед добавлением. (ECMA-335, раздел 3, раздел 1.6)
Ответы, указывающие на некоторую неэффективность добавления байтов и усечения результата обратно в байт, неверны. Процессоры x86 имеют инструкции, специально предназначенные для целочисленной работы в 8-битных количествах.
Фактически, для процессоров x86/64 выполнение 32-битных или 16-битных операций менее эффективно, чем 64-битных или 8-битных операций из-за байта префикса операнда, который должен быть декодирован. На 32-разрядных компьютерах выполнение 16-разрядных операций влечет за собой то же наказание, но для 8-разрядных операций все еще существуют специальные коды операций.
Многие архитектуры RISC имеют схожие родные эффективные инструкции. Те, которые, как правило, не имеют длины "хранить и преобразовать в значение со знаком в некотором бите".
Другими словами, это решение должно было основываться на восприятии типа байта, а не из-за неэффективности аппаратного обеспечения.
Я помню, как однажды читал что-то от Джона Скита (не могу найти его сейчас, я буду продолжать искать) о том, что байт на самом деле не перегружает оператор +. Фактически, при добавлении двух байтов, как в вашем примере, каждый байт фактически неявно преобразуется в int. Результатом этого, очевидно, является int. Теперь о том, ПОЧЕМУ это было разработано таким образом, я буду ждать, пока сам Джон Скит отправит сообщение:)
РЕДАКТИРОВАТЬ: нашел это! Отличная информация об этой самой теме здесь.
Это из-за переполнения и переноски.
Если вы добавите два 8-битных числа, они могут переполниться в 9-й бит.
Пример:
1111 1111
+ 0000 0001
-----------
1 0000 0000
Я не знаю точно, но я предполагаю, что ints
, longs
, а такжеdoubles
дают больше места, потому что они довольно большие, как есть. Кроме того, они кратны 4, что более эффективно для компьютеров, поскольку ширина внутренней шины данных составляет 4 байта или 32 бита (64 бита становятся все более распространенными в настоящее время). Байт и шорт немного более неэффективны, но они могут сэкономить место.
Из спецификации языка C# 1.6.7.5 7.2.6.2 Двоичные числовые продвижения он преобразует оба операнда в int, если не может вписать его в несколько других категорий. Я предполагаю, что они не перегружали оператор + для получения байта в качестве параметра, но хотели, чтобы он работал как-то нормально, поэтому они просто используют тип данных int.
Я подозреваю, что C# на самом деле вызывает operator+
определено на int
(который возвращает int
если вы не в checked
блок), и неявно приведение обоих ваших bytes
/shorts
в ints
, Вот почему поведение кажется противоречивым.
Это было, вероятно, практическое решение со стороны языковых дизайнеров. В конце концов, int - это Int32, 32-разрядное целое число со знаком. Всякий раз, когда вы выполняете целочисленную операцию над типом, меньшим, чем int, она все равно будет преобразована в 32-битное целое число со знаком большинства большинства 32-битных процессоров. Это, в сочетании с вероятностью переполнения маленьких целых чисел, вероятно, закрыло сделку. Это избавляет вас от рутинной проверки на предмет избыточного / недостаточного потока, и когда конечный результат выражения в байтах окажется в диапазоне, несмотря на тот факт, что на каком-то промежуточном этапе он будет вне диапазона, вы получите правильный результат.
Другая мысль: переполнение / переполнение этих типов должно быть смоделировано, поскольку оно не будет происходить естественным образом на наиболее вероятных целевых процессорах. Зачем беспокоиться?
Это по большей части мой ответ, относящийся к этой теме, который сначала был представлен на подобный вопрос здесь.
Все операции с целыми числами, меньшими, чем Int32, округляются до 32 бит до вычисления по умолчанию. Причина, по которой результатом является Int32, заключается в том, чтобы просто оставить его как есть после расчета. Если вы проверяете арифметические коды операций MSIL, то единственными целочисленными числовыми типами, с которыми они работают, являются Int32 и Int64. Это "по замыслу".
Если вы хотите получить результат обратно в формате Int16, это не имеет значения, если вы выполняете приведение в коде, или компилятор (гипотетически) выполняет преобразование "под капотом".
Например, чтобы сделать арифметику Int16:
short a = 2, b = 3;
short c = (short) (a + b);
Два числа расширились бы до 32 битов, были бы добавлены, затем урезаны до 16 битов, что и было задумано MS.
Преимущество использования коротких (или байтовых) данных - это, прежде всего, хранение в тех случаях, когда у вас большие объемы данных (графические данные, потоковая передача и т. Д.)
У меня есть тест производительности между байтом и int.
Со значениями int:
class Program
{
private int a,b,c,d,e,f;
public Program()
{
a = 1;
b = 2;
c = (a + b);
d = (a - b);
e = (b / a);
f = (c * b);
}
static void Main(string[] args)
{
int max = 10000000;
DateTime start = DateTime.Now;
Program[] tab = new Program[max];
for (int i = 0; i < max; i++)
{
tab[i] = new Program();
}
DateTime stop = DateTime.Now;
Debug.WriteLine(stop.Subtract(start).TotalSeconds);
}
}
С байтовыми значениями:
class Program
{
private byte a,b,c,d,e,f;
public Program()
{
a = 1;
b = 2;
c = (byte)(a + b);
d = (byte)(a - b);
e = (byte)(b / a);
f = (byte)(c * b);
}
static void Main(string[] args)
{
int max = 10000000;
DateTime start = DateTime.Now;
Program[] tab = new Program[max];
for (int i = 0; i < max; i++)
{
tab[i] = new Program();
}
DateTime stop = DateTime.Now;
Debug.WriteLine(stop.Subtract(start).TotalSeconds);
}
}
Вот результат:
байт: 3,57 с 157 мес., 3,71 с 171 мес., 3,74 с 168 мес. с процессором ~ = 30%
int: 4,05 с 298 месяцев, 3,92 с 278 месяцев, 4,28 294 месяцев с процессором ~= 27%
Заключение:
байт использует больше ресурсов процессора, но это стоит меньше памяти и быстрее (возможно, из-за того, что выделяется меньше байтов)
Из кода.NET Framework:
// bytes
private static object AddByte(byte Left, byte Right)
{
short num = (short) (Left + Right);
if (num > 0xff)
{
return num;
}
return (byte) num;
}
// shorts (int16)
private static object AddInt16(short Left, short Right)
{
int num = Left + Right;
if ((num <= 0x7fff) && (num >= -32768))
{
return (short) num;
}
return num;
}
Упростите с.NET 3.5 и выше:
public static class Extensions
{
public static byte Add(this byte a, byte b)
{
return (byte)(a + b);
}
}
Теперь вы можете сделать:
byte a = 1, b = 2, c;
c = a.Add(b);
Добавление не определено для байтов. Поэтому они приводятся к int для дополнения. Это верно для большинства математических операций и байтов. (обратите внимание, что так было на старых языках, я полагаю, что сегодня это так).
Я думаю, что это проектное решение о том, какая операция была более распространенной... Если byte+byte = byte, возможно, гораздо больше людей будет обеспокоено необходимостью приводить к int, когда в качестве результата требуется int.
В дополнение ко всем другим замечательным комментариям, я подумал, что добавлю один маленький кусочек. Многие комментарии задавались вопросом, почему int, long и почти любой другой числовой тип также не следуют этому правилу... возвращают "больший" тип в ответ на арифметику.
Многие ответы были связаны с производительностью (ну, 32 бит быстрее, чем 8 бит). На самом деле, 8-битное число по-прежнему 32-битное число для 32-битного ЦП.... даже если вы добавите два байта, кусок данных, на котором работает процессор, будет 32-битным независимо, поэтому добавление целых не будет Быть "быстрее", чем добавлять два байта... это все равно для процессора. ТЕПЕРЬ, добавление двух целых будет БЫСТРЕЕ, чем добавление двух длинных на 32-битном процессоре, потому что добавление двух длинных требует большего количества микроопций, так как вы работаете с числами шире, чем у процессора
Я думаю, что основная причина побуждения байтовой арифметики к целым числам довольно ясна и прямолинейна: 8 бит просто не идут очень далеко!:D С 8 битами у вас есть диапазон без знака 0-255. Это не очень много места для работы... вероятность того, что вы столкнетесь с байтовыми ограничениями, ОЧЕНЬ высока при использовании их в арифметике. Однако вероятность того, что у вас закончатся биты при работе с целыми, длинными или двойными и т. Д., Значительно ниже... настолько мала, что мы очень редко сталкиваемся с необходимостью большего.
Автоматическое преобразование из байта в int логично, потому что масштаб байта очень мал. Автоматическое преобразование из int в long, float в double и т. Д. Не логично, поскольку эти числа имеют значительный масштаб.