Почему мой номер округляется неправильно? - PullRequest
7 голосов
/ 24 марта 2010

Это похоже на код, который не работает только на месте, но я попытаюсь адаптировать его к фрагменту кода, который представляет то, что я вижу.

float f = myFloat * myConstInt; /* Where myFloat==13.45, and myConstInt==20 */
int i = (int)f;
int i2 = (int)(myFloat * myConstInt);

После пошагового выполнения кода i == 269 и i2 == 268. Что здесь происходит, чтобы объяснить разницу?

Ответы [ 5 ]

15 голосов
/ 24 марта 2010

Плавающая математика может быть выполнена с большей точностью, чем рекламируется. Но как только вы сохраняете его в float f, эта дополнительная точность теряется. Вы не потеряете эту точность во втором методе, пока, конечно, вы не приведете результат к int.

Редактировать: см. Этот вопрос Почему отличается точность с плавающей точкой в ​​C #, когда они разделены парантезами и когда они разделены с помощью операторов? для лучшего объяснения, чем я, вероятно, предоставил.

4 голосов
/ 24 марта 2010

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

Различные режимы округления также могут сыграть в эту проблему, но проблема точности - та, с которой вы столкнулись, AFAIK.

2 голосов
/ 24 марта 2010

Плавающая точка имеет ограниченную точность и основана на двоичном, а не на десятичном виде. Десятичное число 13,45 не может быть точно представлено в двоичной форме с плавающей запятой, поэтому округляется вниз. Умножение на 20 еще больше увеличивает потерю точности. На данный момент у вас есть 268,999 ... а не 269, поэтому преобразование в целочисленные усекается до 268.

Чтобы получить округление до ближайшего целого числа, вы можете попробовать добавить 0,5 до преобразования обратно в целое число.

Для "идеальной" арифметики вы можете попробовать использовать десятичный или рациональный числовой тип - я считаю, что в C # есть библиотеки для обоих, но я не уверен. Однако они будут медленнее.

РЕДАКТИРОВАТЬ - Я нашел пока "десятичный" тип, но не рациональный - я могу ошибаться по поводу того, что он доступен. Десятичное число с плавающей запятой является неточным, точно так же как двоичное число, но к этому мы привыкли, и оно дает менее удивительные результаты.

1 голос
/ 25 марта 2010

Я бы хотел предложить другое объяснение.

Вот код, который я аннотировал (я посмотрел в память, чтобы разобрать поплавки):

 float myFloat = 13.45; //In binary is 1101.01110011001100110011
 int myConstInt = 20;
 float f = myFloat * myConstInt; //In binary is exactly 100001101 (269 decimal)
 int i = (int)f; // Turns float 269 into int 269 -- no surprises
 int i2 = (int)(myFloat * myConstInt);//"Extra precision" causes round to 268

Давайте посмотрим подробнее на расчеты:

  • f = 1101.01110011001100110011 * 10100 = 100001100.111111111111111 111

    Часть после пробела - это биты 25-27, в результате чего бит 24 округляется в большую сторону и, следовательно, все значение округляется до 269

  • int i2 = (int) (myFloat * myConstInt)

    myfloat увеличен до двойной точности для расчета (добавлены 0): 1101.0111001100110011001100000000000000000000000000000

    myfloat * 20 = 100001100.11111111111111111100000000000000000000000000

    Биты 54 и выше равны 0, поэтому округление не выполняется: приведение приводит к целому числу 268.

    (Аналогичное объяснение будет работать, если будет использоваться расширенная точность.)

ОБНОВЛЕНИЕ: я уточнил свой ответ и написал полноценную статью под названием Когда плавающие не ведут себя как плавающие

1 голос
/ 24 марта 2010

Заменить на

double f = myFloat * myConstInt;

и посмотрите, получите ли вы тот же ответ.

...