Почему задана спецификация С# (реализация int.MinValue/-1)?
Выражение int.Minvalue / -1
приводит к определенному поведению в соответствии со спецификацией С#:
7.8.2 Оператор дивизиона
Если левый операнд является наименьшим представимым значением int или long, а правый операнд равен -1, происходит переполнение. В проверенном контексте это вызывает System.ArithmeticException(или его подкласс). В неконтролируемый контекст, определяется в отношении того, является ли Исключение System.ArithmeticException(или его подкласс) или переполнение не сообщается с результирующим значением, равным левый операнд.
Программа тестирования:
var x = int.MinValue;
var y = -1;
Console.WriteLine(unchecked(x / y));
Это выдает OverflowException
на .NET 4.5 32bit, но это не обязательно.
Почему спецификация оставляет решение, определяемое результатом? Здесь дело против этого:
- Инструкция x86
idiv
всегда приводит к исключению в этом случае.
- На других платформах может потребоваться проверка времени выполнения, чтобы подражать этому. Но стоимость этой проверки была бы низкой по сравнению со стоимостью подразделения. Целочисленное деление чрезвычайно дорого (15-30 циклов).
- Это открывает риски совместимости ( "write once run никуда" ).
- Презентация разработчика.
Также интересен тот факт, что если x / y
является константой компилятора, мы действительно получаем unchecked(int.MinValue / -1) == int.MinValue
:
Console.WriteLine(unchecked(int.MinValue / -1)); //-2147483648
Это означает, что x / y
может иметь различное поведение в зависимости от используемой синтаксической формы (и не только в зависимости от значений x
и y
). Это допускается спецификацией, но это кажется неразумным выбором. Почему С# был создан так?
A аналогичный вопрос указывает, где в спецификации это точное поведение предписано, но оно (недостаточно) не отвечает, почему язык был разработан таким образом. Альтернативные варианты не обсуждаются.
Ответы
Ответ 1
Это побочный эффект более крупного брата С# Language Specification, Ecma-335, спецификации инфраструктуры общего языка. Раздел III, глава 3.31 описывает, что делает код операции DIV. Специфика того, что спецификация С# очень часто приходится откладывать, довольно неизбежно. Он указывает, что он может бросать, но не требует этого.
Иначе реалистичная оценка того, что делают настоящие процессоры. И тот, который все используют, является странным. Процессоры Intel чрезмерно причудливы по поводу поведения переполнения, они были разработаны еще в 1970-х годах с предположением, что каждый будет использовать инструкцию INTO. Никто не делает, рассказ на другой день. Однако он не игнорирует переполнение на IDIV и поднимает ловушку #DE, не может игнорировать этот громкий удар.
Довольно сложно написать спецификацию языка поверх спецификации woolly runtime поверх непоследовательного поведения процессора. Мало того, что команда С# могла бы с этим справиться, но продвигать неточный язык. Они уже вышли за рамки спецификации, задокументировав OverflowException вместо ArithmeticException. Очень непослушный. У них был быстрый взгляд.
Взгляд, который показал практику. Очень маловероятно, что проблема будет проблемой, дрожание решает, следует ли встраивать. И не-встроенная версия бросает, ожидание заключается в том, что и встроенная версия. Никто еще не был разочарован.
Ответ 2
Основная цель дизайна С# - "Закон минимального сюрприза". Согласно этому руководству компилятор не должен пытаться угадать намерение программиста, а скорее должен сообщить программисту, что для правильного указания намерения необходимо дополнительное руководство. Это относится к интересующему случаю, потому что в рамках ограничений арифметики с двумя дополнениями результат приводит к очень неожиданному результату: Int32.MinValue/-1 оценивается в Int32.MinValue. Произошло переполнение, и для правильного представления правильного значения Int32.MaxValue + 1 потребуется не доступный 33-й бит, равный 0,
Как и ожидалось, и в вашей цитате отмечено, что в проверенном контексте возникает исключение, чтобы предупредить программиста о том, что не удалось правильно указать намерение. В неконтролируемом контексте реализации разрешено либо вести себя как в проверенном контексте, либо разрешать переполнение и возвращать неожиданный результат. Существуют определенные контексты, такие как бит-twiddling, в которых удобно работать с подписанным int, но там, где реально происходит ожидание и желание переполнения. Проверяя примечания по реализации, программист может определить, действительно ли это поведение как ожидалось.