Pull to refresh

Comments 30

Подозреваю, что ответ прост: if( n == 0 ) return 1;
pow принимает и дробную степень, так что простым перемножением не обойтись. Проверка степени на нуль весьма хорошая оптимизация с одним побочным эффектом, на который вы и наткнулись. А исключений в сишке не было…
Судя по комменту 4dmonster, проблема в самом стандарте представления дробных чисел, на который все современные компиляторы опираются.
Исключений в C не было, но в том же IEEE-754 предусмотрен NaN, возвращаемый при дробном делении на ноль, вычислении квадратного корня отрицательного числа и прочих арифметических пакостях. Тут он был бы вполне уместен.
В определенном "математическом" смысле и pow(-2,1) — это тоже NaN. Но это неудобно, согласны? Хочется чтобы отрицательное число все ж таки можно было возвести, скажем, в квадрат используя функцию pow(). И в рамках этого подхода когда pow(x,y) рассматривается не как самостоятельная непрерывная действительнозначная функция, а как часть целого ряда математических формул, удобно определять pow(0,0)=1
Не понял. Почему pow(-2,1) будет равно NaN и почему это неудобно?
  1. log(-2) не определен среди вещественных чисел :)
  2. Функция pow(x,y) для общих x,y определяется по непрерывности через предельный переход, но при x<0 подобный переход в действительных числах провести невозможно

Поэтому существует мнение что есть две разные действительнозначные функции:

  1. функция pown(x,y) определенная только для целых y и любых x
  2. функция powr(x,y) определенная для любых y, но только неотрицательных x
    и есть "convenience function" pow(x,y) которая для целых y совпадает для pown, а для всех остальных с powr.

При этом pown(0,0)=1. А дальше либо Вы говорите что надо пользоваться "правильным" powr, но тогда случаи x<0 вылетают, либо таки используете "удобное" определение и тогда pow(0,0)=1. Можно еще конечно взять комплекснозначную powc(x,y), но там тоже будут свои проблемы связанные с неоднозначностью её определения.
В математике результат возведения в нулевую степень не определён, а в программировании:

The IEEE 754-2008 floating point standard is used in the design of most floating point libraries. It recommends a number of functions for computing a power:[39]
pow treats 00 as 1. This is the oldest defined version. If the power is an exact integer the result is the same as for pown, otherwise the result is as for powr (except for some exceptional cases).
pown treats 00 as 1. The power must be an exact integer. The value is defined for negative bases; e.g., pown(−3,5) is −243.
powr treats 00 as NaN (Not-a-Number – undefined). The value is also NaN for cases like powr(−3,2) where the base is less than zero. The value is defined by epower×log(base).
Именно в этом и проблема — это не очевидно. Если бы я не тестировал, то это стало бы багом (по крайней мере, в моей программе). Поэтому и предупредил тех, кому такая особенность реализации неизвестна.
Это особенность не реализации, а стандарта.
В математике результат возведения в нулевую степень не определён

40 лет назад нас учили, что определен.
Это не ошибка. Это совершенно верное, хотя и для кого-то неожиданное поведение. Есть целый ряд математических причин по которым удобно считать что pow(0,0)=1 и это поведение является частью Стандарта в ряде языков и, как выше уже отметили, в спецификации IEEE 754 на "общую" функцию pow
Озвучте, пожалуйста, эти математические причины.
Удобно считать что pow(x,y) определена специальным образом для случаев когда y является целым числом, потому что этим случаям можно дать специальную интерпретацию. Например исходя из того что (-2)^2=4, удобно доопределить что pow(-2,2)=4 хотя вообще говоря pow(x,y) не определена (точнее является комплекснозначной) для x<0.

Возьмите теперь, к примеру, ряд Фурье для экспоненты:
exp(x) = \sum_{n=0...\inf} x^n / n!

Или возьмите правило дифференцирования d/dx (x^p) = p x^{p-1}

Попробуйте посчитать в этих формулах exp(0) или d/dx(x) и Вы убедитесь, что 0^0 удобно считать равным 1, дабы не обрабатывать эти ситуации как специальные.
А есть куча математических ситуаций, в которых результат другой. Так что оправдание слабоватенькое.

pow(x,y) не определена (точнее является комплекснозначной) для x<0.

Точнее определена только для целых y. Комплексные числа вообще ни при чём.
Обычно разумным решением является считать функцию максимально непрерывной. pow(n*x,x) стремится 1 при x стремится к нулю сверху. Так что выбор удачен.
а pow(0, x) стремится к нулю.
а pow(pow(0.5, 1/x), x) стремится к 0.5
Никакой непрерывностью тут и не пахнет.
If a domain error occurs, an implementation-defined value is returned (NaN where supported)
Вообще, <cmath> не кидает эксепшенов. У вас целые числа, значит вернет, как компилятор решит, на что вы жалуетесь? Что документацию не читаете?
В Erlang тот же результат:
1> math:pow(0,0).
1.0

