Это чувствует себя подобно виду кода, который только перестал работать на месте, но я попытаюсь адаптировать его во фрагмент кода, который представляет то, что я вижу.
float f = myFloat * myConstInt; /* Where myFloat==13.45, and myConstInt==20 */
int i = (int)f;
int i2 = (int)(myFloat * myConstInt);
После продвижения через код, я == 269, и i2 == 268. Что продолжает здесь составлять различие?
Вычисления с плавающей точкой можно выполнять с более высокой точностью, чем заявлено. Но как только вы сохраняете ее в float f, эта дополнительная точность теряется. Во втором методе вы не теряете эту точность, пока, конечно, не приведете результат к int.
Edit: См. этот вопрос Почему в 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.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