Печать поплавка, сохраняя точность
Я пишу программу, которая печатает литералы с плавающей точкой для использования внутри другой программы.
Сколько цифр мне нужно напечатать, чтобы сохранить точность исходного числа?
Так как поплавок имеет 24 * (log(2) / log(10)) = 7.2247199
Точность десятичной цифры, моя первоначальная мысль заключалась в том, что печати 8 цифр должно быть достаточно. Но если мне не повезет, те 0.2247199
распределяются слева и справа от 7 значащих цифр, поэтому я, вероятно, должен вывести 9 десятичных цифр.
Является ли мой анализ правильным? Достаточно ли 9 десятичных цифр для всех случаев? подобно printf("%.9g", x);
?
Существует ли стандартная функция, которая преобразует число с плавающей точкой в строку с минимальным количеством десятичных цифр, требуемым для этого значения, в случаях, когда достаточно 7 или 8, чтобы я не печатал ненужные цифры?
Примечание: я не могу использовать шестнадцатеричные литералы с плавающей точкой, потому что стандарт C++ их не поддерживает.
8 ответов
Чтобы гарантировать, что двоичное-> десятичное-> двоичное сообщение восстанавливает исходное двоичное значение, IEEE 754 требует
The original binary value will be preserved by converting to decimal and back again using:[10]
5 decimal digits for binary16
9 decimal digits for binary32
17 decimal digits for binary64
36 decimal digits for binary128
For other binary formats the required number of decimal digits is
1 + ceiling(p*log10(2))
where p is the number of significant bits in the binary format, e.g. 24 bits for binary32.
В C для этих преобразований можно использовать функции snprintf() и strtof/strtod/strtold().
Конечно, в некоторых случаях могут быть полезны даже больше цифр (нет, они не всегда являются "шумом", в зависимости от реализации подпрограмм десятичного преобразования, таких как snprintf()). Рассмотрим, например, печать двоичных дробей.
24 * (log(2) / log(10)) = 7, 2247199
Это довольно типичный представитель проблемы. Не имеет никакого смысла выражать количество значащих цифр с точностью до 0,0000001. Вы конвертируете числа в текст на благо человека, а не машины. Человеку наплевать, и он бы предпочел, если бы ты написал
24 * (log(2) / log(10)) = 7
Попытка отобразить 8 значащих цифр просто генерирует случайные цифры шума. С ненулевыми коэффициентами это 7 уже слишком много, потому что в вычислениях накапливается ошибка с плавающей запятой. Прежде всего, напечатайте числа, используя разумную единицу измерения. Люди интересуются миллиметрами, граммами, фунтами, дюймами и так далее. Ни один архитектор не позаботится о размере окна, выраженном более точно, чем 1 мм. Ни один завод по производству окон не обещает такое же точное окно.
И последнее, но не менее важное: нельзя игнорировать точность чисел, которые вы вводите в свою программу. Измерение скорости порожней европейской ласточки до 7 цифр невозможно. Это примерно 11 метров в секунду, 2 цифры в лучшем случае. Таким образом, выполнение вычислений на этой скорости и печать результата с более значительными цифрами приводит к бессмысленным результатам, которые обещают точность, которой нет.
Если у вас есть библиотека C, соответствующая C99 (и если у ваших типов с плавающей запятой есть основание, равное степени 2:), printf
форматировать персонажа %a
может печатать значения с плавающей запятой без недостатка точности в шестнадцатеричной форме, а утилиты как scanf
а также strod
смогу их прочитать.
Если программа предназначена для чтения на компьютере, я бы сделал простой трюк использования char*
сглаживание.
- псевдоним
float*
вchar*
- скопировать в
unsigned
(или любой другой тип без знака достаточно большой) черезchar*
наложения спектров - распечатать
unsigned
значение
Декодирование просто полностью изменяет процесс (и на большинстве платформ прямой reinterpret_cast
может быть использован).
Если вы прочтете эти статьи (см. Ниже), то обнаружите, что существует некоторый алгоритм, который печатает минимальное количество десятичных цифр, так что число можно интерпретировать без изменений (например, с помощью scanf).
Поскольку таких чисел может быть несколько, алгоритм также выбирает ближайшую десятичную дробь к исходной двоичной дроби (я назвал значение с плавающей запятой).
Жаль, что в Си нет такой стандартной библиотеки.
Преобразование с плавающей точкой в десятичную, используемое в Java, гарантированно даст наименьшее количество десятичных цифр, превышающее десятичную точку, необходимое для различения числа от его соседей (более или менее).
Вы можете скопировать алгоритм отсюда: http://www.docjar.com/html/api/sun/misc/FloatingDecimal.java.html Обратите внимание на FloatingDecimal(float)
конструктор и тому toJavaFormatString()
метод.
Ты можешь использовать sprintf
, Я не уверен, что это точно отвечает на ваш вопрос, но в любом случае, вот пример кода
#include <stdio.h>
int main( void )
{
float d_n = 123.45;
char s_cp[13] = { '\0' };
char s_cnp[4] = { '\0' };
/*
* with sprintf you need to make sure there's enough space
* declared in the array
*/
sprintf( s_cp, "%.2f", d_n );
printf( "%s\n", s_cp );
/*
* snprinft allows to control how much is read into array.
* it might have portable issues if you are not using C99
*/
snprintf( s_cnp, sizeof s_cnp - 1 , "%f", d_n );
printf( "%s\n", s_cnp );
getchar();
return 0;
}
/* output :
* 123.45
* 123
*/
С чем-то вроде
def f(a):
b=0
while a != int(a): a*=2; b+=1
return a, b
(это Python), вы должны быть в состоянии получить мантиссу и экспоненту без потерь.
В C это, вероятно, будет
struct float_decomp {
float mantissa;
int exponent;
}
struct float_decomp decomp(float x)
{
struct float_decomp ret = { .mantissa = x, .exponent = 0};
while x != floor(x) {
ret.mantissa *= 2;
ret.exponent += 1;
}
return ret;
}
Но имейте в виду, что еще не все значения могут быть представлены таким образом, это всего лишь быстрый способ, который должен дать идею, но, вероятно, нуждается в улучшении.