байт + байт = 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) происходит быстрее (из-за попаданий в кэш). Но обширные броски байтов, распространяемые по коду, делают его намного более нечитаемым.

2.06.2009 19:59:00
Timothy Carter 2.06.2009 20:01:16
Это не знание Эриком стандарта, которое было бы здесь полезно - это его знание дизайна языка; что не почему. Но да, ответ Эрика был бы довольно определенным :)
Jon Skeet 2.06.2009 20:35:27
Различные размышления ниже - разумное приближение к проектным соображениям. В более общем смысле: я не считаю байты «числами»; Я думаю о них как о шаблонах битов, которые можно интерпретировать как числа, или символы, или цвета, или что-то еще. Если вы собираетесь делать с ними математику и рассматривать их как числа, то имеет смысл переместить результат в тип данных, который чаще интерпретируется как число.
Eric Lippert 2.06.2009 20:57:34
@Eric: Это имеет большой смысл для байтов, но, вероятно, не так много для коротких / ushort.
Jon Skeet 3.06.2009 12:24:36
@Eric: byte1 | byte2вовсе не рассматривает их как числа. Это относится к ним как к шаблонам битов. Я понимаю вашу точку зрения, но так получилось, что каждый раз, когда я выполнял какую-либо арифметику с байтами в C #, я фактически рассматривал их как биты, а не числа, и такое поведение всегда на пути.
Roman Starkov 28.12.2009 11:46:18
16 ОТВЕТОВ

Это из-за переполнения и переноски.

Если вы добавите два 8-битных числа, они могут переполниться в 9-й бит.

Пример:

  1111 1111
+ 0000 0001
-----------
1 0000 0000

Я не знаю наверняка, но я предполагаю, что ints, longsи мне doublesдают больше места, потому что они довольно большие, как есть. Кроме того, они кратны 4, что более эффективно для компьютеров, поскольку ширина внутренней шины данных составляет 4 байта или 32 бита (64 бита становятся все более распространенными в настоящее время). Байт и шорт немного более неэффективны, но они могут сэкономить место.

9
24.04.2014 05:49:43
Но большие типы данных не следуют тому же самому поведению.
Inisheer 2.06.2009 20:04:58
Проблемы переполнения остаются в стороне. Если бы вы взяли свою логику и применили ее к языку, то после добавления арифметики все типы данных возвращали бы больший тип данных, что, безусловно, НЕ имеет место. int + int = int, long + long = long. Я думаю, что вопрос в отношении несоответствия.
Joseph 2.06.2009 20:05:55
Это была моя первая мысль, но тогда почему int + int = long? Так что я не покупаю аргумент "возможного переполнения" ... пока <ухмылка>.
Robert Cartaino 2.06.2009 20:07:19
О, а насчет аргумента "возможного переполнения", почему не byte + byte = short?
Robert Cartaino 2.06.2009 20:08:11
А) Почему это работает так, как работает, учитывая правила C #? Смотрите мой ответ ниже. Б) Почему он был разработан так, как есть? Вероятно, это просто соображения юзабилити, основанные на субъективных суждениях о том, как большинство людей склонны использовать целые числа и байты.
mqp 2.06.2009 20:12:02

Я думал, что видел это где-то раньше. Из этой статьи, Старое Новое :

Предположим, мы жили в фантастическом мире, где операции с байтом приводили к байту.

byte b = 32;
byte c = 240;
int i = b + c; // what is i?

В этом фэнтезийном мире ценность меня будет 16! Почему? Поскольку оба операнда оператора + являются байтами, сумма «b + c» вычисляется как байт, что приводит к 16 из-за переполнения целых чисел. (И, как я отмечал ранее, целочисленное переполнение является новым вектором атаки безопасности.)

РЕДАКТИРОВАТЬ : Раймонд, по сути, защищает подход C и C ++ первоначально. В комментариях он защищает тот факт, что C # использует тот же подход на основе обратной совместимости языка.

68
2.06.2009 21:10:24
С целыми числами, если мы добавляем их и они переполняются, это не приводит к автоматическому преобразованию его в другой тип данных, так почему же это делается с байтом?
Ryan 2.06.2009 20:06:22
С int это переполнение. Попробуйте добавить int.MaxValue + 1, вместо 2147483648 вы получите -2147483648.
David Basarab 2.06.2009 20:13:37
@ Longhorn213: Да, это то, что говорит Райан: int math может переполниться, но int math не возвращает longs.
Michael Petrotta 2.06.2009 20:15:10
Точно. Если это мера безопасности, то она очень плохо реализована;)
Jon Skeet 2.06.2009 20:15:16
@Ryan: "ленивый" - довольно серьезное обвинение против разработчиков языка C #, за что-то такое же простое, как примитивная математика. Если вы хотите обвинить их в чем-либо, сделайте это «чрезмерной обратной совместимостью с C / C ++».
Michael Petrotta 2.06.2009 20:33:21

