Простой вопрос об «исключении с плавающей запятой» в C

У меня есть следующая программа на C:

#include <stdio.h>

int main()
{
double x=0;
double y=0/x;
if (y==1)
  printf("y=1\n");
else
  printf("y=%f\n",y);
if (y!=1)
  printf("y!=1\n");
else
  printf("y=%f\n",y);

return 0;
}

Результат, который я получаю,

y=nan
y!=1

Но когда я меняю строку double x=0; в х=0; вывод становится

Floating point exception

Кто-нибудь может объяснить, почему?


person Moshe    schedule 01.08.2011    source источник
comment
Когда исчезнут все эти вопросы о точности, удвоениях, десятичных дробях и числах с плавающей запятой? download.oracle.com/docs/cd/E19957- 01/806-3568/   -  person JonH    schedule 01.08.2011
comment
Вы делите на ноль, что недопустимо.   -  person Hunter McMillen    schedule 01.08.2011


Ответы (7)


В IEE754 есть специальный битовый шаблон, который указывает NaN как результат деления с плавающей запятой на ноль ошибок.

Однако при использовании целочисленной арифметики такого представления нет, поэтому система должна выдать исключение вместо возврата NaN.

person Alnitak    schedule 01.08.2011
comment
Хорошо, я вижу, но почему это? Почему разрешено деление с плавающей запятой на ноль, но не на целые числа? - person Moshe; 02.08.2011
comment
потому что можно обнаружить NaN результат деления с плавающей запятой после того, как вы это сделали. Невозможно сделать это с целочисленным результатом, кроме как проверить делитель нуля перед выполнением деления. - person Alnitak; 02.08.2011

Вы вызываете деление 0/0 с целочисленной арифметикой (которая недействительна и создает исключение, которое вы видите). Независимо от типа y, первым оценивается 0/x.

Когда x объявляется как double, ноль также преобразуется в double, и операция выполняется с использованием арифметики с плавающей запятой.

Когда x объявляется как int, вы делите один int 0 на другой, и результат недействителен.

person Jeremy Roman    schedule 01.08.2011

Поскольку из-за IEEE 754 при выполнении недопустимой операции над числами с плавающей запятой будет выдаваться NaN. (например, 0/0, ∞×0 или sqrt(−1)).

На самом деле существует два типа NaN: сигнальные и тихие. Использование сигнального NaN в любой арифметической операции (включая числовые сравнения) вызовет «недопустимое» исключение. Использование тихого NaN просто приводит к тому, что результат тоже будет NaN.

Представление NaN, указанное стандартом, имеет некоторые неуказанные биты, которые можно использовать для кодирования типа ошибки; но для этой кодировки нет стандарта. Теоретически сигнализация NaN может использоваться системой выполнения для расширения чисел с плавающей запятой другими специальными значениями без замедления вычислений с обычными значениями. Однако такие расширения, похоже, не являются обычным явлением.

Кроме того, Википедия говорит о целочисленном делении на ноль:

Целочисленное деление на ноль обычно обрабатывается иначе, чем с плавающей запятой, поскольку для результата нет целочисленного представления. Некоторые процессоры генерируют исключение при попытке деления целого числа на ноль, тогда как другие просто продолжают и выдают неверный результат деления. Результат зависит от того, как реализовано деление, и может быть либо нулем, либо иногда максимально возможным целым числом.

person Community    schedule 01.08.2011

Проверьте минимальное и максимальное значения целочисленного типа данных. Вы увидите, что результат undefined или nan не входит в его диапазон.

И прочтите это что должен знать каждый программист о плавающей запятой.

person JonH    schedule 01.08.2011

Целочисленное деление на 0 является недопустимым и не обрабатывается. С другой стороны, плавающие значения обрабатываются в C с помощью NaN. Следующее, как всегда, будет работать.

int x=0;
double y = 0.0 / x;
person Joe    schedule 01.08.2011
comment
это очевидно, поскольку y теперь является двойным. Оп уже разобрался. - person JonH; 01.08.2011
comment
@JonH Это может быть не так очевидно, как вы думаете, для ОП, тем более что двойные значения были установлены с использованием буквальных целочисленных констант. :) - person Joe; 01.08.2011

Если вы разделите int на int, вы можете разделить на 0.

0/0 в парном разряде равно NaN.

int x=0;
double y=0/x; //0/0 as ints **after that** casted to double. You can use
double z=0.0/x; //or
double t=0/(double)x; // to avoid exception and get NaN
person RiaD    schedule 01.08.2011

Плавающая точка по своей сути моделирует вещественные числа с ограниченной точностью. Существует только конечное число битовых шаблонов, но бесконечное (непрерывное!) число действительных чисел. Конечно, он делает все возможное, возвращая самое близкое представимое вещественное число к точным входным данным. Ответы, которые слишком малы для прямого представления, вместо этого представлены нулем. Деление на ноль является ошибкой в ​​действительных числах. Однако в плавающей запятой, поскольку из этих очень маленьких ответов может возникнуть ноль, может быть полезно рассматривать x / 0,0 (для положительного x) как «положительную бесконечность» или «слишком большой для представления». Это больше не полезно для x = 0,0.

Лучшее, что мы могли бы сказать, это то, что деление нуля на ноль на самом деле означает «деление чего-то маленького, что нельзя отличить от нуля, на что-то маленькое, что нельзя отличить от нуля». Что ответить на это? Ну, нет ответа для точного случая 0/0, и нет хорошего способа трактовать его неточно. Это будет зависеть от относительных величин, поэтому процессор в основном пожимает плечами и говорит: «Я потерял всю точность — любой результат, который я дал бы вам, будет вводящим в заблуждение», возвращая Not a Number.

Напротив, при делении целого числа на ноль делитель действительно может означать только ноль. Невозможно придать этому последовательное значение, поэтому, когда ваш код запрашивает ответ, он действительно делает что-то нелегитимное.

(Во втором случае это целочисленное деление, но не в первом из-за правил продвижения C. 0 может быть взят как целочисленный литерал, и поскольку обе стороны являются целыми числами, деление является целочисленным делением. В первом случае тот факт, что x является двойным, приводит к удвоению дивиденда. Если вы замените 0 на 0.0, это будет деление с плавающей запятой, независимо от типа x.)

person wnoise    schedule 01.08.2011
comment
Ваш первый абзац крайне вводит в заблуждение. Основные арифметические операции с плавающей запятой IEEE-754 обрабатывают свои аргументы так, как если бы они были точными. Ноль на самом деле означает ровно ноль. 0.0/0.0 не создает NaN, поскольку оно может быть не равно нулю; он выдает NaN, потому что не существует уникального числа x, для которого 0 = 0*x. - person Stephen Canon; 01.08.2011
comment
@Stephen Canon: Конечно, FP обрабатывает входные данные так, как если бы они были точными, и возвращает ближайшее представимое вещественное число, когда оно существует. Но у них нет бесконечной точности. Ноль может возникнуть при вычислении, когда точный ответ не представим; и это мотивирует обращение к делению на ноль. Рассмотрим случай x/0 для положительного x: это дает +бесконечность. Это не потому, что бесконечность * 0 - это любой x, но на самом деле вместо этого нуль рассматривается как очень маленький (а бесконечность - как очень большой). Такое поведение полезно и имеет смысл, но перестает быть таковым при x = 0. - person wnoise; 01.08.2011
comment
Извините, но это неправильно. Я немного поторопился в своем комментарии, но это все же неправильно. Ноль не считается действительно маленьким. Что на самом деле происходит, так это то, что a/x математически не определена при x = 0, но функция расширяется по непрерывности, беря предел как x -> 0+. Этот предел берется не в обычных действительных числах, а в двухточечной компактификации действительных чисел, так что это + бесконечность. Если бы с 0 просто обращались так, как если бы он был очень маленьким, то деление на ноль означало бы переполнение. Однако это не так; результатом будет точная бесконечность. - person Stephen Canon; 02.08.2011
comment
@Stephen Canon: я вижу разницу, и технически ты прав. 0 трактуется точно как 0, а бесконечность точно как бесконечность. Но причины для такого выбора работы с этими расширенными вещественными числами заключаются в том, что они хорошо фиксируют ограничивающее поведение и непрерывность многих функций вещественных чисел. Да, сигналы IEEE отличаются точностью, но для большинства целей это глупо. Что касается фактических возвращаемых значений, бесконечность может быть результатом переполнения, а 0 — недостаточным значением. - person wnoise; 02.08.2011