Целочисленное преобразование в С#
string[] strArray = new string[10] { "21.65", "30.90", "20.42", "10.00", "14.87", "72.19", "36.00", "45.11", "18.66", "22.22" };
float temp = 0.0f;
Int32 resConvert = 0;
Int32 resCast = 0;
for (int i = 0; i < strArray.Length; i++)
{
float.TryParse(strArray[i], out temp);
resConvert = Convert.ToInt32(temp * 100);
resCast = (Int32)(temp * 100);
Console.WriteLine("Convert: " + resConvert + " ExplCast: " + resCast);
}
Ans:
Convert: 2165 ExplCast: 2164 // ??
Convert: 3090 ExplCast: 3089 // ??
Convert: 2042 ExplCast: 2042
Convert: 1000 ExplCast: 1000
Convert: 1487 ExplCast: 1486 //??
Convert: 7219 ExplCast: 7219
Convert: 3600 ExplCast: 3600
Convert: 4511 ExplCast: 4511
Convert: 1866 ExplCast: 1865 //??
Convert: 2222 ExplCast: 2221 //??
Почему значение иногда отличается при выполнении Explicit Cast, но не всегда.
Любая причина?
Ответы
Ответ 1
Чтобы взять один пример, формат 21.65 в float
фактически представлен числом, например 21.6499999. Convert.ToInt32
округляет число до ближайшего целого числа, давая 21.65, тогда как явный листинг (Int32)
просто обрезает (округляет к нулю), поэтому вы получаете 21.64.
Если вы хотите, чтобы числа с плавающей запятой были представлены на компьютере так же, как они выглядели, используйте decimal
вместо float
или double
.
Ответ 2
Convert.ToInt32
раунды до ближайшего целого, прямой приведение просто обрезает число.
Итак, если вы из-за неточности с плавающей запятой имеете значение 2165.99999whatever
вместо 2165.0
, прямой литье усекает все после плавающей запятой, а Convert.ToInt32
округляется до ближайшего целого.
Пример:
22.22f * 100.0f
приводит к чему-то вроде 2221.99993133544921875
.
Таким образом, Convert.ToInt32
будет округлить его вверх до ожидаемого значения 2222
, в то время как литье усекает его до 2221
.
45.11f * 100.0f
, с другой стороны, имеет значение около 4511.00006103515625
,
который Convert.ToInt32
округляет вниз, что приводит к 4511
, тому же результату, что и при литье.
Ответ 3
Следуя ответу Botz3000, соответствующие разделы из MSDN:
Метод Convert.ToInt32 (одиночный)
Возвращаемое значение Тип: System.Int32, округленное до ближайшего 32-битного целое число со знаком. Если значение находится на полпути между двумя целыми числами, четное число возвращается; то есть 4,5 преобразуется в 4, а 5.5 - преобразован в 6.
Явная таблица числовых преобразований
• Когда вы конвертируете из значения double или float в интегральный тип, значение округляется до нуля до ближайшего целочисленного значения. Если результирующее значение интеграла находится вне диапазона адресата значение, результат зависит от контекста проверки переполнения. В проверенный контекст, генерируется исключение OverflowException, в то время как неконтролируемый контекст, результатом является неопределенное значение типа назначения.
Ответ 4
Я думаю, вы обнаружите, что проблема вызвана неточностями точности с плавающей запятой, в частности с помощью float
. При использовании decimal
проблема исчезнет. Существует действительно хороший ответ на различия между десятичными и двойными (и float) здесь: decimal vs double! - Кого я должен использовать и когда?
Ответ 5
Вызов Convert.ToInt32 похож на вызов:
(int) Math.Round(floatValue, 0);
Прямое кастинг - это вызов
(int) Math.Floor(float);
Пол всегда дает вам значение, меньшее или равное значению, которое вы предоставили в аргументе. Представления с плавающей точкой не являются "точными". Таким образом, 21.65, вероятно, представлен как 21.649999 или аналогичный, поскольку недостаточно точности.
Итак:
21,65 * 100 = 2164,9999
Покрытие этого значения должно дать вам целое число, которое меньше или равно 2164,9... т.е.: 2164
Округление 2164.99, с другой стороны, даст вам: 2165
Здесь вы можете увидеть эффект:
Console.WriteLine(Math.Round(21.65f*100)); //2165
Console.WriteLine(Math.Floor(21.65f*100)); //2164
Использование двойников вместо float (более точность, но не бесконечная):
Console.WriteLine(Math.Round(21.65d*100)); //2165
Console.WriteLine(Math.Floor(21.65d*100)); //2165