2016-11-04 3 views
2

Рассмотрим следующий код:Convert.ToDouble (десятичное) неожиданная потеря точности

double d = 1478110092.9070129; 
decimal dc = 1478110092.9070129M; 

Console.WriteLine(d.ToString("R")); 
Console.WriteLine(dc); 

Console.WriteLine(Convert.ToDouble(dc).ToString("R")); 
Console.WriteLine(double.Parse(dc.ToString()).ToString("R")); 

Это приводит следующее:

1478110092.9070129 
1478110092.9070129 
1478110092.9070127 
1478110092.9070129 

Мой вопрос, что происходит в Convert.ToDouble? Ясно, что это число может быть представлено в двойном?

+2

Здесь мы идем, я думаю, что эта ссылка должна instersting читать Http: // csharpindepth. com/Articles/General/FloatingPoint.aspx – mybirthname

+0

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

+1

Двойной может быть приближением, но двойной тип разрешает число, оканчивающееся на 129, когда мы вызываем ToString («R»), так почему ConvertToDouble возвращает 127 из десятичного числа, которое заканчивается на 129? –

ответ

2

Это я считаю ошибкой в ​​Convert.ToDouble(decimal d). Спецификация C# говорит, что преобразование должно дать ближайший двойной, но здесь это явно не так. Посмотрев на биты, мы видим, что он отключен на один двойной.

double d = 1478110092.9070129; 
decimal dc = 1478110092.9070129M; 
double dcd = Convert.ToDouble(dc); 
long d_bits = BitConverter.DoubleToInt64Bits(d);  // 4743986451068882048 
long dcd_bits = BitConverter.DoubleToInt64Bits(dcd); // 4743986451068882047 

Смотрите также этот возможный дубликат:

Conversion of a decimal to double number in C# results in a difference

+1

Я кое-что сделал, это ошибка в 'OleAut32.dll', если что-нибудь. Он просто перенаправляет вызов на вызов функции. –

0

Двойной показатель точности до 16 знаков после запятой, и поэтому повторяющиеся вычисления быстро накапливают ошибки округления. Десятичный - это что-то вроде 28 десятичных знаков точности. Двойной может обрабатывать большие числа, но если вы не имеете дело с большими научными вычислениями и понимаете, что может означать для вас потеря точности, всегда используйте десятичный код, , особенно для денежных расчетов.

Существует хорошее обсуждение на this Вопрос.

EDIT

Пожалуйста, смотрите приведенные ниже комментарии - я неправильно понял вопрос, а другой пользователь (user1666620) поставил законный ответ в комментариях, которые имеют отношение к тому, как числа на самом деле хранятся. В случае Double это двоичное представление, которое не может правильно представлять дроби.

+0

Это на самом деле не отвечает на вопрос, так как в этом случае реальная проблема заключается в том, что дубликаты хранятся в двоичном формате, а некоторые числа не имеют точного представления в двоичном формате. – user1666620

+1

Справедливая точка зрения, я не достаточно внимательно прочитал вопрос. Я виню обед в пабе. –

3

Чтобы понять, почему это происходит, мы должны смотреть, чтобы внутренности Decimal.

В the source for Convert.ToDouble(Decimal) мы видим, что делает

public static double ToDouble(decimal value) { 
    return (double)value; 
} 

В источнике десятичного мы можем видеть its explicit conversion operator for double

public static explicit operator double(Decimal value) { 
     return ToDouble(value); 
    } 

и ее double ToDouble(Decimal) call

[System.Security.SecuritySafeCritical] // auto-generated 
[MethodImplAttribute(MethodImplOptions.InternalCall)] 
public static extern double ToDouble(Decimal d);\ 

Затем мы должны идти к the native implementation of the ToDouble call

FCIMPL1(double, COMDecimal::ToDouble, FC_DECIMAL d) 
{ 
    FCALL_CONTRACT; 

    ENSURE_OLEAUT32_LOADED(); 

    double result = 0.0; 
    // Note: this can fail if the input is an invalid decimal, but for compatibility we should return 0 
    VarR8FromDec(&d, &result); 
    return result; 
} 
FCIMPLEND 

Что приводит нас к VarR8FromDec, которая является функцией окна внутри OleAut32.dll, которые мы не имеем источника к.

Вероятно, проблема в OleAut32.dll не делает преобразование максимального уровня, которое может быть выполнено.

Если вы хотите конвертировать с max percision, вам нужно сначала перейти к строке с форматом R, а затем проанализировать строку.


Если вам интересно, я побежал VarR8FromDec через декомпилятор, и вот что он делает внутренне

HRESULT __stdcall VarR8FromDec(const DECIMAL *pdecIn, DOUBLE *pdblOut) 
{ 
    BYTE v2; // [email protected] 
    BYTE v3; // [email protected] 
    double v4; // [email protected] 
    HRESULT result; // [email protected] 

    v2 = pdecIn->scale; 
    if (v2 > 0x1Cu || (v3 = pdecIn->sign, v3 & 0x7F)) 
    { 
    result = -2147024809; 
    } 
    else 
    { 
    if ((pdecIn->Mid32 & 0x80000000u) == 0) 
     v4 = ((double)pdecIn->Hi32 * 1.844674407370955e19 + (double)*(signed __int64 *)&pdecIn->Lo32)/sub_1006AC0D(v2); 
    else 
     v4 = ((double)*(signed __int64 *)&pdecIn->Lo32 + 1.844674407370955e19 + (double)pdecIn->Hi32 
                      * 1.844674407370955e19) 
     /sub_1006AC0D(v2); 
    if (v3) 
     v4 = -v4; 
    *(_QWORD *)pdblOut = *(_QWORD *)&v4; 
    result = 0; 
    } 
    return result; 
} 