Я думаю, что это проектное решение о том, какая операция была более распространенной ... Если byte + byte = byte, возможно, гораздо больше людей будет обеспокоено необходимостью приводить к int, когда в качестве результата требуется int.

0
2.06.2009 20:05:02
На этот раз меня беспокоит другой путь :) Мне всегда нужен результат в байтах, поэтому я всегда должен приводить.
Roman Starkov 28.12.2009 11:53:07
За исключением того, что вам не нужно приводить к int. Актерский состав неявный. Только другой способ является явным.
Niki 15.03.2010 08:31:16
@nikie Я думаю, ты не понял мой ответ. Если добавление двух байтов приведет к получению байта, для предотвращения переполнения кому-то придется преобразовать операнды (а не результат) в int перед добавлением.
fortran 15.03.2010 11:04:07

Я подозреваю, что C # на самом деле вызывает operator+определенное значение int(которое возвращает intисключение, если вы не в checkedблоке) и неявно приводит оба ваших bytes/ shortsк ints. Вот почему поведение кажется противоречивым.

4
2.06.2009 20:05:52
Он помещает оба байта в стек, а затем вызывает команду «add». В IL добавьте «съедает» два значения и заменяет их на int.
Jonathan Allen 8.08.2010 05:21:35

Я помню, как однажды читал что-то от Джона Скита (не могу сейчас найти, я буду продолжать искать) о том, что байт на самом деле не перегружает оператор +. Фактически, при добавлении двух байтов, как в вашем примере, каждый байт фактически неявно преобразуется в int. Результатом этого, очевидно, является int. Теперь о том, ПОЧЕМУ это было разработано таким образом, я буду ждать, пока сам Джон Скит отправит сообщение :)

РЕДАКТИРОВАТЬ: нашел это! Отличная информация об этой самой теме здесь .

13
2.06.2009 20:06:14

Добавление не определено для байтов. Таким образом, они приводятся к int для дополнения. Это верно для большинства математических операций и байтов. (обратите внимание, что так было на старых языках, я предполагаю, что сегодня это так).

1
3.06.2009 12:03:46

Вероятно, это было практическое решение со стороны языковых дизайнеров. В конце концов, int - это Int32, 32-разрядное целое число со знаком. Всякий раз, когда вы выполняете целочисленную операцию над типом, меньшим, чем int, он все равно будет преобразован в 32-битное целое число со знаком большинства большинства 32-битных процессоров. Это, в сочетании с вероятностью переполнения маленьких целых чисел, вероятно, заключило сделку. Это избавляет вас от рутинной проверки на предмет избыточного / недостаточного потока, и когда конечный результат выражения в байтах окажется в диапазоне, несмотря на то, что на некотором промежуточном этапе он будет вне диапазона, вы получите правильный результат.

Другая мысль: переполнение / переполнение этих типов должно быть смоделировано, поскольку оно не будет происходить естественным образом на наиболее вероятных целевых ЦП. Зачем беспокоиться?

3
2.06.2009 20:06:51

С точки зрения «почему это вообще происходит», это потому, что в C # нет никаких операторов, определенных для арифметики с байтами, sbyte, short или ushort, как уже говорили другие. Этот ответ о том, почему эти операторы не определены.

Я считаю, что это в основном ради производительности. Процессоры имеют собственные операции для выполнения арифметики с 32-битными очень быстро. Можно автоматически выполнить преобразование обратно из результата в байт , но это приведет к снижению производительности в том случае, если вы на самом деле не хотите такого поведения.

Я думаю, что это упоминается в одном из аннотированных стандартов C #. Ищу...

РЕДАКТИРОВАТЬ: досадно, я теперь просмотрел аннотированную спецификацию ECMA C # 2, аннотированную спецификацию MS C # 3 и аннотацию CLI, и ни один из них не упомянул это, насколько я вижу. Я уверен, что видел причину, приведенную выше, но я взорван, если знаю, где. Извиняюсь, отзыв фанатов :(

