Подтвердить что ты не робот

Почему ushort + ushort равно int?

Ранее сегодня я пытался добавить два ushorts и заметил, что мне пришлось вернуть результат обратно в ushort. Я думал, что это могло бы стать uint (чтобы предотвратить возможное непреднамеренное переполнение?), Но, к моему удивлению, это был int (System.Int32).

Есть ли какая-то умная причина для этого или, может быть, потому, что int рассматривается как "базовый" целочисленный тип?

Пример:

ushort a = 1;
ushort b = 2;

ushort c = a + b; // <- "Cannot implicitly convert type 'int' to 'ushort'. An explicit conversion exists (are you missing a cast?)"
uint d = a + b; // <- "Cannot implicitly convert type 'int' to 'uint'. An explicit conversion exists (are you missing a cast?)"

int e = a + b; // <- Works!

Редактировать: Как в ответе GregS, в спецификации С# говорится, что оба операнда (в этом примере "a" и "b") должны быть преобразованы в int. Меня интересует основная причина, по которой это является частью спецификации: почему спецификация С# не допускает операций непосредственно над значениями ushort?

4b9b3361

Ответ 1

Простой и правильный ответ - "потому что спецификация языка С# так говорит".

Ясно, что вы не удовлетворены этим ответом и хотите знать "почему это так". Вы ищете "надежные и/или официальные источники", это будет немного сложно. Эти проектные решения были сделаны давно, 13 лет - много жизни собак в разработке программного обеспечения. Они были сделаны "старыми таймерами", как их называет Эрик Липпер, они перешли к более крупным и лучшим вещам и не публикуют ответы здесь, чтобы предоставить официальный источник.

Это может быть выведено, однако, с риском просто быть заслуживающим доверия. Любой управляемый компилятор, такой как С#, имеет ограничение на то, что ему нужно сгенерировать код для виртуальной машины .NET. Правила для которых тщательно (и вполне читаемы) описаны в спецификации CLI. Это спецификация Ecma-335, вы можете скачать ее бесплатно здесь.

Перейдите к разделу III, глава 3.1 и 3.2. Они описывают две команды IL, доступные для выполнения добавления, add и add.ovf. Нажмите ссылку на таблицу 2 "Двоичные числовые операции", в которой описаны, какие операнды допустимы для этих инструкций IL. Обратите внимание, что здесь есть всего несколько типов. байтовый и короткий, а также все неподписанные типы отсутствуют. Допускается только int, long, IntPtr и с плавающей запятой (float и double). С дополнительными ограничениями, отмеченными x, вы не можете добавить int к длинному, например. Эти ограничения не являются полностью искусственными, они основаны на том, что вы можете сделать достаточно эффективно на доступном оборудовании.

Любой управляемый компилятор должен иметь дело с этим, чтобы генерировать действительный IL. Это не сложно, просто преобразуйте ushort в более крупный тип значения, который в таблице - это всегда правильное преобразование. Компилятор С# выбирает int, следующий более крупный тип, который появляется в таблице. Или, вообще говоря, конвертируйте любой из операндов в следующий наибольший тип значения, чтобы они оба имели один и тот же тип и отвечали ограничениям в таблице.

Теперь возникает новая проблема, проблема, которая заставляет программистов на С# довольно беспорядочно. Результат добавления относится к продвинутому типу. В вашем случае это будет int. Таким образом, добавление двух значений ushort, скажем, 0x9000 и 0x9000, имеет вполне допустимый результат int: 0x12000. Проблема заключается в следующем: это значение, которое не соответствует возврату в пользовательское пространство. Значение переполнено. Но он не переполнялся в расчете IL, он переполняется только тогда, когда компилятор пытается втиснуть его обратно в ushort. 0x12000 усекается до 0x2000. Неожиданное другое значение, которое имеет смысл только при подсчете с 2 или 16 пальцами, а не с 10.

Примечательно, что команда add.ovf не справляется с этой проблемой. Это инструкция для автоматической генерации исключения переполнения. Но это не так, фактический расчет на преобразованных ints не переполнялся.

