2013-04-08 2 views
0

Я играю с простым старым C. Я просто вычисляю несколько пенни, а затем увеличиваю количество экспоненциально на определенное количество итераций. В конце процесса у меня есть большое количество центов/копеек, которые я хочу отображать на экране в виде долларов и центов.Почему плавать?

Мой выходной ток выглядит следующим образом:

"You have 805306365 pennies" 

Я пытаюсь преобразовать это число в доллары, определив новую переменную как поплавок:

float totalD = total/100.00; 

Выход показывает 8053063.50. Почему разделение этого int приводит к потере 15 центов? Я пытаюсь показать это через «Printf», если это помогает:

printf("You have %.2f dollars", totalD); 

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

+8

Если вы используете 'float', он не будет иметь достаточной точности, чтобы держать' 805306365'. И не используйте типы с плавающей запятой за деньги. – Mysticial

+2

[Прочтите внимательно] (http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html). Это займет некоторое время, но навсегда изменит любые идеи, которые могут возникнуть в отношении чисел с плавающим указателем. – WhozCraig

+0

Не делайте математику с номерами с плавающей запятой, когда целых чисел будет достаточно. –

ответ

5

обновляется для точности и полноты

Вы встречая это (довольно часто) проблема точности в числа с плавающей точкой. Во многих компиляторах числа с плавающей запятой составляют всего 32 бита, и они используют определенное количество этих битов (23) для своего «значимого» (иногда называемого «мантисса»), 1 знакового бита и 8 бит для " exponent "(в ряду, например, 1.23E45, значение« 1.23 »является значимым, а« 45 »- показателем. В двоичном формате у вас на самом деле имеется относительно мало (24) единиц и нулей, поэтому у вас заканчивается точность вокруг цифра 6 или 7 в десятичной нотации).

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

#include <stdio.h> 

int main(){ 
    float fpennies; 
    long lpennies, ii; 

    lpennies = 805306360; 
    for(ii = 0; ii< 100; ii++) { 
     fpennies = lpennies + ii; 
     printf("%ld pennies converted to float: %.0f fpennies\n",ii+ lpennies, fpennies); 
    } 
} 

Это вызвало много линий типа

805306360 pennies converted to float: 805306368 fpennies 
805306361 pennies converted to float: 805306368 fpennies 
805306362 pennies converted to float: 805306368 fpennies 
... 
805306400 pennies converted to float: 805306368 fpennies 
805306401 pennies converted to float: 805306432 fpennies 

Как вы можете видеть, прямо вокруг 805306400, увеличивающиеся long всего за один шаг увеличивает числовое значение float на 64! Это лучше всего объяснить, если немного взглянуть на двоичное представление числа с плавающей запятой.

Во-первых, здесь является организация 32-битовое число с плавающей запятой (от http://upload.wikimedia.org/wikipedia/commons/d/d2/Float_example.svg):

enter image description here

Мы можем получить шестнадцатеричное представление числа с некоторой явной отливке:

printf("%.0f %08x", fpennies, *(unsigned int*)(&fpennies)); 

Для двух значений, которые охватывали скачок, который мы видели ранее, это приводит к

805306368 4e400000 
805306432 4e400001 

Как вы можете видеть, «наименее значащий бит» значимости увеличился на 1, но показатель показывает умножитель 64. Почему 64?Ну, давайте расширим несколько верхних битов:

0x4e40 = 0100 1110 0100 0000 in binary 

Поскольку верхний бит знаковый бит (0 = положительный), а следующие восемь битов показатель, это делает показатель

1001 1100 = 0x9c = 156 

Теперь правило для получения из битов в плавающей точкой к его значению (см http://en.wikipedia.org/wiki/Single-precision_floating-point_format) является

value = (-1)^(sign bit) * (1 + sum(i=1 to 23, bit(23-i)*2^(-i))) * 2^(exponent - 127) 

в этом случае изменение 1 в наименее значащий бит (бит 0) добавляет 2^(-23) * 2^(156 - 127) = 2^6 = 64

Таким образом, для чисел этой величины наименьший шаг, который может быть представлен, равен 64, как вы видите на выходе.

Если вы хотите обойти эту проблему, вы можете сделать то, что было предложено в ответе Вона: работать с длинными целыми числами, представляющими пенни, и использовать целочисленную математику (деление, по модулю) для получения суммы «целых долларов, целых центов» ,

long int dollars, cents, pennies; 
... 
dollars = pennies/100; 
cents = pennies % 100; 

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

На практике, когда вы пишете

float pennies = 805306365; 
printf("you have %f pennies\n", pennies); 

Вы

You have 805306368 pennies 

Если вы используете double тип у вас будет лучшая удача (в данном случае).

+2

Я уверен, что это опечатка. Обычно «float» использует 8 бит для величины и 24 ('24 - 1' физических бит) для значения. –

+0

@ DanielFischer - это была абсолютно моя опечатка. Спасибо, что поймали его. Фиксация ... – Floris

+2

Я думаю, вы обнаружите, что для знака используется 1 бит, 8 бит, используемых для экспоненты, 23 явных бита и 1 неявный бит, используемый для мантиссы. См. Википедия в [IEEE 754] (http://en.wikipedia.org/wiki/IEEE_floating_point). –

-1

Рассмотрите возможность использования long int или long long для представления копейки. Поплавки или двойники не подходят для того, что вы пытаетесь сделать. Я предлагаю прочитать это discussion on StackOverflow.

2

Я думаю, вы бы лучше удачи делать это таким образом:

printf("You have %d.%02d dollars", total/100,total%100); 
+0

+1 для решения второй половины вопроса - «и, возможно, как с этим справиться». Работа явно в грошах и отказ от преобразования в 'float' - это способ поддерживать такую ​​(абсолютную) точность. – Floris

+0

Очень полезно. Это мои первые пару дней обучения C, поэтому я не знал, что я мог бы форматировать свой вывод таким образом. Еще раз спасибо! –

Смежные вопросы