172
23.06.2012 06:31:28
Мне жаль это говорить, но я нахожу это не лучшим ответом.
VVS 3.06.2009 12:16:44
Вы опровергли каждый ответ, который вы считаете не лучшим? ;)
Jon Skeet 3.06.2009 12:23:43
(Просто, чтобы уточнить, я на самом деле не проверяю вас. Кажется, у каждого свои критерии понижения голосов, и это нормально. Я только одобряю ответ, если считаю, что он скорее вреден, чем не идеален. )
Jon Skeet 3.06.2009 12:26:07
Я использую голосование как инструмент, чтобы получить «лучший» ответ на вершину. На самом деле я обнаружил, что вы ничего не сказали в своем ответе, что было главной причиной моего отрицательного голоса. Другой причиной может быть мое субъективное ощущение, что ваш представитель дает вам большой бонус, когда дело доходит до голосования, и вы попадаете на вершину «лучших» ответов.
VVS 3.06.2009 12:56:34
ИМО лучший способ получить «лучший» ответ на вершину - это поднять это. Честно говоря, я думаю, что наиболее информативным ответом здесь является комментарий Эрика в вопросе ... но кроме этого, для перспективы дизайна (в отличие от перспективы "что делает компилятор"), я не думаю, что есть много ответ за пределы «производительности». В частности, я действительно не покупаю аргумент «это предотвращает переполнение» (17 голосов), поскольку это предполагает int + int = long.
Jon Skeet 3.06.2009 13:14:49

Из спецификации языка C # 1.6.7.5 7.2.6.2 Двоичные числовые продвижения он преобразует оба операнда в int, если не может вписать его в несколько других категорий. Я предполагаю, что они не перегружали оператор + для получения байта в качестве параметра, но хотели, чтобы он работал как обычно, поэтому они просто используют тип данных int.

Спецификация языка C #

5
2.06.2009 20:13:28

Третья строка вашего кода:

byte z = x + y;

на самом деле означает

byte z = (int) x + (int) y;

Таким образом, в байтах нет операции +, байты сначала приводятся к целым числам, а результатом сложения двух целых чисел является (32-разрядное) целое число.

228
2.06.2009 20:17:51
Я пробовал код ниже, но он все еще не работает. байт z = (байт) x + (байт) y;
Anonymous 4.06.2009 05:51:35
это потому, что нет операции + для байтов (см. выше). Попробуйте байт z = (byte) ((int) x + (int) y)
azheglov 5.06.2009 18:51:40
Это должен быть самый правильный и краткий ответ. Нет операнда для добавления между байтами, поэтому вместо объяснения, почему «добавление двух байтов» работает или нет ( этого никогда не было ), это ясно показывает, почему результатом является int, потому что единственное, что произошло, это добавление 2-х целых ,
RichardTheKiwi 3.04.2011 23:22:08
У меня закружилась голова при чтении всех остальных ответов (без обид мистера Джона Скита). Я нашел, что это самый простой ответ, который правильно описывает, что происходит под капотом. Спасибо!
rayryeng 7.01.2015 17:34:55
Вот ответ, который я написал в другом месте, в котором содержится программа, позволяющая определить, когда intпроисходит автоматическое продвижение на основе компилятора : stackoverflow.com/a/43578929/4561887
Gabriel Staples 25.04.2017 01:22:25

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

Фактически, для процессоров x86 / 64 выполнение 32-битных или 16-битных операций менее эффективно, чем 64-битных или 8-битных операций из-за байта префикса операнда, который должен быть декодирован. На 32-разрядных компьютерах выполнение 16-разрядных операций влечет за собой то же наказание, но для 8-разрядных операций все еще существуют специальные коды операций.

Многие архитектуры RISC имеют схожие родные эффективные инструкции. Те, которые, как правило, не имеют длины «хранить и преобразовать в значение со знаком в некотором бите».

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

26
2.06.2009 21:30:24
+1; если бы только это восприятие не было неправильным каждый раз, когда я сдвигался и OR передавал два байта в C # ...
Roman Starkov 28.12.2009 11:43:23
Не должно быть никаких затрат производительности для усечения результата. В сборке x86 разница только в копировании одного байта из регистра или четырех байтов из регистра.
Jonathan Allen 8.08.2010 04:55:49
@JonathanAllen Точно. По иронии судьбы, единственное отличие заключается в расширении конверсии. Текущий дизайн влечет за собой снижение производительности для выполнения увеличивающегося инструкции (либо подписаны продлить или без знака распространяется.)
reirab 26.06.2015 20:55:15
« Восприятие того, для чего предназначен тип байта » - это может объяснить это поведение для bytechar), но не для shortкоторого семантически это явно число.
smls 19.04.2018 06:36:21

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)

