Как printf и scanf обрабатывают форматы точности с плавающей точкой? - PullRequest
5 голосов
/ 24 сентября 2010

Рассмотрим следующий фрагмент кода:

float val1 = 214.20;
double val2 = 214.20;

printf("float : %f, %4.6f, %4.2f \n", val1, val1, val1);
printf("double: %f, %4.6f, %4.2f \n", val2, val2, val2);

Какие выходы:

float : 214.199997,  214.199997, 214.20 | <- the correct value I wanted 
double: 214.200000,  214.200000, 214.20 |

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

Как функции scanf, fscanf, printf, fprintf (и т. Д.) Обрабатывают точные форматы?

Без указания точности, printf распечатал приблизительное значение, но с %4.2f он дал правильный результат. Можете ли вы объяснить мне алгоритм, используемый этими функциями для обработки точности?

Ответы [ 3 ]

10 голосов
/ 24 сентября 2010

Дело в том, что 214.20 нельзя выразить точно с помощью двоичного представления. Несколько десятичных чисел могут. Таким образом, приближение сохраняется. Теперь, когда вы используете printf, двоичное представление превращается в десятичное представление, но оно снова не может быть точно выражено и является только приблизительным.

Как вы заметили, вы можете указать printf точность, чтобы он указывал, как округлять десятичное приближение. А если вы не указали точность, то предполагается, что она равна 6 (подробности см. На странице руководства).

Если вы используете %.40f для числа с плавающей запятой и %.40lf для двойного числа в приведенном выше примере, вы получите следующие результаты:

214.1999969482421875000000000000000000000000
214.1999999999999886313162278383970260620117

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

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

4 голосов
/ 25 сентября 2010

Поскольку вы спрашивали о scanf, вы должны заметить, что POSIX требуется printf и последующие scanf (или strtod) для восстановления исходного значения точно до тех пор, покадостаточно значимые цифры (как минимум DECIMAL_DIG, я считаю) были напечатаны.Обычный C, конечно, не предъявляет таких требований;Стандарт C в значительной степени позволяет операциям с плавающей запятой давать любой результат, который понравился разработчику, до тех пор, пока они его документируют.Однако, если вы намереваетесь хранить числа с плавающей запятой в текстовых файлах для последующего чтения, лучше использовать спецификатор C99 %a, чтобы напечатать их в шестнадцатеричном формате.Таким образом, они будут точными, и нет никакой путаницы в том, теряет ли точность процесс сериализации / десериализации.

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

1 голос
/ 24 сентября 2010

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

...