Определение вывода (печати) с плавающей запятой с% f в C / C ++ - PullRequest
1 голос
/ 05 декабря 2009

I have gone through earlier discussions on floating point numbers in SO but that didn't clarified my problem,I knew this floating point issues may be common in every forum but my question in not concerned about Floating point arithmetic or Comparison.I am rather inquisitive about its representation and output with %f.

Вопрос прост: "Как определить точный результат:

float = <Some_Value>f;     
printf("%f \n",<Float_Variable>);

Позвольте нам рассмотреть этот фрагмент кода:

float f = 43.2f,
f1 = 23.7f,
f2 = 58.89f,
f3 = 0.7f;

printf("f1 = %f\n",f);
printf("f2 = %f\n",f1);
printf("f3 = %f\n",f2);
printf("f4 = %f\n",f3);

Выход:

f1 = 43.200001
f2 = 23.700001
f3 = 58.889999
f4 = 0.700000

Мне известно, что% f (предназначено для двойного) имеет точность по умолчанию, равную 6, также я знаю, что проблема (в данном случае) может быть исправлена ​​с помощью двойного, но мне любопытно узнать о выводе f2 = 23.700001 и f3 = 58.889999 в поплавке.

EDIT: I am aware that floating point number cannot be represented precisely, but what is the rule of for obtaining the closest representable value ?

Спасибо

Ответы [ 8 ]

6 голосов
/ 05 декабря 2009

Предполагая, что вы говорите о IEEE 754 с плавающей запятой, который имеет точность 24 двоичных разряда: представляйте число в двоичном (точно) и округлите число до 24-й самой значимой цифры. Результатом будет ближайшая с плавающей точкой.

Например, 23.7 в двоичном виде -

10111.1011001100110011001100110011...

После округления вы получите

10111.1011001100110011010

Который в десятичном виде

23.700000762939453125

После округления до шестого знака после запятой вы получите

23.700001

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

4 голосов
/ 05 декабря 2009

Что должен знать каждый компьютерщик об арифметике с плавающей точкой

Вас также может заинтересовать вопрос о других людях относительно этого на SO.

Пожалуйста, взгляните тоже.

https://stackoverflow.com/search?q=floating+point

3 голосов
/ 05 декабря 2009

32-битное значение с плавающей запятой (как в данном случае) представляется как 1 бит знака, 8 битов экспоненты и 23 бита дробной части мантиссы.

Во-первых, забудьте о знаке того, что вы вставили. Затем все остальное, что вы положили, будет сохранено как часть формата

(1 + x / 8,388,608) * 2 ^ (у-127) (обратите внимание, что 8,388,608 равно 2 ^ 23). Где х - дробная мантисса, а у - показатель степени. Хотите верьте, хотите нет, в этой форме есть только одно представление для каждого введенного вами значения. Сохраненное значение будет самым близким значением к желаемому числу, если ваше значение не может быть представлено точно, это означает, что вы получите дополнительно .0001 или что-то еще.

Итак, если вы хотите выяснить значение, которое будет фактически сохранено, просто выясните, во что оно превратится.

Итак, второе, что нужно сделать (после выброса знака), - найти наибольшую степень 2, которая меньше по величине, чем число, которое вы представляете. Итак, давайте возьмем 43,2.

Наибольшая степень двух меньших, чем это, составляет 32. Так что это «1» слева, так как это 32, а не 1, это означает, что значение 2 ^ справа должно быть 2 ^ 5 (32) , что означает, что у 132. Так что теперь вычтите из 32, это сделано для. Осталось 11.2. Теперь нам нужно представить 11,2 как дробь более 8,338,608 раз 2 ^ 5.

So

11,2 приблизительно равно x * 32 / 8,336,608 или x / 262,144. Значение, которое вы получите для x, равно 2,938,013 / 262,144. Реальный числитель был на 0,2 ниже (2 938 012,8), поэтому будет ошибка 0,2 в 262 144 или 2 в 131 072. В десятичном формате это значение равно 0,000015258789063. Поэтому, если вы напечатаете достаточно цифр, вы увидите это значение ошибки в вашем выводе.

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

Это не просто, но вот, пожалуйста. Я уверен, что вы можете закодировать это.

* примечание: для очень малых по величине значений (примерно менее 2 ^ -127) вы попадаете в странность, называемую денормалией. Я не собираюсь их объяснять, но они не будут соответствовать шаблону. К счастью, они не появляются много. И как только вы попадаете в этот диапазон, ваша точность все равно возрастает.

2 голосов
/ 05 декабря 2009

Вы можете контролировать количество выводимых десятичных знаков, включив это в спецификатор формата.

Так что вместо

float f = 43.2f,
printf("f1 = %f\n",f);

Иметь это

float f = 43.2f,
printf("f1 = %.2f\n",f);

чтобы напечатать два числа после десятичной точки.

Обратите внимание, что числа с плавающей запятой не точно представлены в памяти.

0 голосов
/ 05 декабря 2009

Чтобы выяснить, что будет напечатано, нужно точно смоделировать, что будет делать компилятор / библиотеки / оборудование:

  1. Преобразовать число в двоичное и округлить до 24 значащих (двоичных) цифр.
  2. Преобразование этого числа в десятичное и округление до 6 (десятичных) цифр после десятичной точки.

Конечно, это именно то, что ваша программа уже делает, так что вы просите?

Редактировать , чтобы проиллюстрировать, я рассмотрю один из ваших примеров:

Начните с преобразования 23,7 в двоичный файл:

10111.1011001100110011001100110011001100110011001100110011...

Округлите это число до 24 значащих двоичных цифр:

10111.1011001100110011010

Обратите внимание, что это округлено. Преобразование обратно в десятичную дает:

23.700000762939453125

Теперь, округляем это значение до 6 цифр после десятичной точки:

23.700001

Что именно вы и наблюдали.

0 голосов
/ 05 декабря 2009

Общее упрощение: правило таково: « с плавающей запятой хороши для 2 или 3 десятичных знаков, удваивается для 4 или 5 ». Другими словами, первые 2 или 3 напечатанных десятичных знака будут именно тем, что вы вставили. После этого вам нужно выработать кодировку, чтобы увидеть, что вы собираетесь получить.

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

0 голосов
/ 05 декабря 2009

Число с плавающей запятой или число с плавающей запятой двойной точности сохраняется как целочисленный числитель, а степень 2 - как знаменатель. Математика за этим довольно проста. Это включает сдвиг и битовое тестирование.

Таким образом, когда вы объявляете константу в базе 10, компилятор преобразует ее в двоичное целое число в 23 бита и показатель степени в 8 (или 52-битное целое и 11-битное значение).

Чтобы распечатать его обратно, он преобразует эту дробь обратно в базу 10.

0 голосов
/ 05 декабря 2009

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

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

// outputs "0.70"
printf("%.2f\n", 0.7f);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...