Почему результат разделения отличается от типа броска?
Вот часть кода, которую я не понимаю:
byte b1 = (byte)(64 / 0.8f); // b1 is 79
int b2 = (int)(64 / 0.8f); // b2 is 79
float fl = (64 / 0.8f); // fl is 80
Почему первые два вычисления отключены одним? Как я должен выполнять эту операцию, поэтому ее быстро и правильно?
EDIT: мне понадобится результат в байте
Ответы
Ответ 1
РЕДАКТИРОВАТЬ: Не совсем правильно, см. Почему результат разделения отличается в зависимости от типа приведения? (Followup)
Проблема округления: путем преобразования в byte/int вы обрезаете десятичные знаки.
Но 64 / 0.8
не должно приводить к десятичным разрядам? Неправильно: из-за характера чисел с плавающей запятой 0.8f нельзя представить точно так же, как в памяти; он хранится как нечто близкое к 0.8f (но не точно). См. Примеры неточности с плавающей запятой или аналогичные потоки. Таким образом, результат вычисления не равен 80.0f, но 79.xxx, где xxx близок к 1, но все еще не точно один.
Вы можете проверить это, введя следующее в окно Immediate в Visual Studio:
(64 / 0.8f)
80.0
(64 / 0.8f) - 80
-0.0000011920929
100 * 0.8f - 80
0.0000011920929
Вы можете решить это, используя округление:
byte b1 = (byte)(64 / 0.8f + 0.5f);
int b2 = (int)(64 / 0.8f + 0.5f);
float fl = (64 / 0.8f);
Ответ 2
Я боюсь, что быстрые и правильные противоречия в таких случаях.
Двоичная арифметика с плавающей запятой почти всегда создает небольшие ошибки из-за базового представления в наших архитектурах процессора. Таким образом, в вашем первоначальном выражении вы фактически получаете значение немного меньше, чем математически корректное. Если вы ожидаете целое число в результате конкретной математической операции и получаете что-то очень близкое к ней, вы можете использовать метод Math.Round(Double, MidpointRounding)
для правильного округления и компенсации небольших ошибок (и убедитесь, что вы выбрали стратегию MidpointRounding
, которую вы ожидаете).
Простое приведение результата к типу типа byte
или int
не делает округления - он просто отсекает дробную часть (даже 1.99999f
станет 1
, когда вы просто применяете его к этим типам).
Десятичная арифметика с плавающей запятой работает медленнее и интенсивнее, но не вызывает этих ошибок. Чтобы выполнить его, используйте decimal
литералы вместо float
литералов (например, 64 / 0.8m
).
Эмпирическое правило:
- Если вы имеете дело с точными количествами (как правило, искусственными, как деньги), используйте
decimal
.
- Если вы имеете дело с неточными величинами (такими как дробные физические константы или иррациональные числа, такие как π), используйте
double
.
- Если вы имеете дело с неточными количествами (как указано выше), и некоторая точность может быть дополнительно принесена в жертву за скорость (например, при работе с графикой), используйте
float
.
Ответ 3
Чтобы понять проблему, вам нужно понять основы представления и операций с плавающей запятой.
0.8f не может быть точно представлен в памяти с использованием числа с плавающей запятой.
В математике 64/0.8 равно 80.
В арифметике с плавающей запятой 60/0.8 равно приблизительно 80.
Когда вы создаете float для целого числа или байта, сохраняется только целая часть числа. В вашем случае неточный результат деления с плавающей запятой немного меньше 80, следовательно, преобразование в целое число дает 79.
Если вам нужен целочисленный результат, я предлагаю вам округлить результат, а не отбрасывать его.
Один из способов сделать это - использовать следующую функцию, которая преобразует в целое число округлением до ближайшего целого числа:
Convert.ToInt32(64/0.8f);