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

前端 未结 5 1820
我寻月下人不归
我寻月下人不归 2020-11-30 00:44

Summary of the problem:

For some decimal values, when we convert the type from decimal to double, a small fraction is added to the result.

What makes it wors

5条回答
  •  臣服心动
    2020-11-30 01:13

    The answer lies in the fact that decimal attempts to preserve the number of significant digits. Thus, 8224055000.0000000000m has 20 significant digits and is stored as 82240550000000000000E-10, while 8224055000m has only 10 and is stored as 8224055000E+0. double's mantissa is (logically) 53 bits, i.e. at most 16 decimal digits. This is exactly the precision you get when you convert to double, and indeed the stray 1 in your example is in the 16th decimal place. The conversion isn't 1-to-1 because double uses base 2.

    Here are the binary representations of your numbers:

    dcm:
    00000000000010100000000000000000 00000000000000000000000000000100
    01110101010100010010000001111110 11110010110000000110000000000000
    dbl:
    0.10000011111.1110101000110001000111101101100000000000000000000001
    dcm2:
    00000000000000000000000000000000 00000000000000000000000000000000
    00000000000000000000000000000001 11101010001100010001111011011000
    dbl2 (8224055000.0):
    0.10000011111.1110101000110001000111101101100000000000000000000000
    

    For double, I used dots to delimit sign, exponent and mantissa fields; for decimal, see MSDN on decimal.GetBits, but essentially the last 96 bits are the mantissa. Note how the mantissa bits of dcm2 and the most significant bits of dbl2 coincide exactly (don't forget about the implicit 1 bit in double's mantissa), and in fact these bits represent 8224055000. The mantissa bits of dbl are the same as in dcm2 and dbl2 but for the nasty 1 in the least significant bit. The exponent of dcm is 10, and the mantissa is 82240550000000000000.

    Update II: It is actually very easy to lop off trailing zeros.

    // There are 28 trailing zeros in this constant —
    // no decimal can have more than 28 trailing zeros
    const decimal PreciseOne = 1.000000000000000000000000000000000000000000000000m ;
    
    // decimal.ToString() faithfully prints trailing zeroes
    Assert ((8224055000.000000000m).ToString () == "8224055000.000000000") ;
    
    // Let System.Decimal.Divide() do all the work
    Assert ((8224055000.000000000m / PreciseOne).ToString () == "8224055000") ;
    Assert ((8224055000.000010000m / PreciseOne).ToString () == "8224055000.00001") ;
    

提交回复
热议问题