Почему мое число округляет неправильно?

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

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

После продвижения через код, я == 269, и i2 == 268. Что продолжает здесь составлять различие?

7
задан Bill the Lizard 24 April 2010 в 22:33
поделиться

5 ответов

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

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

15
ответ дан 6 December 2019 в 07:50
поделиться

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

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

4
ответ дан 6 December 2019 в 07:50
поделиться

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

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

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

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

2
ответ дан 6 December 2019 в 07:50
поделиться

Заменить на

double f = myFloat * myConstInt;

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

1
ответ дан 6 December 2019 в 07:50
поделиться

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

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

 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.011100110011001100110011 * 10100 = 100001100. 11111111111111111 111

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

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

    myfloat расширяется до двойной точности для вычисления (добавляются 0): 1101. 0111001100110011001100110000000000000000000000000

    myfloat * 20 = 100001100.111111111111111111111111111111111111111111110000000000000000000000

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

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

UPDATE: Я уточнил свой ответ и написал полноценную статью под названием When Floats Don't Behave Like Floats

1
ответ дан 6 December 2019 в 07:50
поделиться
Другие вопросы по тегам:

Похожие вопросы: