2013-06-12 1 views
2

ОК, я знаю, что было много вопросов о функции pow и отбрасывании ее результата в int, но я не мог найти ответ на этот немного конкретный вопрос.Различия в округленном результате при вызове pow()

ОК, это код C:

#include <stdio.h> 
#include <stdlib.h> 
#include <math.h> 

int main() 
{ 
    int i = 5; 
    int j = 2; 

    double d1 = pow(i,j); 
    double d2 = pow(5,2); 
    int i1 = (int)d1; 
    int i2 = (int)d2; 
    int i3 = (int)pow(i,j); 
    int i4 = (int)pow(5,2); 

    printf("%d %d %d %d",i1,i2,i3,i4); 

    return 0; 
} 

И это выход: "25 25 24 25". Обратите внимание, что только в третьем случае, когда аргументы pow не являются литералами, мы имеем неправильный результат, вероятно, вызванный ошибками округления. То же самое происходит без явного литья. Может ли кто-нибудь объяснить, что происходит в этих четырех случаях?

Im using CodeBlocks в Windows 7 и компилятор MinGW gcc, который пришел с ним.

+0

Что именно вы хотите объяснить точно? И 24, и 25 являются правильными ответами. Реализация делает то, что наиболее эффективно. –

+0

Мой вывод '25 25 25 25'. Это странно. – user2448027

+0

@ user2448027 Попробуйте отключить всю оптимизацию. – johnchen902

ответ

3

Результат операции pow - 25.0000 плюс или минус некоторая ошибка округления. Если ошибка округления положительна или равна нулю, результат преобразования будет равен целому числу. Если ошибка округления отрицательная, результат будет 24. Оба ответа верны.

Что, скорее всего, происходит внутри, так это то, что в одном случае используется более высокое значение 80-битного значения FPU, а в другом случае результат записывается из FPU в память (в виде 64- бит дважды), а затем снова прочитать (преобразование его в немного отличающееся 80-битное значение). Это может сделать микроскопическую разницу в конечном результате, и это все, что требуется, чтобы изменить 25.0000000001 на 24.999999997

Еще одна возможность заключается в том, что ваш компилятор распознает константы, переданные pow, и сам вычисляет, подставляя результат для позвоните по номеру pow. Ваш компилятор может использовать внутреннюю математическую библиотеку с произвольной точностью или просто использовать другую.

+0

Округление никогда не изменяет значение выше 25 до значения ниже 25. Даже если кто-то не знает правил или механизмов арифметики с плавающей запятой, термин «круглый» является большой подсказкой: 24.999999997 не является более чем 25.0000000001. Арифметика с плавающей точкой не является генератором случайных чисел. –

+0

@EricPostpischil Согласен. Это не то, что я говорю. Я говорю, что округление может превратить значение, которое было бы 25, если бы не округление до 24.9999997 или 25.000001. а арифметика с плавающей запятой может быть генератором случайных чисел в том смысле, что повторение точно таких же операций не гарантирует получение точных результатов. –

+0

Повторение одних и тех же операций дает точные результаты. Это случай, когда операции ** не повторялись **, потому что реализация C не позволяет однозначно связывать исходный код с операциями с плавающей запятой. Тем не менее, каждая операция дает одинаковые результаты каждый раз, когда она выполняется. Только тот же исходный код (не совсем тот же) дает разные результаты. Это дефект реализации C, а не арифметики с плавающей запятой. –

0

Я довольно уверен, что это можно объяснить «промежуточное округлением», и тот факт, что pow не просто обхват вокруг j раз умножая на i, но вычислений с использованием exp(log(i)*j) как вычисление с плавающей запятой. Промежуточное округление может хорошо конвертировать 24.999999999996 в 25.000000000 - даже произвольное хранение и перезагрузка значения могут вызывать различия в такого рода поведении, поэтому в зависимости от того, как генерируется код, это может иметь значение для точного результата.

И, конечно же, в некоторых случаях компилятор может даже «знать», что фактически достигает pow, и заменяет вычисления постоянным результатом.

0

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

Я настоятельно рекомендую прочитать эту статью (несмотря на то, что это долго читать): http://hal.archives-ouvertes.fr/docs/00/28/14/29/PDF/floating-point-article.pdf

Перейти к разделу 3 для практических примеров, но не пренебрегайте предыдущие главы!

1

Это обусловлено сочетанием двух задач:

  • Реализация pow вы используете не высокого качества. Арифметика с плавающей запятой обязательно является приблизительной во многих случаях, но хорошие реализации заботятся о том, чтобы простые случаи, такие как pow(5, 2), возвращали точные результаты.Используемый вами pow возвращает результат, который меньше 25 на сумму больше 0, но меньше или равен 2 -49. Например, он может возвращать 25-2 -50.
  • Реализация C, которую вы используете, иногда использует 64-битный формат с плавающей запятой и иногда использует 80-битный формат с плавающей запятой. Пока номер сохраняется в 80-битном формате, он сохраняет полное значение, возвращаемое pow. Если вы преобразуете это значение в целое число, оно выдает 24, потому что значение меньше 25 и преобразование в целые усечения; он не круглый. Когда число преобразуется в 64-битный формат, оно округляется. Преобразование между раундами форматов с плавающей точкой, поэтому результат округляется до ближайшего представляемого значения 25. После этого преобразование в целое число создает 25.

Компилятор может переключать форматы, когда это «удобно» в некотором смысле , Например, существует ограниченное количество регистров с 80-битным форматом. Когда они заполнены, компилятор может преобразовать некоторые значения в 64-битный формат и сохранить их в памяти. Компилятор также может переупорядочить выражения или выполнить их части во время компиляции, а не во время выполнения, и это может повлиять на выполняемую арифметику и используемый формат.

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

+0

Я согласен со всем, кроме вашего последнего абзаца. На самом деле хуже, когда реализация не смешивает форматы с плавающей запятой. Тогда ваш код будет работать, и ошибки будут пропущены незаметно. Именно благодаря его выполнению он узнал, что он * не может * полагаться на реализацию, чтобы быть предсказуемым на той арене, на которой на самом деле нельзя полагаться на предсказуемость. Когда вы делаете что-то действительно не так, тем быстрее и сложнее выполнение не удается из-за этого, тем лучше. –

+0

@DavidSchwartz: Это вредная позиция, которая защищает программистов от компиляторов, которые неаккуратные. Когда поведение компилятора затрудняет разработку хорошего программного обеспечения, его следует рассматривать как ошибку в компиляторе. (Соответствие стандарту C означает ** не ** конечную точку для компилятора. Они могут и должны выходить за ее пределы, а стандарт должен быть улучшен в своих спецификациях с плавающей запятой.) –

+0

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