2013-08-01 19 views
34

Zauważyłem dziwne zachowanie podczas mnożenia wartości dziesiętnych w C#. Rozważ następujące operacje mnożenia:C# dziesiętne mnożenie dziwne zachowanie

1.1111111111111111111111111111m * 1m = 1.1111111111111111111111111111 // OK 
1.1111111111111111111111111111m * 2m = 2.2222222222222222222222222222 // OK 
1.1111111111111111111111111111m * 3m = 3.3333333333333333333333333333 // OK 
1.1111111111111111111111111111m * 4m = 4.4444444444444444444444444444 // OK 
1.1111111111111111111111111111m * 5m = 5.5555555555555555555555555555 // OK 
1.1111111111111111111111111111m * 6m = 6.6666666666666666666666666666 // OK 
1.1111111111111111111111111111m * 7m = 7.7777777777777777777777777777 // OK 
1.1111111111111111111111111111m * 8m = 8.888888888888888888888888889 // Why not 8.8888888888888888888888888888 ? 
1.1111111111111111111111111111m * 9m = 10.000000000000000000000000000 // Why not 9.9999999999999999999999999999 ? 

Czego nie mogę zrozumieć, to ostatnie dwa z powyższych przypadków. Jak to możliwe?

+11

Witamy w cudownym świecie błędów precyzji. – christopher

+0

To nie jest błąd precyzji, tylko zaokrąglenie. –

+0

Oto matematyczna zagadka dla Ciebie: '10/9 = 1.111 ...'; '1,111 ... * 9 = 9,999 ... ≠ 10'. ;) – stakx

Odpowiedz

73

decimal przechowuje 28 lub 29 cyfr znaczących (96 bitów). Zasadniczo mantysa znajduje się w przedziale -/+ 79.222.68.514,264,337,593,543,950.335.

To oznacza do około 7,9 ... można uzyskać dokładnie 29 cyfr znaczących - ale powyżej nie można. Dlatego zarówno 8, jak i 9 się nie zgadzają, ale nie wcześniejsze wartości. Powinieneś tylko ufać na 28 cyfr znaczących w ogóle, aby uniknąć takich dziwnych sytuacji jak ta.

Po zmniejszyć oryginalny wkład do 28 cyfr znaczących, dostaniesz wyjście się spodziewać:

using System; 

class Test 
{ 
    static void Main() 
    { 
     var input = 1.111111111111111111111111111m; 
     for (int i = 1; i < 10; i++) 
     { 
      decimal output = input * (decimal) i; 
      Console.WriteLine(output); 
     } 
    } 
} 
+0

Nie jestem pewien, co dokładnie masz na myśli mówiąc "polegaj na" 28 cyfrach. Czy chodzi ci o wyraźne zaokrąglanie rzeczy takich jak wyniki podziałów? Fakt, że "dziesiętny" używa wykładników bazowych, a nie bazowych-2, oznacza, że ​​nominalna wartość liczbowa "dziesiętnego" będzie pasować do jej krótkiej reprezentacji, ale fakt, że jest to zmiennoprzecinkowy, sugerowałby, że równość -testing z 'Decimal' może mieć te same problemy, co w przypadku każdego innego typu zmiennoprzecinkowego. – supercat

+0

@supercat: Mam na myśli to, że jeśli masz 29 cyfr, które musisz reprezentować, możesz nie być w stanie dokładnie tego zrobić, ale możesz dokładnie reprezentować do 28 cyfr. Myślę, że rozsądniej jest polegać na równości "dziesiętnej" niż "float' /" double "- po pierwsze, nie musisz martwić się o możliwość wykonywania niektórych operacji z większą dokładnością w zależności od tego, czy wartość jest w rejestrze lub nie.Nadal należałoby to zrobić z dużą ostrożnością, ale w wielu przypadkach wydaje mi się, że będzie dobrze. –

2

Matematycy odróżniania liczb wymiernych i rzeczywistych liczb rozszerzeniem. Operacje arytmetyczne na liczbach wymiernych są dobrze określone i precyzyjne. Arytmetyka (za pomocą operatorów dodawania, odejmowania, mnożenia i dzielenia) na liczbach rzeczywistych jest "dokładna" tylko w takim zakresie, w jakim liczby irracjonalne pozostaną w formie nieracjonalnej (symbolicznej) lub ewentualnie przekształcalne w niektórych wyrażeniach w liczbę racjonalną . Przykład: pierwiastek kwadratowy z dwóch nie ma reprezentacji dziesiętnej (lub innej racjonalnej podstawy). Jednak pierwiastek kwadratowy z dwóch pomnożonych przez pierwiastek kwadratowy z dwóch jest racjonalny - 2, oczywiście.

Komputery i działające na nich języki generalnie realizują tylko liczby wymierne - ukryte pod nazwami takimi jak int, long int, float, double precision, real (FORTRAN) lub inną nazwą sugerującą liczby rzeczywiste. Ale liczby wymierne są ograniczone, w przeciwieństwie do zbioru liczb wymiernych, których zakres jest nieskończony.

Przykładowy przykład - nie występuje na komputerach. 1/2 * 1/2 = 1/4 To działa dobrze, jeśli masz klasę liczb wymiernych ORAZ wielkość liczników i mianowników nie przekracza limitów arytmetycznych całkowitych. więc (1,2) * (1,2) -> (1,4)

Ale jeśli dostępne liczby wymierne były dziesiętne ORAZ ograniczone do jednej cyfry po przecinku - niepraktyczne - ale reprezentatywne dla wyboru dokonanego, gdy wybierając implementację przybliżającą liczby racjonalne (zmiennoprzecinkowe/rzeczywiste, itp.), wówczas 1/2 byłoby idealnie wymienialne na 0,5, a następnie 0,5 + 0,5 równało się 1,0, ale 0,5 * 0,5 musiało wynosić 0,2 lub 0,3!