2016-12-30 3 views
0

В этом примере поведение floor различен, и я не понимаю, почему:Почему функция пола дает разные результаты в этом случае?

printf("floor(34000000.535 * 100 + 0.5) : %lf \n", floor(34000000.535 * 100 + 0.5)); 
printf("floor(33000000.535 * 100 + 0.5) : %lf \n", floor(33000000.535 * 100 + 0.5)); 

Выход для этого кода:

floor(34000000.535 * 100 + 0.5) : 3400000053.000000 
floor(33000000.535 * 100 + 0.5) : 3300000054.000000 

Почему первый результат не равен 3400000054.0 как мы можно ожидать?

+1

Проблемы с округлением, конечно, используйте библиотеку для произвольной математики точности (например, gmp), если вам нужны точные вычисления. – Ctx

+2

Потому что вы имеете дело с бинарной плавающей точкой. – cHao

+0

@cHao Обратите внимание, что даже с _decimal_ [с плавающей запятой] (https://en.wikipedia.org/wiki/Decimal64_floating-point_format) такие проблемы могут возникать со значениями, близкими к пределам точности формата. Таковы свойства арифметики с плавающей запятой, в любой базе. Это, безусловно, более распространено с десятичным текстом и двоичным 'double'. – chux

ответ

3

double в C не представляет все возможные числа, которые могут быть выражены в тексте.

double может типично представлять около 2 разные номера. Ни 34000000.535, ни 33000000.535 находятся в этом наборе, когда double кодируется как номер binary floating point. Вместо этого используется самое близкое представимое число.

Text    34000000.535 
closest double 34000000.534999996423... 
Text    33000000.535 
closest double 33000000.535000000149... 

С double в виде двоичного числа с плавающей точкой, умножения на не-включения питания из-2, как 100,0, могут вводить дополнительные различия округления. Тем не менее, в этих случаях все еще возникают продукты, один чуть выше xxx.5, а другой ниже.

Добавление 0.5, простая мощность 2, не вызывает проблем округления, поскольку значение не является экстремальным по сравнению с 3x00000053.5.

Проведение промежуточных результатов для высокоточной точности показа показывает типичный поэтапный процесс.

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

void fma_test(double a, double b, double c) { 
    int n = DBL_DIG + 3; 
    printf("a b c  %.*e %.*e %.*e\n", n, a, n, b, n, c); 
    printf("a*b  %.*e\n", n, a*b); 
    printf("a*b+c  %.*e\n", n, a*b+c); 
    printf("a*b+c  %.*e\n", n, floor(a*b+c)); 
    puts(""); 
} 

int main(void) { 
    fma_test(34000000.535, 100, 0.5); 
    fma_test(33000000.535, 100, 0.5); 
} 

Выход

a b c  3.400000053499999642e+07 1.000000000000000000e+02 5.000000000000000000e-01 
a*b  3.400000053499999523e+09 
a*b+c  3.400000053999999523e+09 
a*b+c  3.400000053000000000e+09 

a b c  3.300000053500000015e+07 1.000000000000000000e+02 5.000000000000000000e-01 
a*b  3.300000053500000000e+09 
a*b+c  3.300000054000000000e+09 
a*b+c  3.300000054000000000e+09 

Вопрос является более сложным, то это простые ответы, как различные платформы могут 1) использовать более высокую точность математику, как long double или 2) редко используют десятичной с плавающей точкой double. Таким образом, результаты кода могут отличаться.

+0

chux, вы сделали ошибку, сказав, что есть 2^64 разных числа. На самом деле их гораздо меньше. Поскольку в этих 64 присутствует часть экспоненты, такое же представление, как в случае с 'float', которое я проанализировал в своем ответе. Компилятор может представлять число 100 как 10e2 или 100e1. Часть экспоненты не имеет 'property' о том, как компилятор должен ее установить. См. Мой ответ, который печатает, как представлены 'double' /' long double'/'float'. Поэтому, когда вы пишете x = 100, вы не можете быть уверены, ни то, что представит компилятор, ни аппаратное обеспечение с плавающей запятой – alinsoar

+0

@alinsoar Этот ответ не сказал, что есть 2^64 разных номера. Этот ответ перед вашим ненужным редактированием сказал «около 2^64 разных номеров». На самом деле существует ** не ** много меньших значений. Блок двух различных шаблонов бит - это Not-A-Numbers, который уменьшает количество шаблонов значений менее 0,1%. Ваше экспоненциальное обсуждение печати 100 как 10e2 или 100e1, все еще только одно из числа шаблонов 2^64, не имеет значения. Не редактируйте этот ответ - это редактирование и комментарий намеренно вводят в заблуждение правильное намерение. – chux

+0

вы могли бы использовать «меньше», поскольку это более корректно, чем «о». – alinsoar

0

Вопрос был уже дан here.

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

float a = 0.2 + 0.3; 
float b = 0.25 + 0.25; 

if (a == b) { 
    //might happen 
} 
if (a != b) { 
    // also might happen 
} 

только гарантируется то, что a-b является относительно небольшим.

-1

Используя code that shows the representation of floats in memory as sum of terms, мы получаем:

main() 
{ 
    float x=floor(34000000.535 * 100 + 0.5); 
    float y=floor(33000000.535 * 100 + 0.5); 
    xx(&x); 
    xx(&y); 
    yy(x); 
    yy(y); 
} 

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

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

Примечание: Я установил scale=20 в bc, что означает, что каждое промежуточное вычисление хранит 20 цифр после точки.

./a.out 
1ST NUMBER=> sign:0 exponent:1 0 0 1 1 1 1 fraction:0 1 0 0 1 0 1 0 1 0 1 0 0 1 1 1 1 1 1 0 0 0 1 0 
2ND NUMBER=> sign:0 exponent:1 0 0 1 1 1 1 fraction:0 1 0 0 0 1 0 0 1 0 1 1 0 0 1 0 0 0 0 0 0 0 0 1 
1ST NUMBER=> positive (1+1/(2) +1/(16) +1/(64) +1/(256) +1/(1024) +1/(8192) +1/(16384) +1/(32768) +1/(65536) +1/(131072) +1/(262144) +1/(4194304))*2^31 
2ND NUMBER=> positive (1+1/(2) +1/(32) +1/(256) +1/(1024) +1/(2048) +1/(16384) +1/(8388608))*2^31 
@ bc 
scale=20 
(1+1/(2) +1/(16) +1/(64) +1/(256) +1/(1024) +1/(8192) +1/(16384) +1/(32768) +1/(65536) +1/(131072) +1/(262144) +1/(4194304))*2^31 
3399999999.99999999999463129088 
(1+1/(2) +1/(32) +1/(256) +1/(1024) +1/(2048) +1/(16384) +1/(8388608))*2^31 
3299999999.99999999999731564544 
+0

Думаю, вы что-то смешаете здесь. В исходном вопросе было два разных номера (34 .... против 33 ....), что является основной причиной проблемы. У вас есть тот же номер два раза, который, конечно же, дает тот же результат при применении 'floor()' к нему. Реализация printf здесь не учитывается. – Ctx

+0

@Ctx спасибо. Я не заметил, что цифры были разные. Исправленный. – alinsoar

+0

'void main' вызывает неопределенное поведение. –

 Смежные вопросы

  • Нет связанных вопросов^_^