58
2.06.2009 23:21:36
То, что ECMA определяет только эти конкретные операции, не помешает языку реализовать другие правила. VB.NET будет любезно разрешить byte3 = byte1 And byte2без приведения, но бесполезно сгенерирует исключение времени выполнения, если получит int1 = byte1 + byte2значение больше 255. Я не знаю, позволят ли какие-либо языки byte3 = byte1+byte2и сгенерирует исключение, если оно превышает 255, но не сгенерирует исключение, если int1 = byte1+byte2дает значение в диапазоне 256-510.
supercat 6.07.2014 20:28:43

В дополнение ко всем другим замечательным комментариям, я подумал, что добавлю один маленький кусочек. Многие комментарии задавались вопросом, почему int, long и почти любой другой числовой тип также не следуют этому правилу ... возвращают «больший» тип в ответ на арифметику.

Многие ответы были связаны с производительностью (ну, 32 бита быстрее, чем 8 бит). На самом деле, 8-битное число по-прежнему 32-битное число для 32-битного ЦП .... даже если вы добавите два байта, кусок данных, на котором работает процессор, будет 32-битным независимо ... поэтому добавление целых не будет Быть "быстрее", чем добавлять два байта ... это все равно для процессора. ТЕПЕРЬ, добавление двух целых будет БЫСТРЕЕ, чем добавление двух длинных на 32-битном процессоре, потому что добавление двух длинных требует большего количества микроопций, так как вы работаете с числами шире, чем у процессора

Я думаю, что основная причина побуждения байтовой арифметики к целым числам довольно ясна и прямолинейна: 8 бит просто не идут очень далеко! : D С 8 битами у вас есть диапазон без знака 0-255. Это не много места для работы ... вероятность того, что вы столкнетесь с байтовыми ограничениями, ОЧЕНЬ высока при использовании их в арифметике. Однако вероятность того, что у вас закончатся биты при работе с целыми, длинными или двойными и т. Д., Значительно ниже ... настолько мала, что мы очень редко сталкиваемся с необходимостью большего.

Автоматическое преобразование из байта в int логично, потому что масштаб байта очень мал. Автоматическое преобразование из int в long, float в double и т. Д. Не логично, поскольку эти числа имеют значительный масштаб.

-1
8.04.2013 11:22:33
Это все еще не объясняет, почему byte - byteвозвращается int, или почему они не бросают short...
KthProg 17.10.2017 20:35:55
Почему вы хотите, чтобы сложение возвращало другой тип, чем вычитание? Если byte + byteвозвращается int, потому что 255 + что-либо больше, чем может содержать байт, не имеет смысла, чтобы какой-либо байт, кроме любого другого байта, возвращал что-либо кроме int с точки зрения согласованности возвращаемого типа.
jrista 24.10.2017 20:48:49
Я бы не стал, это просто показывает, что приведенная выше причина, вероятно, неверна. Если бы это было связано с «подгонкой» к результату, то byteвычитание вернуло бы a byte, а сложение байтов вернуло бы short( byte+ byteвсегда будет вписываться в a short). Если бы речь шла о последовательности, как вы говорите, то shortвсе равно было бы достаточно для обеих операций, а не int. Понятно, что есть смесь причин, но не все они обязательно продуманы. Или причина производительности, приведенная ниже, может быть более точной.
KthProg 25.10.2017 19:25:30

Это по большей части мой ответ, относящийся к этой теме, который сначала был представлен на подобный вопрос здесь .

Все операции с целыми числами, меньшими, чем Int32, округляются до 32 бит до вычисления по умолчанию. Причина, по которой результатом является Int32, заключается в том, чтобы просто оставить его как есть после расчета. Если вы проверяете арифметические коды операций MSIL, то единственными целыми числовыми типами, с которыми они работают, являются Int32 и Int64. Это "по замыслу".

Если вы хотите получить результат обратно в формате Int16, это не имеет значения, если вы выполняете приведение в коде, или компилятор (гипотетически) выполняет преобразование «под капотом».

Например, чтобы сделать арифметику Int16:

short a = 2, b = 3;

short c = (short) (a + b);

Два числа будут расширены до 32 битов, добавлены, а затем усечены до 16 битов, как и предполагал MS.

Преимущество использования коротких (или байтовых) данных - это, прежде всего, хранение в тех случаях, когда у вас большие объемы данных (графические данные, потоковая передача и т. Д.)

2
23.05.2017 11:47:24

Из кода .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);

0
1.02.2010 10:44:26

У меня есть тест производительности между байтом и 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%
Вывод:
байт использует больше ЦП, но это стоит меньше памяти и быстрее (возможно потому, что выделяется меньше байтов)

0
1.11.2018 11:37:19