Проблема преобразования из int в float
Есть странное поведение, которое я не могу понять.
Согласовано, что число точек с плавающей точкой является приближением, поэтому даже операции, которые, очевидно, возвращают число без десятичных чисел, могут быть приближены к чему-то с десятичными знаками.
Я делаю это:
int num = (int)(195.95F * 100);
и поскольку это операция с плавающей запятой, я получаю 19594 вместо 19595 года.. но это правильно.
Меня озадачивает то, что если я делаю
float flo = 195.95F * 100;
int num = (int) flo;
Я получаю правильный результат 19595 года.
Любая идея, почему это происходит?
Ответы
Ответ 1
Я посмотрел, был ли это компилятор, выполняющий математику, но он ведет себя таким образом, даже если вы его отключите:
static void Main()
{
int i = (int)(GetF() * GetI()); // 19594
float f = GetF() * GetI();
int j = (int)f; // 19595
}
[MethodImpl(MethodImplOptions.NoInlining)]
static int GetI() { return 100; }
[MethodImpl(MethodImplOptions.NoInlining)]
static float GetF() { return 195.95F; }
Похоже, что разница в том, остается ли она в регистрах (шире, чем нормальный r4), или принудительно изменяется на переменную float
:
L_0001: call float32 Program::GetF()
L_0006: call int32 Program::GetI()
L_000b: conv.r4
L_000c: mul
L_000d: conv.i4
L_000e: stloc.0
против
L_000f: call float32 Program::GetF()
L_0014: call int32 Program::GetI()
L_0019: conv.r4
L_001a: mul
L_001b: stloc.1
L_001c: ldloc.1
L_001d: conv.i4
L_001e: stloc.2
Единственное отличие - это stloc.1
/ldloc.1
.
Это подтверждается тем фактом, что если вы сделаете оптимизированную сборку (которая удалит локальную переменную), я получаю тот же ответ (19594) для обоих.
Ответ 2
этот код...
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
float result = 195.95F*100;
int intresult = (int)(195.95F * 100);
}
}
}
дают этот IL
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
// Code size 14 (0xe)
.maxstack 1
.locals init ([0] float32 result,
[1] int32 intresult)
IL_0000: nop
IL_0001: ldc.r4 19595.
IL_0006: stloc.0
IL_0007: ldc.i4 0x4c8a
IL_000c: stloc.1
IL_000d: ret
} // end of method Program::Main
посмотрите на IL_00001 → компилятор сделал расчёт..
В противном случае существует проблема с двоичным преобразованием decimal →
Ответ 3
Отметьте ответ правильно, что это преобразование между nativefloat и float32/float64.
Это описано в спецификации CLR ECMA, но David Notario объясняет это намного лучше, чем я мог.
Ответ 4
Попробуйте преобразовать float в double во втором примере:
double flo = 195.95F * 100;
int num = (int) flo;
Я предполагаю, что в первом примере компилятор использует double для хранения промежуточного результата, и поэтому в случае с плавающей точкой вы теряете точность.
Ответ 5
При умножении на 100, это целое число, поэтому на этом шаге выполняется неявное преобразование. Если вы поместите "F" за 100, я буду держать пари, что они будут такими же.
Обычно я использую бокс /unboxing с круглыми скобками, когда это ссылочный тип. Когда это тип значения, я пытаюсь использовать статические методы Convert.
Попробуйте Convert.ToSingle(YourNumber); для более надежного преобразования.
НТН
Ответ 6
Я не могу ответить, почему второй работает, а первый - нет. Тем не менее, я могу сказать вам, что 195.95 является бесконечным десятичным числом в двоичном формате, и такие ошибки округления, как этот, неизбежно произойдут.
Попробуйте преобразовать в double, а не в float. Вы также можете использовать деньги или десятичный тип, а не float. Это сохранит номер по-разному и более точно.
Подробнее о числах с плавающей запятой и представлении IEEE см. здесь:
http://en.wikipedia.org/wiki/IEEE_754