2015-10-25 5 views
3

У меня есть этот код:Почему это сравнение с нолем работает неправильно?

double A = doSomethingWonderful(); // same as doing A = 0; 
if(A == 0) 
{ 
    fprintf(stderr,"A=%llx\n", A); 
} 

и этот вывод:

A=7f35959a51c0 

, как это возможно?

Я проверил значение 7f35959a51c0 и, похоже, что-то вроде 6.91040329973658785751176861252E-310, который очень мал, но не равен нулю.

EDIT:

Хорошо, я понял, что этот способ печати шестнадцатеричного значения для дубля не работает. Мне нужно найти способ печати байтов двойника.

После комментариев я изменил мой код:

A = doSomethingWonderful();// same as doing A = 0; 
    if(A == 0) 
    { 
     char bytes[8]; 
     memcpy(bytes, &A, sizeof(double)); 
     for(int i = 0; i < 8; i++) 
     fprintf(stderr," %x", bytes[i]); 
    } 

и я получаю этот выход:

0 0 0 0 0 0 0 0 

Итак, наконец, кажется, что сравнение работает правильно, но я делаю плохую печать.

+0

Возможный дубликат [? Насколько опасно для сравнения значений с плавающей точкой] (http://stackoverflow.com/questions/10334688/how- опасные-это-сравнить-с плавающей точкой) – HadeS

+0

@ HadeS не является дубликатом, я прекрасно знаю, что это опасно. :) – HAL9000

+2

Вы имели в виду использовать% llx, а не% f для печати значения? AFAIK с использованием% llx для печати double - это неопределенное поведение. –

ответ

6

Точные значения с плавающей запятой IEEE 754 используют смещение в показателе экспоненты для полного представления как положительных, так и отрицательных показателей. Для значений двойной точности это смещение составляет 1023 [source], который, как оказалось, равен 0x3ff в шестнадцатеричном формате, который соответствует шестнадцатеричному значению A, которое вы напечатали для 1, или 0e0.


Две другие небольшие примечания:

  • При печати байт, вы можете использовать %hhx, чтобы получить его, чтобы печатать только 2 шестнадцатеричных цифр вместо знака-простирающейся до 8.
  • Вы можете использовать чтобы надежно напечатать двойное значение как 8-байтовое целое число.
double A = 0; 
if(A == 0) 
{ 
    A = 1; // Note that you were setting A to 1 here! 
    char bytes[8]; 
    memcpy(bytes, &A, sizeof(double)); 
    for(int i = 0; i < 8; i++) 
     printf(" %hhx", bytes[i]); 
} 
int isZero; 
union { 
    unsigned long i; 
    double d; 
} u; 
u.d = 0; 
isZero = (u.d == 0.0); 
printf("\n============\n"); 
printf("hex = %lx\nfloat = %f\nzero? %d\n", u.i, u.d, isZero); 

Результат:

0 0 0 0 0 0 f0 3f 
============ 
hex = 0 
float = 0.000000 
zero? 1 

Таким образом, в первой строке, мы видим, что 1.0 является 0e0 (т.е. 0).

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

+0

@ HAL9000 - Нет, вы устанавливаете 'A = 1', что означает' 0E0', что и печаталось. – DaoWen

+0

Я только что заметил сейчас :) Я редактировал свой вопрос – HAL9000

2

Когда вы передаете свой двойной номер printf(), вы передаете его как с плавающей точкой. Однако, поскольку формат "%x" является целочисленным, ваша реализация printf() попытается прочитать аргумент integer.Из-за этого несоответствия основного типа возможно, например, что код вызова помещает ваше двойное значение в регистр с плавающей запятой, а реализация printf() пытается прочитать его из целочисленного регистра. Детали зависят от вашего ABI, но, видимо, биты, которые вы видите, не являются битами, которые вы передали. С точки зрения языка, у вас есть неопределенное поведение в тот момент, когда у вас есть несоответствие типа между одним аргументом printf() и его соответствующей спецификацией формата.

Кроме того, +0.0 действительно представлен как все биты нуль, как в одиночном, так и в формате двойной точности. Однако это только положительный ноль, -0.0 представлен знаковым битом.

В последнем бите кода вы проверяете битовый шаблон 1.0, потому что перед выполнением преобразования вы переписываете значение A. Также обратите внимание, что вы получаете fffffff0 вместо f0 для седьмого байта из-за расширения знака. Для правильного вывода используйте массив из unsigned байт.

Шаблон, который вы видите декодирует как это:

00 00 00 00 00 00 f0 3f 
to big endian: 
3f f0 00 00 00 00 00 00 

decode fields: 
sign: 0 (1 bit) 
exponent: 01111111111 (11 bit), value = 1023 
    exponent = value - bias = 1023 - 1023 = 0 
mantissa: 0...0 (52 bit), value with implicit leading 1 bit: 1.0000... 

entire value: -1^0 * 2^0 * 1.0 = 1.0 
Смежные вопросы