Почему мое число округляется неправильно?
-
22-09-2019 - |
Вопрос
Это похоже на код, который дает сбой только на месте, но я попытаюсь адаптировать его во фрагмент кода, который представляет то, что я вижу.
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.
(Аналогичное объяснение будет работать, если используется расширенная точность.)
ОБНОВЛЯТЬ:Я уточнил свой ответ и написал полноценную статью под названием Когда поплавки ведут себя не так, как поплавки