it-swarm.com.ru

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

338
Robert Cartaino

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

byte z = x + y;

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

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

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

206
azheglov

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

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

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

Правка: досадно, я теперь просмотрел аннотированную спецификацию C # 2 ECMA, аннотированную спецификацию MS C # 3 и спецификацию CLI аннотации, и ни один из них не упоминают об этом, насколько я вижу. Я уверен я видел причину, приведенную выше, но я ошарашен, если знаю, где. Извиняюсь, отзыв фанатов :(

161
Jon Skeet

Я подумал Я видел это где-то раньше. От этой статьи, The Old New Thing :

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

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

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

EDIT: Раймонд защищает, по сути, подход C и C++, который изначально использовался. В комментариях он защищает тот факт, что C # использует тот же подход на основе обратной совместимости языка.

67
Michael Petrotta

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)

57
Alun Harford

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

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

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

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

25
Christopher

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

Правка: Нашел это! Отличная информация об этой самой теме здесь .

13
BFree

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

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

Пример:

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

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

6
samoz

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

C # Язык Spec

5
Ryan

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

4
mquander

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

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

3
PeterAllenWebb

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

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

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

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

short a = 2, b = 3;

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

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

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

2
Kenan E. K.

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

1
fortran

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

1
Jim C

У меня есть тест производительности между байтом и 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.05s 298mo, 3.92s 278mo, 4.28 294mo с процессором ~ = 27%
Заключение :
байт использует больше ресурсов процессора, но это стоит меньше памяти и быстрее (возможно, из-за того, что выделяется меньше байтов)

1
puipuix

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

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

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

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

0
jrista