байт + байт = int… чому?


365

Дивлячись на цей 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) швидше (через хіти кешу). Але широкі байтові коди, розповсюджені через код, роблять його набагато нечитабельнішим.



10
Тут не корисними будуть знання Еріка щодо стандарту - це його знання дизайну мови; що ні чому. Але так, відповідь Еріка була б досить остаточною :)
Джон Скіт

143
Різні роздуми нижче є розумним наближенням до дизайнерських міркувань. Більш загально: я не вважаю байти "цифрами"; Я думаю, що вони є візерунками бітів, які можна інтерпретувати як цифри, символи, кольори чи що завгодно. Якщо ви збираєтеся займатися математикою і обробляти їх як числа, то має сенс перемістити результат у тип даних, який частіше трактується як число.
Ерік Ліпперт

28
@Eric: Це має багато сенсу для байти, але, мабуть, не так багато сенсу для коротких / ушортів.
Джон Скіт

23
@Eric: byte1 | byte2зовсім не трактує їх як числа. Це трактує їх саме як візерунки бітів. Я розумію вашу точку зору, але так трапляється, що кожного разу, коли я робив будь-яку арифметику на байтах в C #, я насправді ставився до них як до бітів, а не до числа, і така поведінка завжди перешкоджає.
Роман Старков

Відповіді:


228

Третій рядок фрагмента коду:

byte z = x + y;

насправді означає

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

Отже, немає жодної операції над байтами, байти спочатку передаються цілим числам, а результатом додавання двох цілих чисел є (32-бітове) ціле число.


Я спробував код нижче, але він все ще не працює. байт z = (байт) x + (байт) y;
Анонім

10
це тому, що для байтів немає жодної операції (див. вище). Спробуйте byte z = (byte) ((int) x + (int) y)
azheglov

35
Це має бути найбільш правильною, стислою відповіддю. Немає операнду для додавання між байтами, тому замість пояснення, чому "додавання двох байтів" працює чи ні ( ніколи не бувало ), це чітко показує, чому результат є int, тому що єдине, що сталося, - це додавання 2 ints .
RichardTheKiwi

2
Мені запаморочилося читати всі інші відповіді (без образи на пана Джона Скіта). Це я знайшов найпростішу відповідь, яка правильно описує, що відбувається під кришкою. Дякую!
rayryeng

Ось відповідь , який я написав в іншому місці , який містить програму , щоб визначити , коли цей компілятор з приводом автоматичного просування на intце зустрічається: stackoverflow.com/a/43578929/4561887
Gabriel Скоба

172

З точки зору "чому це взагалі трапляється", це тому, що не існує жодних операторів, визначених C # для арифметики з байтом, sbyte, коротким або ушортом, як це сказали інші. Ця відповідь стосується того, чому ці оператори не визначені.

Я вважаю, що це в основному заради продуктивності. Процесори мають власні операції, щоб робити арифметичні з 32 бітами дуже швидко. Здійснення автоматичного перетворення з результату в байт автоматично може бути здійснено, але це призведе до штрафних покарань у тому випадку, коли ви насправді не бажаєте такої поведінки.

Я думаю, що це згадується в одному з анотованих стандартів C #. Дивлячись ...

EDIT: Напорядливо я переглянув коментовану специфікацію C # 2 ECMA, анотовану специфікацію MS C # 3 та специфікацію CLI анотації, і жодна з них не згадує про це, наскільки я бачу. Я впевнений, що бачив причину, наведену вище, але мене здувало, якщо я знаю, де. Вибачення, шанувальники посилання :(


14
Вибачте це, але це не найкраща відповідь.
VVS

42
Чи відмовились ви від кожної відповіді, яку ви вважаєте не найкращою? ;)
Джон Скіт

55
(Просто для уточнення, я насправді не маю на вас бажання. Здається, у кожного є свої критерії відхилення, і це нормально. Я відповідаю лише на відповідь, якщо вважаю, що це є активно шкідливим, а не просто неідеальним. )
Джон Скіт

21
Я використовую голосування як інструмент для отримання "найкращої" відповіді вгорі. Насправді я виявив, що ти взагалі багато не сказав у своїй відповіді, що було головною причиною мого протистояння. Ще одна причина, можливо, моє суб’єктивне відчуття того, що ваша представниця дає вам великий бонус, коли справа доходить до голосування, і ви висаджуєтеся на "кращі" відповіді.
VVS