Здесь происходит реальное дизайнерское решение. Похоже, старожилы решили, что просто усечение результата int в ushort было ошибкой factory. Это определенно. Они решили, что вам нужно признать, что вы знаете, что добавление может переполняться и что все в порядке, если это произойдет. Они сделали это вашей проблемой, главным образом потому, что они не знали, как сделать это, и по-прежнему генерировать эффективный код. Вы должны бросить. Да, это безумие, я уверен, что вы тоже не хотели этой проблемы.

Вполне примечательно, что разработчики VB.NET приняли другое решение проблемы. Они фактически сделали это своей проблемой и не прошли мимо. Вы можете добавить два UShorts и назначить их в UShort без трансляции. Разница заключается в том, что компилятор VB.NET фактически генерирует дополнительный IL для проверки состояния переполнения. Этот дешевый код делает каждое короткое дополнение примерно в 3 раза медленнее. Но в остальном причина, объясняющая, почему Microsoft поддерживает два языка, которые имеют в тоже время очень похожие возможности.

Короче говоря: вы платите цену, потому что используете тип, который не очень хорошо сочетается с современными архитектурами процессора. Что само по себе является действительно хорошим основанием для использования uint вместо ushort. Получение тяги из Ushort затруднено, вам нужно много их, прежде чем затраты на их манипулирование - весит экономию памяти. Не только из-за ограниченной спецификации CLI, ядро ​​x86 занимает дополнительный цикл процессора, чтобы загрузить 16-битное значение из-за байта префикса операнда в машинный код. На самом деле не уверен, что это все еще так, но когда-то он возвращался, когда я все еще обращал внимание на циклы подсчета. Собака год назад.


Помните, что вы можете чувствовать себя лучше об этих уродливых и опасных бросках, позволяя компилятору С# генерировать тот же код, который генерирует компилятор VB.NET. Таким образом, вы получаете исключение OverflowException, когда литье оказалось неразумным. Используйте "Проект" > "Свойства" > вкладка "Строка" > "Дополнительно" > отметьте флажок "Проверить арифметическое переполнение/недополнение". Только для сборки Debug. Почему этот флажок не включен автоматически шаблоном проекта, является еще одним очень загадочным вопросом, решение, которое было сделано слишком давно.

Ответ 2

ushort x = 5, y = 12;

Следующий оператор присваивания приведет к ошибке компиляции , потому что по умолчанию арифметическое выражение в правой части оператора присваивания по умолчанию имеет значение int.

ushort z = x + y;   // Error: conversion from int to ushort

http://msdn.microsoft.com/en-us/library/cbf1574z(v=vs.71).aspx

EDIT:
В случае арифметических операций над ushort операнды преобразуются в тип, который может содержать все значения. Таким образом, можно избежать переполнения. Операнды могут меняться в порядке int, uint, long и ulong. См. Спецификация языка С# В этом документе перейдите к разделу 4.1.5 Интегральные типы (около страницы 80 в документе слова). Здесь вы найдете:

Для двоичных чисел +, -, *,/,%, &, ^, |, ==,! =, > , <, >= и <= операторы, операнды преобразуются в тип T, где T - первое int, uint, long и ulong, которые могут полностью представлять все возможные значения обоих операндов. Затем операцию выполняют с использованием точность типа T, а тип результата - T (или bool для реляционные операторы). Не допускается, чтобы один операнд тип long, а другой - типа ulong с бинарными операторами.

Эрик Липпер заявил в question

Арифметика никогда не выполняется в шортах на С#. Арифметика может быть выполнена в ints, uints, longs и ulongs, но арифметика никогда не делается в шортах. Шорты продвигают к int, и арифметика выполняется в ints, потому что Я уже говорил, что подавляющее большинство арифметических вычислений int. Подавляющее большинство не вписывается в короткий. Короткая арифметика возможно, медленнее на современном оборудовании, которое оптимизировано для ints, и короткая арифметика не занимает меньше места; это будет сделанные в ints или longs на чипе.

Ответ 3

Из спецификации языка С#:

7.3.6.2 Двоичные числовые рекламные акции Двоичное числовое продвижение происходит для операндов предопределенных операторов +, -, *,/,%, &, |, ^, ==,! =, > , <, >= и <= двоичных. Двоичное числовое продвижение неявно преобразует оба операнда в общий тип, который в случае нереляционных операторов также становится типом результата операции. Двоичное числовое продвижение состоит из применения следующих правил в следующем порядке:

. Если любой из операндов имеет тип decimal, другой операнд преобразуется в тип десятичного или возникает ошибка привязки, если другой операнд имеет тип float или double.

. В противном случае, если любой из операндов имеет тип double, другой операнд преобразуется в тип double.

. В противном случае, если любой из операндов имеет тип float, другой операнд преобразуется в тип float.

. В противном случае, если любой из операндов имеет тип ulong, другой операнд преобразуется в тип ulong, или возникает ошибка времени привязки, если другой операнд имеет тип sbyte, short, int или long.

. В противном случае, если любой из операндов имеет тип long, другой операнд преобразуется в тип long.

. В противном случае, если любой операнд имеет тип uint, а другой операнд имеет тип sbyte, short или int, оба операнда преобразуются в тип long.

. В противном случае, если любой операнд имеет тип uint, другой операнд преобразуется в тип uint.

. В противном случае оба операнда преобразуются в тип int.

Ответ 4

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

Это указано в Спецификации С#, раздел 7.3.6 следующим образом:

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

Далее иллюстрируется пример:

В качестве примера числовой рекламы рассмотрите предопределенные реализации двоичного * оператора:

int operator * (int x, int y);

uint operator * (uint x, uint y);

длинный оператор * (long x, long y);

ulong operator * (ulong x, ulong y);

float operator * (float x, float y);

двойной оператор * (double x, double y);

десятичный оператор * (десятичный x, десятичный y);

Когда к этому набору операторов применяются правила разрешения перегрузки (§7.5.3), эффект заключается в том, чтобы выбрать первый из операторов, для которых неявные преобразования существуют из типов операндов. Например, для операции b * s, где b является байтом, а s является коротким, разрешение перегрузки выбирает оператор * (int, int) как лучший оператор.

Ответ 5

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


Однако реальный ответ, вероятно, будет включать много контекстных решений в день в 1999-2000 годах. Я уверен, что команда, которая сделала С#, имела довольно решительные дебаты по поводу всех этих деталей языка.

  • ...
  • С# предназначен для простого, современного, универсального, объектно-ориентированного языка программирования.
  • Переносимость исходного кода очень важна, как и переносимость программиста, особенно для тех программистов, которые уже знакомы с C и С++.
  • Поддержка интернационализации очень важна.
  • ...

Цитата выше из Википедии С#

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

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

Таким образом, если вам нужно написать оболочку С# вокруг старой программы на C или С++, вам может понадобиться этот тип для хранения некоторых значений. В этом случае эти типы довольно удобны.

Это решение, которое Java не сделал. Например, если вы пишете Java-программу, которая взаимодействует с программой на С++, то в результате вы получаете значения ushort, а у Java только короткие (которые подписаны), поэтому вы не можете легко назначить друг другу и ожидать правильные значения.

Я позволю вам сделать ставку, следующий доступный тип, который может получить такое значение в Java, - int (32-бит, конечно). Вы только что удвоили свою память здесь. Что не может быть большой проблемой, вместо этого вы должны создать экземпляр массива из 100 000 элементов.

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

Но теперь я чувствую, что расхожусь из первоначального вопроса.


Итак, ваш вопрос хороший, и, надеюсь, я смог принести вам некоторые ответы, хотя, если я знаю, что, вероятно, не то, что вы хотели услышать.

Если вы хотите, вы можете даже больше узнать о спецификациях С#, приведенных ниже. Есть интересная документация, которая может быть вам интересна.

Интегральные типы

Проверенные и непроверенные операторы

Таблица нечетных числовых преобразований

Кстати, я считаю, что вы должны, вероятно, вознаградить habib-osu за это, так как он дал довольно хороший ответ на исходный вопрос с правильной ссылкой.:)

Привет