Странное поведение при приведении float в int в C #

У меня есть следующий простой код:

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, а вызов функции * константа. У меня есть следующий байт-код:

для скорости 1:

 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 

для скорости 2:

 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 

мы можем видеть, что операнды являются float и что единственное различие заключается в 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.

Упражнение: Объясните результаты следующей последовательности операций.

 double d = 6.2f * 10; int tmp2 = (int)d; // evaluate tmp2 

Обновление: Как отмечено в комментариях, выражение 6.2f * 10 формально является float поскольку второй параметр имеет неявное преобразование в float которое лучше, чем неявное преобразование в double .

Фактическая проблема заключается в том, что компилятор разрешен (но не требуется) для использования промежуточного элемента, который является более высокой точностью, чем формальный . Вот почему вы видите различное поведение в разных системах: в выражении (int)(6.2f * 10) компилятор имеет возможность сохранить значение 6.2f * 10 в промежуточной форме высокой точности перед преобразованием в int . Если это так, то результат равен 61. Если это не так, то результат равен 62.

Во втором примере явное назначение float заставляет округление иметь место до преобразования в целое.

Описание

Плавающие числа редко точны. 6.2f – это что-то вроде 6.1999998... Если вы добавите это в int, он усечет его, и это * 10 приведет к 61.

Ознакомьтесь с classом DoubleConverter от Jon DoubleConverter . С помощью этого classа вы можете реально визуализировать значение плавающего числа как строки. Double и float – это плавающие числа , десятичные – нет (это число с фиксированной точкой).

Образец

 DoubleConverter.ToExactString((6.2f * 10)) // output 61.9999980926513671875 

Больше информации

  • Класс DoubleConverter от Jon Skeet
  • Assert.AreEqual () с System.Double, действительно запутывающий
  • Что каждый компьютерный ученый должен знать о арифметике с плавающей точкой

Посмотрите на ИЛ:

 IL_0000: ldc.i4.s 3D // speed1 = 61 IL_0002: stloc.0 IL_0003: ldc.r4 00 00 78 42 // tmp = 62.0f IL_0008: stloc.1 IL_0009: ldloc.1 IL_000A: conv.i4 IL_000B: stloc.2 

Компилятор уменьшает константы постоянной времени компиляции до их постоянного значения, и я думаю, что он делает неправильное приближение в какой-то момент, когда он преобразует константу в int . В случае speed2 это преобразование выполняется не компилятором, а CLR, и они, похоже, применяют разные правила …

Я предполагаю, что реальное представление 6.2f с точностью до float равно 6.1999999 а 62f , вероятно, что-то похожее на 62.00000001 . (int) кастинг всегда усекает десятичное значение , поэтому вы получаете такое поведение.

EDIT : Согласно комментариям, я перефразировал поведение int casting до гораздо более точного определения.

Single манты всего 7 цифр, и при передаче его в Int32 компилятор усекает все числа с плавающей запятой. Во время преобразования одна или несколько значащих цифр могут быть потеряны.

 Int32 speed0 = (Int32)(6.2f * 100000000); 

дает результат 619999980, поэтому (Int32) (6.2f * 10) дает 61.

Иначе, когда два Single умножаются, в этом случае нет операции усечения, а только аппроксимация.

См. http://msdn.microsoft.com/en-us/library/system.single.aspx

Я скомпилировал и разобрал этот код (на Win7 / .NET 4.0). Я предполагаю, что компилятор оценивает плавающее постоянное выражение как двойное.

 int speed1 = (int)(6.2f * 10); mov dword ptr [rbp+8],3Dh //result is precalculated (61) float tmp = 6.2f * 10; movss xmm0,dword ptr [000004E8h] //precalculated (float format, xmm0=0x42780000 (62.0)) movss dword ptr [rbp+0Ch],xmm0 int speed2 = (int)tmp; cvttss2si eax,dword ptr [rbp+0Ch] //instrunction converts float to Int32 (eax=62) mov dword ptr [rbp+10h],eax 

Есть ли причина, по которой вы выполняете персонализацию типа int вместо синтаксического анализа?

 int speed1 = (int)(6.2f * 10) 

затем будет читать

 int speed1 = Int.Parse((6.2f * 10).ToString()); 

Разница, вероятно, связана с округлением: если вы нажмете double вы, вероятно, получите что-то вроде 61.78426.

Обратите внимание на следующий результат

 int speed1 = (int)(6.2f * 10);//61 double speed2 = (6.2f * 10);//61.9999980926514 

Вот почему вы получаете разные ценности!

  • Как отобразить двоичное представление float или double?
  • Сравнение числа с плавающей точкой с нолем
  • Библиотека высокой точности с плавающей запятой Java
  • Как обнаружить переполнение и переполнение двойной точки с двойной точностью?
  • Как вы объедините число с плавающей запятой в Perl?
  • Точное хранение больших целых чисел
  • Деление с плавающей запятой против умножения с плавающей запятой
  • Является ли плавающая точка == когда-либо ОК?
  • Является ли добавление и умножение с плавающей запятой ассоциативным?
  • Какие типы чисел представляются в двоичной с плавающей запятой?
  • Как сравнить два числа с плавающей запятой в Bash?
  • Interesting Posts

    Сгенерировать список устройств, установленных на ПК через командную строку

    Невозможно применить объект типа NHibernate.Collection.Generic.PersistentGenericBag к списку

    Каков наилучший способ генерации случайных чисел в C ++?

    Удаление пароля из файла PDF

    Отсутствует вкладка совместимости для Devenv.exe (VS 2010 и VS 2012) в Windows 8

    Заблокированный Chrome по доверенности компании

    Как включить Bitlocker без TPM

    Как разделять строку с любыми символами пробелов как разделителями?

    SSH не работает на Ubuntu

    Ошибка: неотрицательная ширина, требуемая в строке формата в (1)

    JPA EntityManager: зачем использовать persist () над merge ()?

    Как проверить, транслируется ли канал YouTube в прямом эфире

    Как (в одну сторону) синхронизировать календари между учетной записью электронной почты «work» (exchange) и outlook.com \ hotmail.com

    Возможно ли запустить одновременно несколько AsyncTask?

    Как правильно закрыть java ExecutorService

    Давайте будем гением компьютера.