Точность printf со спецификатором "% g"

Может ли кто-нибудь объяснить мне, как [.precision] в printf работает со спецификатором "% g"? Я довольно смущен следующим выходом:

double value = 3122.55;
printf("%.16g\n", value); //output: 3122.55
printf("%.17g\n", value); //output: 3122.5500000000002

Я узнал, что %g использует кратчайшее представление.

Но следующие результаты все еще путают меня

printf("%.16e\n", value); //output: 3.1225500000000002e+03
printf("%.16f\n", value); //output: 3122.5500000000001819
printf("%.17e\n", value); //output: 3.12255000000000018e+03
printf("%.17f\n", value); //output: 3122.55000000000018190

Мой вопрос: почему %.16g дает точное число, а %.17g не может?

Кажется, что 16 значащих цифр могут быть точными. Может ли кто-нибудь сказать мне причину?

Ответ 1

%g использует самое короткое представление.

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

Когда вы указываете %.16g, вы говорите, что хотите получить кратчайшее представление числа, содержащего не более 16 значащих цифр.

Если самое короткое представление имеет более 16 цифр, printf сократит строку чисел, обрезав 2 цифры в самом конце, оставив вам 3122.550000000000, что на самом деле составляет 3122.55 в самой короткой форме, объясняя полученный результат.

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

Для дальнейшего примера, когда вы используете %.17g а 17 е десятичное число содержит значение, отличное от 0 (в частности, 2), вы 3122.5500000000002 полный номер 3122.5500000000002.

Мой вопрос: почему %.16g дает точное число, а %.17g не может?

Это на самом деле %.17g который дает вам точный результат, в то время как %.16g дает вам только округленное приближение с ошибкой (по сравнению со значением в памяти).

Если вам нужна более фиксированная точность, используйте %f или %F

Ответ 2

Десятичное значение 3122.55 не может быть точно представлено в двоичной форме с плавающей запятой. Когда ты пишешь

double value = 3122.55;

в итоге вы получите максимально возможное значение, которое может быть точно представлено. Как это происходит, это значение в точности 3122.5500000000001818989403545856475830078125.

Это значение для 16 значащих цифр составляет 3122.550000000000. Для 17 значащих цифр это 3122.5500000000002. И вот те представления, которые дают вам %.16g и %.17g.

Обратите внимание, что ближайшее double представление десятичного числа гарантированно будет точно по крайней мере до 15 десятичных значащих цифр. Вот почему вам нужно печатать до 16 или 17 цифр, чтобы начать видеть эти очевидные неточности в вашем выводе в этом случае - при любом меньшем числе значащих цифр, double представление гарантированно будет соответствовать исходному введенному вами десятичному числу.

Последнее замечание: вы говорите, что

Я узнал, что %g использует самое короткое представление.

Хотя это популярное изложение того, как ведет себя %g, оно также неверно. См. Что именно означает% g printf? где я подробно остановлюсь на этом и покажу пример %g с использованием научной записи, хотя это на 4 символа длиннее, чем без использования научной записи.

Ответ 3

Десятичное представление 3122.55 не может быть точно представлено двоичным представлением с плавающей запятой.

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

Я узнал, что% g использует самое короткое представление.

Правило таково:

Где P - точность (или 6, если точность не указана, или 1, если точность равна нулю), а X - десятичный показатель, необходимый для нотации стиля E/e, тогда:

  • если P> X ≥ −4, преобразование выполняется со стилем f или F и точностью P - 1 - X.
  • в противном случае выполняется преобразование со стилем e или E и точностью P - 1.

Изменение точности для %g приводит к другому выводу:

printf("%.16g\n", value); //output: 3122.55
printf("%.16e\n", value); //output: 3.1225500000000002e+03
printf("%.16f\n", value); //output: 3122.5500000000001819

несмотря на одинаковую точность в спецификаторе формата.