добавление двойного значения к 64-битному значению без знака дает странные результаты - PullRequest
3 голосов
/ 07 марта 2012
int main(int argc, char *argv[])
{
    uint64_t length = 0x4f56aa5d4b2d8a80;
    uint64_t new_length = 0;

    new_length = length + 119.000000;

    printf("new length  0x%"PRIx64"\n",new_length);

    new_length = length + 238.000000;

    printf("new length  0x%"PRIx64"\n",new_length);

    return 0;
}

С кодом выше.Я добавляю два разных двойных значения в 64-разрядное целое число без знака. Я получаю одинаковый результат в обоих случаях. Вывод программы показан ниже

$./a.out
new length  0x4f56aa5d4b2d8c00
new length  0x4f56aa5d4b2d8c00

Я ожидаю двух разных результатовно это не так. Я также пытался преобразовать значение типа uint64_t в double, как в

new_length = (double)length + 119.000000;

Но это тоже не помогает. Любая идея о том, что можетбыть проблемой?

Ответы [ 2 ]

7 голосов
/ 07 марта 2012

Поскольку вы добавляете операнд с плавающей точкой, оба операнда неявно приводятся к double, а добавление выполняется с использованием арифметики с плавающей точкой.

Однако double не обладает достаточной точностью дляточно удерживайте любое из следующих значений:

0x4f56aa5d4b2d8a80 + 119.0  (requires 63 bits of precision)

0100111101010110101010100101110101001011001011011000101011110111
 <-------------------63 bits of precision---------------------->


0x4f56aa5d4b2d8a80 + 238.0  (requires 62 bits of precision)

0100111101010110101010100101110101001011001011011000101101101110
 <-------------------62 bits of precision--------------------->

Стандартная двойная точность IEEE имеет только 53 бит точности .

В результате получается, чтооба они округляются до одного и того же конечного значения:

0x4f56aa5d4b2d8c00  (53 bits of precision)

0100111101010110101010100101110101001011001011011000110000000000
 <-----------------53 bits of precision-------------->

Если вы хотите избежать этого округления, вам следует вообще избегать арифметики с плавающей точкой, приводя операнды к целому числу.(или просто вместо 119 и 238)

3 голосов
/ 07 марта 2012

Арифметика с плавающей точкой не является точной.По мере увеличения числа точность младших цифр уменьшается.

0x4f56aa5d4b2d8a80 - очень большое число.

Что происходит в

new_length = length + 119.000000;

Это length + 119.000000получает удвоение, чтобы сделать дополнение.Этот двойной округляется, довольно резко, потому что он такой большой.Затем он снова приводится к целочисленному типу uint64_t, когда ему присваивается значение new_length.

Когда вы звоните

new_length = length + 238.000000; 

Бывает, что округленный результат заканчивается тем же.

Что вы действительно хотите сделать, это

new_length = length + (uint64_t)238.0; 

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

...