Подтвердить что ты не робот

Почему добавление 0 в конец плавающего литерала меняет способ его округления (возможная ошибка GCC)?

Я обнаружил на своей x86 VM (32 бит) следующую программу:

#include <stdio.h>
void foo (long double x) {
    int y = x;
    printf("(int)%Lf = %d\n", x, y);
}
int main () {
    foo(.9999999999999999999728949456878623891498136799780L);
    foo(.999999999999999999972894945687862389149813679978L);
    return 0;
}

Производит следующий вывод:

(int)1.000000 = 1
(int)1.000000 = 0

Идеал также производит такое поведение.

Что делает компилятор, чтобы это произошло?

Я нашел эту константу, поскольку я отслеживал, почему следующая программа не произвела 0 как я ожидал (используя 19 9 вызвал 0 я ожидал):

int main () {
    long double x = .99999999999999999999L; /* 20 9 */
    int y = x;
    printf("%d\n", y);
    return 0;
}

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

4b9b3361

Ответ 1

Ваша проблема в том, что long double на вашей платформе недостаточно точности для хранения точного значения 0.99999999999999999999. Это означает, что значение этого параметра должно быть преобразовано в представляемое значение (это преобразование происходит во время перевода вашей программы, а не во время выполнения).

Это преобразование может генерировать либо ближайшее представимое значение, либо следующее большее или меньшее представляемое значение. Выбор определяется реализацией, поэтому ваша реализация должна документировать, что она использует. Кажется, что ваша реализация использует 80bit long double в стиле x87 и округляется до ближайшего значения, в результате чего значение в 1.0 сохраняется в x.


С принятым форматом для long double (с битами 64 мантиссы) наивысшее представимое число меньше 1,0 в шестнадцатеричном формате:

0x0.ffffffffffffffff

Число точно на полпути между этим значением и следующим высшим представимым числом (1.0):

0x0.ffffffffffffffff8

Ваша очень длинная константа 0.9999999999999999999728949456878623891498136799780 равна:

0x0.ffffffffffffffff7fffffffffffffffffffffffa1eb2f0b64cf31c113a8ec...

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

Ответ 2

Компилятор использует двоичные числа. Большинство компиляторов делают то же самое.

Согласно wolframalpha, двоичное представление

0.99999999999999999999

выглядит так:

0.11111111111111111111111111111111111111111111111111111111111111111101000011000110101111011110011011011011011110111011100101000101010111011100001011010001001110001101011001010000110000101001111011111001111110000101010111111110100110000010001001101011001101010110110010010101101111101001110001100111101100000000100110110001100110000011000100100011000011110100001000000100001000101000111011010111111101011010010000010110011111110100100110001011001110100011100001111101011110101001000000111110010000101101001001010110010011001110111111100111101111100000111010001101101011000100110001010010001000100010110000101110100101010101001010100010001001100111111111001001101100000000010010001011110100101011101001001101001111001001000101011101001100111101110111111001101110100111000001111101101101101101110100100111101000000000111101101101001000111101100010101110011101110001110010110110111101000011110110100011000110101100011111111110111000010010001111000000000101100101000100101110100001001101000010110101000100011100000110010001110101...

Это 932 бита и что STILL недостаточно, чтобы точно представлять ваш номер (см. точки в конце).

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

Поскольку номер не может быть сохранен точно, он будет округлен или опущен. С 20 9 секунд он заканчивается округленным, а с 19 9 секунд он заканчивается округленным.

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

Ответ 3

Двойные значения, когда для представления значения недостаточно точности, округляются вверх или вниз до ближайшего. В вашей реализации оно округляется до 1.

Ответ 4

Здесь есть два преобразования. Во-первых, и в некотором роде наиболее важным является преобразование буквального .99999999999999999999L в long double. Как говорили другие, это преобразование округляется до ближайшего представимого значения, которое кажется 1.0L. Второе преобразование - это длинное двойное значение, полученное в результате первого преобразования в целочисленное значение. Это преобразование округляется к 0, поэтому быстрое исследование предполагает, что значение y должно быть 0. Но поскольку преобразование first составило 1, а не значение, немного меньшее 1, это преобразование также производит 1.