23
ІМО найкращий спосіб отримати "найкращу" відповідь до вершини - зняти це. Якщо чесно, я думаю, що найбільш інформативною відповіддю тут є коментар Еріка у питанні ... але крім цього, щодо дизайнерської точки зору (на відміну від точки зору "що робить компілятор"), я не думаю, що є багато відповідь за межами "виконання". Зокрема, я дійсно не купую аргумент "це запобігає переповненню" (17 голосів), як це могло б запропонувати int + int = long.
Джон Скіт

68

Я думав, що бачив це десь раніше. З цієї статті "Стара нова річ" :

Припустимо, ми жили у світі фантазій, де операції над «байтом» призводили до «байту».

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

У цьому світі фантазій цінність i була б 16! Чому? Оскільки два операнди до оператора + є обома байтами, тож сума "b + c" обчислюється як байт, в результаті чого 16 відбувається через ціле переповнення. (І, як я вже зазначав, цілий переповнення - це новий вектор атаки безпеки.)

EDIT : Реймонд захищає, по суті, підхід C і C ++ застосовувався спочатку. У коментарях він відстоює той факт, що C # застосовує той самий підхід, ґрунтуючись на звороті сумісності мови.


42
З цілими числами, якщо ми додаємо їх, і вони переповнюються, це не автоматично передає його як інший тип даних, хоча так чому це робити з байтом?
Райан

2
З ints він переповнює. Спробуйте додати int.MaxValue + 1, ви отримаєте -2147483648 замість 2147483648.
Девід Басараб

8
@ Longhorn213: Так, це те, що говорить Райан: int math може переповнювати, але int math не повертає довго.
Майкл Петротта

28
Саме так. Якщо це має бути заходом безпеки, це дуже погано реалізований;)
Джон Скіт

5
@Ryan: "Ледачий" - це досить здоровенний заряд, який потрібно зробити проти дизайнерів мови C #, для чогось такого самого елементарного, як примітивна математика. Якщо ви хочете звинуватити їх у чому-небудь, зробіть це "надмірно зворотною сумісністю з C / C ++".
Майкл Петротта

58

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) до довгого (T2), оскільки є неявна передача від int до long:

  • Якщо існує неявна конверсія з T1 в T2, а неявна конверсія з T2 в T1 не існує, C1 є кращою конверсією.

Отже, використовується функція int + int, яка повертає int.

Що є дуже довгим способом сказати, що він похований дуже глибоко в специфікації C #.

CLI

CLI працює лише на 6 типів (int32, native int, int64, F, O і &). (Розділ 3 розділу ECMA-335, розділ 1.5)

Байт (int8) не є одним із таких типів і автоматично додається до int32 перед додаванням. (Розділ 3 розділу ECMA-335, розділ 1.6)


Це те, що ECMA визначає лише ті конкретні операції, що не завадить мові впроваджувати інші правила. VB.NET допоможе дозволити byte3 = byte1 And byte2без виклику, але безрезультатно викине виняток під час виконання, якщо дасть int1 = byte1 + byte2значення понад 255. Я не знаю, чи дозволяли б якісь мови byte3 = byte1+byte2і викинули виняток, коли це перевищує 255, але не викидає виняток, якщо int1 = byte1+byte2врожайність значення в діапазоні 256-510.
supercat

26

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

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

У багатьох архітектурах RISC є схожі інструкції щодо ефективного використання слів / байтів. Ті, які зазвичай не мають "зберігати та перетворювати" в "підписані" значення "якогось біта".

Іншими словами, це рішення повинно було базуватися на уявленні того, для чого тип байту, а не через неефективність апаратних засобів.


+1; якби тільки це сприйняття не було помилковим кожен раз, коли я коли-небудь зміщувався, АБО би два байти в C # ...
Роман Старков

Не повинно бути жодних витрат на ефективність обрізання результату. У збірці x86 - це лише різниця між копіюванням одного байта з регістра або чотирьох байтів з реєстру.
Джонатан Аллен

