Вопрос

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

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

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

Это было полезно?

Решение

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

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

Другие советы

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

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

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

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

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

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

Заменить

double f = myFloat * myConstInt;

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

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

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

 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.01110011001100110011000000000000000000000000000000

    myfloat * 20 = 100001100.111111111111111111100000000000000000000000000

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top