В любом языке программирования под x86 по очевидным причинам одинаковое поведение этой функции. Собственно, потому что x86 реализует его по стандарту IEEE 754 (точнее, стандарт был основан на i8087 в свое время, не столь важно). Исключения — языки, реализующие операции с плавающей точкой самостоятельно (всякие длинные арифметики и прочее).
Почему-то в памяти возникает школьные знания из которых следует, что возведение любого числа в нулевую степень равно единице. В том числе и нуля. Но это так давно было, могу и ошибаться.
UFO just landed and posted this here
Это вы еще не сталкивались со случаями неопределенного поведения в C++!

В С++, если вы напишете следующий код:

int8_t i = 127;
i++;

То поведение вашей программы будет неопределенным. Может быть, полетит исключение. А может быть и нет. Что будет в переменной i после выполнения инкремента — неизвестно. Любые инструкции языка, стоящие после i++, могут выполняться неожиданным и причудливым образом. В общем, гуглите "undefined behavior в C++" — вам откроется новая Вселенная.
Любые инструкции языка, стоящие после i++, могут выполняться неожиданным и причудливым образом
Излишне оптимистично. Те, которые стоят раньше, тоже могут, если бы их нормальное выполнение привело бы к переполнению.
Даже в математике возведение нуля в нулевую степень не определено. Читаем Википедию:

Выражение 0^0 (ноль в нулевой степени) принято считать лишённым смысла[8][9][10], то есть неопределённым.
Связано это с тем, что функция двух переменных x^y в точке \{0,0\} имеет неустранимый разрыв. (В самом деле, вдоль положительного направления оси X, где y=0, она равна единице, а вдоль положительного направления оси Y, где x=0, она равна нулю.)

А в языках C и C++ не принято проверять все подобные случаи. Иначе программа будет тормозить от таких проверок. Вместо этого обычно считается, что в результате запрещенной операции поведение программы оказывается неопределенным: она может делать все, что угодно, возвращать какие угодно числа, сбоить, бросать исключения и даже (стандартом это не запрещено, а некоторыми адептами — поощряется) отформатировать ваш диск. Так что будьте аккуратны.
Это не пример неопределённого поведения в C++. Возвращение 1 здесь абсолютно гарантировано стандартом.
Да, в самом деле. Удивительно даже. Сколько более безобидных вещей в C/C++ приводит к неопределенному поведению, а этот вопиющий случай попытки вычисления математического выражения, лишенного смысла — нет.

Тем не менее, возвращаемое значение является по стандарту implementation-defined (а не обязательно 1). Также возможна генерация математического исключения, если они разрешены.

Вот из стандарта C99:

The pow functions compute x raised to the power y. A domain error occurs if x is finite and negative and y is finite and not an integer value. A range error may occur. A domain error may occur if x is zero and y is zero. A domain error or range error may occur if x is zero and y is less than zero.

On a domain error, the function returns an implementation-defined value; if the integer expression math_errhandling & MATH_ERRNO is nonzero, the integer expression errno acquires the value EDOM; if the integer expression math_errhandling & MATH_ERREXCEPT is nonzero, the ‘‘invalid’’ floating-point exception is raised.

Так что некоторая неопределенность остается. Ну и вообще я бы не рекомендовал писать программы, поведение которых зависит от обработки компилятором или интерпретатором подобных ситуаций — вычисление неопределенных математических выражений.
Я вас только об одном прошу — постарайтесь воздержаться от использования исключений в C++, хотя бы для проверки таких простых вещей. Оно может работать на порядок медленнее и вносить ненужные сложности в отладку. Многие крупные проекты — как Chromium или Qt, написаны вообще без использования исключений, и все работает отлично. Если производительность не критична, то лучше писать на более высокоуровневых языках, где исключения дешевле.
Вот ведь подсуропили нам ребятки — почти во всех языках выдается единица, вместо ожидаемой ошибки, как, например, при делении на ноль.

Вообще-то при делении на ноль в числах с плавающей точкой никакой ошибки нет: возвращается бесконечность с соответствующим знаком (или NaN, если числитель тоже ноль или NaN).
Что интересно, на интерпретаторе бейсика ZX Spectrum 48K, ноль в степени ноль — будет тоже единица. Однако деление на ноль приводит к ошибке "6 Number too big"
Sign up to leave a comment.

Articles