1
@JonathanAllen Рівно. Єдина відмінність полягає в тому, що за іронією долі при здійсненні розширення перетворення. Поточний дизайн тягне за собою зниження продуктивності для виконання зростаючого інструкції (або підписані продовжити або без знака поширюється.)
reirab

" сприйняття того, для чого призначений тип байта " - Це може пояснити цю поведінку для bytechar), але не для shortяких семантично є чіткою цифрою.
smls

13

Я пам'ятаю, як одного разу читав щось від Джона Скіта (не можу його зараз знайти, я продовжую шукати) про те, як байт насправді не перевантажує + оператора. Насправді, додаючи два байти, як у вашому зразку, кожен байт насправді неявно перетворюється на int. Результат цього, очевидно, є int. Тепер щодо того, ЧОМУ це було розроблено таким чином, я зачекаю, коли сам Джон Скіт викладе повідомлення :)

EDIT: Знайшов! Чудова інформація про цю саму тему тут .


9

Це через переповнення і несе.

Якщо додати два 8-бітних числа, вони можуть переповнюватися в 9-й біт.

Приклад:

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

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


23
Але більші типи даних не відповідають тій же поведінці.
Inisheer

12
Питання переповнення - осторонь. Якби ви взяли свою логіку і застосували її до мови, то всі типи даних повернули б більший тип даних після додавання арифметики, що, безумовно, НЕ так. int + int = int, long + long = long. Я думаю, що питання стосується невідповідності.
Йосип

Це була моя перша думка, але тоді чому не int + int = long? Тому я не купую суперечку про "можливий перелив" ... все-таки <grin>.
Роберт Картенто

11
О, а про аргумент "можливого переповнення", чому б не байт + байт = короткий?
Роберт Картенто

A) Чому він працює так, як це працює з урахуванням правил C #? Дивіться мою відповідь нижче. Б) Чому він був розроблений таким, яким він є? Ймовірно, лише міркування щодо юзабіліті, засновані на суб'єктивних судженнях про те, як більшість людей схильні використовувати вставки та байти.
mqp

5

З мовної специфікації C # 1.6.7.5 7.2.6.2 Бінарні числові акції перетворюють обидва операнди в int, якщо він не може вписати його в декілька інших категорій. Я здогадуюсь, що вони не перевантажували оператора +, щоб взяти байт як параметр, але хочуть, щоб він діяв нормально, щоб вони просто використовували тип даних int.

C # мова Spec


4

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


3
Він натискає обидва байти на стек, потім викликає команду "додати". У IL додайте "eats" два значення та заміняє їх на int.
Джонатан Аллен

3

Це, мабуть, було практичним рішенням з боку мовних дизайнерів. Зрештою, int - це Int32, 32-бітове ціле число. Щоразу, коли ви робите цілу операцію над типом, меншим від int, він все одно буде перетворений на 32-бітний підписаний int більшістю будь-якого 32-бітного процесора. Це, в поєднанні з ймовірністю переповнення малих цілих чисел, ймовірно, запечатало угоду. Це позбавляє вас від клопоту постійної перевірки на надмірний / надточний потік, і коли кінцевий результат вираження на байтах виявиться в діапазоні, незважаючи на те, що на деякому проміжному етапі він буде поза діапазоном, ви отримуєте правильний результат.

Інша думка: надмірний / недостаточний потік для цих типів повинен був би імітуватись, оскільки це не відбудеться природним чином на найбільш ймовірних цільових процесорах. Навіщо турбуватися?


2

Це здебільшого моя відповідь, що стосується цієї теми, подана спочатку на подібне запитання тут .

Усі операції з інтегральними числами меншими за Int32 округляються до 32 біт перед обчисленням за замовчуванням. Причина, чому результат Int32, просто залишити його таким, яким він є після обчислення. Якщо ви перевіряєте арифметичні опкоди MSIL, єдиним інтегральним числовим типом, з яким вони працюють, є Int32 та Int64. Це "задумом".

Якщо ви бажаєте отримати результат у форматі Int16, це не має значення, якщо ви виконуєте роль в коді, або компілятор (гіпотетично) видає перетворення "під капотом".

Наприклад, виконати арифметику Int16:

short a = 2, b = 3;

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

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

