обновляется для точности и полноты
Вы встречая это (довольно часто) проблема точности в числа с плавающей точкой. Во многих компиляторах числа с плавающей запятой составляют всего 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):
Мы можем получить шестнадцатеричное представление числа с некоторой явной отливке:
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
тип у вас будет лучшая удача (в данном случае).
Если вы используете 'float', он не будет иметь достаточной точности, чтобы держать' 805306365'. И не используйте типы с плавающей запятой за деньги. – Mysticial
[Прочтите внимательно] (http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html). Это займет некоторое время, но навсегда изменит любые идеи, которые могут возникнуть в отношении чисел с плавающим указателем. – WhozCraig
Не делайте математику с номерами с плавающей запятой, когда целых чисел будет достаточно. –