2013-11-07 2 views
1
double a = 135.24;   // a is set to 135.24000000000001 actually 
double b = Math.Round(a, 0); // set to 135.0 
double c = Math.Round(a, 1); // set to 135.19999999999999 
double d = Math.Round(a, 2); // set to 135.24000000000001 
double e = Math.Round(a, 3); // set to 135.24000000000001 
double f = Math.Round(a, 4); // set to 135.24000000000001 
double g = Math.Round(a, 5); // set to 135.24000000000001 
double h = Math.Round(a, 10); // set to 135.24000000000001 
double i = Math.Round(a, 14); // set to 135.24000000000001 


double j = Math.Round(a, 2 
, MidpointRounding.AwayFromZero); // set to 135.24000000000001 
double k = Math.Round(a, 2 
, MidpointRounding.ToEven); // set to 135.24000000000001 

Sooooo, это означает, что 135.24 не может быть представлен двойным, право?двойное и округление очень тяжелое, но это безумие

+8

Я предпочитаю верить число 135,24 не существуют –

+0

Округление до одной цифры (135.2) не представляется в двоичном виде. Ближайшее представимое значение - 135.199, повторяющееся. Округление до двух цифр и выше будет округлено до 135.24, которое не представляется в двоичном виде (135.2400 ... 01). Я нахожу http://www.h-schmidt.net/FloatConverter/ полезным. – Caramiriel

+0

В качестве примечания стороны: double не представляет целые числа больше 2 ** 52 правильно. float имеет предел точности 2 ** 23 so '(float) Math.Pow (2,24) == 16777217' Идите и попробуйте! – user2622016

ответ

3

Попробуйте использовать десятичный знак вместо. Плавающие точки не очень точны (поэтому некоторые цифры не могут быть представлены):

+4

точное не обязательно правильное слово; они очень точно представляют значения, которые * могут быть сохранены в их кодировке * –

+0

Извините, это был бы лучший способ выразить это. Они очень точны в зависимости от их возможностей :) –

+0

В десятичном виде также много цифр не могут быть представлены, например, 2/3. На самом деле существует только одна важная разница: десятичная запятая использует десятичную (очевидно), двойную - двоичную. Поскольку 10 делит на 2 и 5, десятичное число может представлять 3/5 точно, double может представлять только числа, такие как 1/8. – user2622016

0

Да, это невозможно. Вот почему есть еще один неинтегратный тип данных, который называется decimal. Он занимает различный объем памяти и имеет разные минимальные/максимальные числовые диапазоны, чем double, и НЕ бит-конвертируемый *), чтобы удвоить, но в свою очередь он может содержать номера точно без каких-либо искажений.

*) То есть вы не можете скопировать его в виде байтов и нажать на код C++. Тем не менее, вы можете по-прежнему cast его удвоить и обратно. Просто помните, что бросок НЕ будет точным, так как double не может содержать некоторые цифры, которые decimal может, и наоборот

4

Это, как правило, проблема с числами с плавающей запятой. Если вам нужно точное представление чисел (например, для выставления счетов, ...), вы должны использовать Decimal. Попробуйте выполнить часть кода, и вы увидите, что у вас нет выходных данных 0, 0.1, 0.2, ... 1.0.

for(double i = 0; i <= 1.0; i += 0.001) 
{ 
    Console.WriteLine(i); 
} 
+0

Десятичное число может представлять собой только десятичные числа, такие как '3434.1234m/25m', но если вы разделитесь на 3, вы можете в итоге получить« 0.3333 ... »(28 цифр 3). Не точная, но большая точность, чем двойная, которая имеет около 15 значащих цифр. – user2622016

0

Вы можете увидеть функцию definition.The Round определяется как-

public static double Round(double value, int digits, MidpointRounding mode) 
    { 
     if (digits < 0 || digits > 15) 
     throw new ArgumentOutOfRangeException("digits", Environment.GetResourceString("ArgumentOutOfRange_RoundingDigits")); 
     if (mode >= MidpointRounding.ToEven && mode <= MidpointRounding.AwayFromZero) 
     return Math.InternalRound(value, digits, mode); 
     throw new ArgumentException(Environment.GetResourceString("Argument_InvalidEnumValue", (object) mode, (object) "MidpointRounding"), "mode"); 
    } 


private static unsafe double InternalRound(double value, int digits, MidpointRounding mode) 
    { 
     if (Math.Abs(value) < Math.doubleRoundLimit) 
     { 
     double num1 = Math.roundPower10Double[digits]; 
     value *= num1; 
     if (mode == MidpointRounding.AwayFromZero) 
     { 
      double num2 = Math.SplitFractionDouble(&value); 
      if (Math.Abs(num2) >= 0.5) 
      value += (double) Math.Sign(num2); 
     } 
     else 
      value = Math.Round(value); 
     value /= num1; 
     } 
     return value; 
    } 
5

Да, 135,24 не могут быть представлены дважды, так как дважды использует двоичную экспоненциальную нотацию.

То есть: 135.24 может быть представлено экспоненциально в основании по 2 как 1.0565625 * 128 = (1 + 1/32 + 1/64 + 1/128 + 1/1024 + ...) * (2 ** 7).

представление не может быть сделано точно, поскольку 13524 не делится на 5. Давайте посмотрим:

135.24 = 13524/(10**2)

представление является конечным <=> существует целое х и п, удовлетворяющих 135.24 = x/(2**n)

135.24 = x/(2**n) 
13524/(10**2) = x/(2**n) 
13524 * (2**n) = (10**2) * x 
13524 * (2**n) = 2*2*5*5 * x 

нет «5» с левой стороны, поэтому это невозможно сделать (известная как фундаментальная теорема Ари)

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

Теперь самое интересное:

double delta = 0.5; 
    while(1 + delta > 1) 
     delta /= 2; 

    Console.WriteLine(delta); 

Точность двойной отличается рядом 1, отличается около 0, а другая для некоторых больших чисел. Некоторые бинарные примеры представления в Википедии: Double precision floating point format

Но самое главное, что стек внутреннего процессора с плавающей точкой может иметь гораздо лучше точность чем 8 байт (дважды). Если номер не нужно передавать в ОЗУ и разделять до 8 байтов, мы можем получить очень хорошую точность.

Тестирование чего-то подобного на разных процессорах (AMD, Intel), языках (C, C++, C#, Java) или уровнях оптимизации компилятора может дать результаты могут быть около 1e-16, 1e-20 или даже 1e-320

Посмотрите на КСС/ассемблер/жасмин код, чтобы увидеть то, что происходит (например, для C++ g++ -S test.cpp создает test.s файл на ассемблере в нем)

Смежные вопросы