Перевага використання коротких (або байтових) даних полягає в першу чергу в зберіганні у випадках, коли у вас є величезна кількість даних (графічні дані, потокове передавання тощо)


1

Додавання не визначено для байтів. Тож їх додають до int для додавання. Це справедливо для більшості математичних операцій та байтів. (зауважте, так це було раніше у старих мовах; я припускаю, що це актуально і сьогодні).


0

Я думаю, що це рішення дизайну щодо того, яка операція була більш поширеною ... Якщо байт + байт = байт, можливо, набагато більше людей буде занепокоєно тим, що потрібно кидати в Int, коли в результаті вимагається int.


2
Мене колись турбує інший шлях :) Мені завжди здається, що потрібен байт-результат, тому мені завжди доводиться робити це.
Роман Старков

За винятком того, що вам не доведеться кидати в Int. Актриса неявна. Тільки інший спосіб явний.
Нікі

1
@nikie Я думаю, ти не зрозумів моєї відповіді. Якщо додавання двох байтів створює байт, для запобігання переповнення комусь доведеться передати операнди (не результат) до int перед додаванням.
фортран

0

Від коду .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

Я перевіряю продуктивність між байтом та 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,57s 157mo, 3,71s 171mo, 3,74s 168mo з процесором ~ = 30%
int: 4,05s 298mo, 3,92s 278mo, 4,28 294mo з CPU ~ = 27%
Висновок:
байт більше використовує процесор, але це коштує менше пам'яті, і це швидше (можливо, тому що менше байтів для виділення)


-1

На додачу до всіх інших чудових коментарів, я думав, що додам ще одну маленьку примху. Багато коментарів задаються питанням, чому int, long і майже будь-який інший числовий тип також не дотримується цього правила ... поверніть "більший" тип у відповідь на арифметичний.

Дуже багато відповідей стосується продуктивності (ну, 32 біт швидше, ніж 8 біт). Насправді 8-бітове число - це все-таки 32-бітове число до 32-бітового процесора .... навіть якщо ви додасте два байти, шматок даних, на якому працює процесор, буде 32-бітним незалежно ... тому додавання ints не збирається будь-який "швидший", ніж додавання двох байтів ... його все одно на процесор. ЗАРАЗ, додавання двох ints БУДЕ швидше, ніж додавання двох довгих 32-бітових процесорів, тому що для додавання двох довгих потрібно більше мікрооперацій, оскільки ви працюєте з числами, ширшими, ніж слово процесорів.

Я думаю, що фундаментальна причина того, що арифметика байтів призводить до ints, є досить чіткою і прямою: 8bit просто не надто далеко! : D З 8 бітами у вас діапазон без підпису 0-255. Це не багато можливостей для роботи ... ймовірність того, що ви збираєтеся зіткнутися з обмеженнями в байтах, ДУЖЕ висока при використанні їх в арифметиці. Однак ймовірність того, що у вас будуть вичерпані шматочки, коли ви працюєте з ints, або longs, або doublelies тощо, значно нижчий ... досить низький, що ми дуже рідко стикаємося з потребою в більшому.

Автоматичне перетворення від байта до int є логічним, оскільки масштаб байта настільки малий. Автоматичне перетворення з int в long, плаваюче в подвійне тощо не є логічним, оскільки ці числа мають значну шкалу.


Це все ще не пояснює, чому byte - byteповертається int, або чому вони не short
звертаються

Чому ви хочете, щоб додавання повертало інший тип, ніж віднімання? Якщо byte + byteповертається int, оскільки 255 + що-небудь більше, ніж вміщає байт, не має сенсу байт мінус будь-який інший байт повертає щось інше, ніж int з точки зору узгодженості типу повернення.
jrista

Я б не став, це просто показує, що вищезазначена причина, мабуть, не вірна. Якби це було пов'язане з "вписуванням" в результат, byteвіднімання поверне a byte, а додавання байтів поверне a short( byte+ byteзавжди буде вписуватися в a short). Якщо мова йшла про послідовність, як ви кажете, то shortвсе одно вистачить для обох операцій, а не int. Очевидно, що існує сукупність причин, не всі вони обов'язково добре продумані. Або причина виконання, наведена нижче, може бути більш точною.
KthProg
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.