У меня есть такой простой код:
int speed1 = (int)(6.2f * 10);
float tmp = 6.2f * 10;
int speed2 = (int)tmp;
speed1
и speed2
должно иметь такое же значение, но на самом деле у меня есть:
speed1 = 61
speed2 = 62
Я знаю, что мне, вероятно, следует использовать Math.Round вместо приведения, но я хотел бы понять, почему значения разные.
Я посмотрел на сгенерированный байт-код, но, за исключением хранения и загрузки, коды операций совпадают.
Я также пробовал тот же код в java, и я правильно получил 62 и 62.
Кто-нибудь может это объяснить?
Изменить: в реальном коде это не напрямую 6.2f * 10, а вызов функции * константа. У меня есть следующий байт-код:
для speed1
:
IL_01b3: ldloc.s V_8
IL_01b5: callvirt instance float32 myPackage.MyClass::getSpeed()
IL_01ba: ldc.r4 10.
IL_01bf: mul
IL_01c0: conv.i4
IL_01c1: stloc.s V_9
для speed2
:
IL_01c3: ldloc.s V_8
IL_01c5: callvirt instance float32 myPackage.MyClass::getSpeed()
IL_01ca: ldc.r4 10.
IL_01cf: mul
IL_01d0: stloc.s V_10
IL_01d2: ldloc.s V_10
IL_01d4: conv.i4
IL_01d5: stloc.s V_11
мы можем видеть, что операнды являются числами с плавающей запятой, и единственная разница - это stloc/ldloc
.
Что касается виртуальной машины, я пробовал с Mono / Win7, Mono / MacOS и .NET / Windows с теми же результатами.
источник
Ответы:
Прежде всего, я предполагаю, что вы знаете, что
6.2f * 10
это не совсем 62 из-за округления с плавающей запятой (на самом деле это значение 61.99999809265137, когда оно выражено какdouble
), и что ваш вопрос только о том, почему два, казалось бы, одинаковых вычисления приводят к неправильному значению.Ответ заключается в том, что в случае
(int)(6.2f * 10)
сdouble
значение 61.99999809265137 и усекаете его до целого числа, что дает 61.В случае
float f = 6.2f * 10
, вы принимаете удвоенное значение 61.99999809265137 и округления до ближайшегоfloat
, что 62. Вы затем усечение чтоfloat
в целое, а результат 62.Упражнение: Объясните результаты следующей последовательности операций.
Обновление: как отмечено в комментариях, выражение
6.2f * 10
формально является a,float
поскольку второй параметр имеет неявное преобразование,float
которое лучше, чем неявное преобразование вdouble
.Фактическая проблема заключается в том, что компилятору разрешено (но не обязательно) использовать промежуточное звено, точность которого выше, чем у формального типа (раздел 11.2.2) . Вот почему вы видите разное поведение в разных системах: в выражении
(int)(6.2f * 10)
компилятор имеет возможность сохранить значение6.2f * 10
в промежуточной форме высокой точности перед преобразованием вint
. Если да, то результат 61. Если нет, то результат 62.Во втором примере явное присвоение для
float
принудительного округления выполняется перед преобразованием в целое число.источник
(int)(6.2f * 10)
беретсяdouble
значение, какf
указаноfloat
? Я думаю, что главный вопрос (до сих пор без ответа) здесь.6.2f * 10
самом делеfloat
нетdouble
. Я думаю, что компилятор оптимизирует промежуточное звено, как разрешено последним абзацем 11.1.6 .float
сначала выполняется преобразование.Описание
Плавающие числа редко бывают точными.
6.2f
это что-то вроде6.1999998...
. Если вы приведете это к int, он усечет его, и это * 10 приведет к 61.Посмотрите
DoubleConverter
класс Джона Скитса . С помощью этого класса вы действительно можете визуализировать значение плавающего числа в виде строки.Double
иfloat
оба являются числами с плавающей запятой, а десятичные - нет (это число с фиксированной запятой).Образец
Больше информации
источник
Посмотрите на ИЛ:
Компилятор сокращает выражения констант времени компиляции до их постоянного значения, и я думаю, что в какой-то момент он делает неправильное приближение, когда преобразует константу в
int
. В случаеspeed2
, это преобразование выполняется не компилятором, а CLR, и они, похоже, применяют другие правила ...источник
Я думаю, что
6.2f
реальное представление с плавающей точкой точности является в6.1999999
то время62f
, вероятно , что - то подобное62.00000001
.(int)
приведение всегда обрезает десятичное значение , поэтому вы получаете такое поведение.РЕДАКТИРОВАТЬ : Согласно комментариям, я перефразировал поведение
int
приведения в более точное определение.источник
int
десятичному значению усекает, оно не округляется.float
->int
включает округление. = DЯ скомпилировал и дизассемблировал этот код (на Win7 / .NET 4.0). Я предполагаю, что компилятор оценивает выражение с плавающей константой как double.
источник
Single
сохраняет только 7 цифр и при преобразованииInt32
в компилятор усекает все цифры с плавающей запятой. Во время преобразования могут быть потеряны одна или несколько значащих цифр.дает результат 619999980, поэтому (Int32) (6.2f * 10) дает 61.
Другое дело, когда два Single умножаются, в этом случае нет операции усечения, а только приближение.
См. Http://msdn.microsoft.com/en-us/library/system.single.aspx
источник
Есть ли причина, по которой вы выполняете приведение типов
int
вместо синтаксического анализа?затем прочитал бы
Разница, вероятно, связана с округлением: если вы выберете
double
выберете, вы, вероятно, получите что-то вроде 61.78426.Обратите внимание на следующий вывод
Вот почему вы получаете разные значения!
источник
Int.Parse
принимает строку в качестве параметра.