double __fastcall sub_1006AC0D(unsigned int a1) 
{ 
    double result; // [email protected] 

    if (a1 > 0x50) 
    result = sub_1006ABD4((void *)a1, 10.0); 
    else 
    result = dbl_1002EF08[a1]; 
    return result; 
} 

double __thiscall sub_1006ABD4(void *this, double a2) 
{ 
    unsigned int v2; // [email protected] 
    double v3; // [email protected] 
    double i; // [email protected] 
    double result; // [email protected] 

    v2 = (unsigned int)this; 
    if ((signed int)this < 0) 
    v2 = -(signed int)this; 
    v3 = 1.0; 
    for (i = a2; ; i = i * i) 
    { 
    if (v2 & 1) 
     v3 = v3 * i; 
    v2 >>= 1; 
    if (!v2) 
     break; 
    } 
    if ((signed int)this >= 0) 
    result = v3; 
    else 
    result = 1.0/v3; 
    return result; 
} 

.text:1002EF08 ; double dbl_1002EF08[] 
.text:1002EF08 dbl_1002EF08 dq 1.0     ; DATA XREF: VarDecFromR4:loc_1002F23Ar 
.text:1002EF08           ; VarDecFromR8:loc_1002F4BAr ... 
.text:1002EF10     dd 0 
.text:1002EF14     dd 40240000h, 0 
.text:1002EF1C     dd 40590000h, 0 
.text:1002EF24     dd 408F4000h, 0 
.text:1002EF2C     dd 40C38800h, 0 
.text:1002EF34     dd 40F86A00h, 0 
.text:1002EF3C     dd 412E8480h, 0 
.text:1002EF44     dd 416312D0h, 0 
.text:1002EF4C     dd 4197D784h, 0 
.text:1002EF54     dd 41CDCD65h, 20000000h, 4202A05Fh, 0E8000000h, 42374876h 
.text:1002EF54     dd 0A2000000h, 426D1A94h, 0E5400000h, 42A2309Ch, 1E900000h 
.text:1002EF54     dd 42D6BCC4h, 26340000h, 430C6BF5h, 37E08000h, 4341C379h 
.text:1002EF54     dd 85D8A000h, 43763457h, 674EC800h, 43ABC16Dh, 60913D00h 
.text:1002EF54     dd 43E158E4h, 78B58C40h, 4415AF1Dh, 0D6E2EF50h, 444B1AE4h 
.text:1002EF54     dd 64DD592h, 4480F0CFh, 0C7E14AF6h, 44B52D02h, 79D99DB4h 
.text:1002EF54     dd 44EA7843h, 2C280291h, 45208B2Ah, 0B7320335h, 4554ADF4h 
.text:1002EF54     dd 0E4FE8402h, 4589D971h, 2F1F1281h, 45C027E7h, 0FAE6D721h 
.text:1002EF54     dd 45F431E0h, 39A08CEAh, 46293E59h, 8808B024h, 465F8DEFh 
.text:1002EF54     dd 0B5056E17h, 4693B8B5h, 2246C99Ch, 46C8A6E3h, 0EAD87C03h 
.text:1002EF54     dd 46FED09Bh, 72C74D82h, 47334261h, 0CF7920E3h, 476812F9h 
.text:1002EF54     dd 4357691Bh, 479E17B8h, 2A16A1B1h, 47D2CED3h, 0F49C4A1Dh 
.text:1002EF54     dd 48078287h, 0F1C35CA5h, 483D6329h, 371A19E7h, 48725DFAh 
.text:1002EF54     dd 0C4E0A061h, 48A6F578h, 0F618C879h, 48DCB2D6h, 59CF7D4Ch 
.text:1002EF54     dd 4911EFC6h, 0F0435C9Eh, 49466BB7h, 0EC5433C6h, 497C06A5h 
.text:1002EF54     dd 0B3B4A05Ch, 49B18427h, 0A0A1C873h, 49E5E531h, 8CA3A8Fh 
.text:1002EF54     dd 4A1B5E7Eh, 0C57E649Ah, 4A511B0Eh, 76DDFDC0h, 4A8561D2h 
.text:1002EF54     dd 14957D30h, 4ABABA47h, 6CDD6E3Eh, 4AF0B46Ch, 8814C9CEh 
.text:1002EF54     dd 4B24E187h, 6A19FC41h, 4B5A19E9h, 0E2503DA9h, 4B905031h 
.text:1002EF54     dd 5AE44D13h, 4BC4643Eh, 0F19D6057h, 4BF97D4Dh, 6E04B86Dh 
.text:1002EF54     dd 4C2FDCA1h, 0E4C2F344h, 4C63E9E4h, 1DF3B015h, 4C98E45Eh 
.text:1002EF54     dd 0A5709C1Bh, 4CCF1D75h, 87666191h, 4D037269h, 0E93FF9F5h 
.text:1002EF54     dd 4D384F03h, 0E38FF872h, 4D6E62C4h, 0E39FB47h, 4DA2FDBBh 
.text:1002EF54     dd 0D1C87A19h, 4DD7BD29h, 463A989Fh, 4E0DAC74h, 0ABE49F64h 
.text:1002EF54     dd 4E428BC8h, 0D6DDC73Dh, 4E772EBAh, 8C95390Ch, 4EACFA69h 
.text:1002EF54     dd 0F7DD43A7h, 4EE21C81h, 75D49491h, 4F16A3A2h, 1349B9B5h 
.text:1002EF54     dd 4F4C4C8Bh, 0EC0E1411h, 4F81AFD6h