简体   繁体   English

C#双精度到十进制精度损失

[英]C# double to decimal precision loss

I have a double "138630.78380386264" and I want to convert it to a decimal, however when I do so I do it either by casting or by using Convert.ToDecimal() and I lose precision.我有一个双"138630.78380386264" ,我想将它转换为十进制,但是当我这样做时,我通过强制转换或使用Convert.ToDecimal() ,但我失去了精度。

What's going on?这是怎么回事? Both decimal and double can hold this number: decimal 和 double 都可以保存这个数字:

在此处输入图片说明

double doub = double.Parse("138630.78380386264");
decimal dec = decimal.Parse("138630.78380386264");
string decs = dec.ToString("F17");
string doubse =DoubleConverter.ToExactString(doub);
string doubs = doub.ToString("F17");

decimal decC = (decimal) doub;
string doudeccs = decC.ToString("F17");
decimal decConv = Convert.ToDecimal(doub);
string doudecs = decConv.ToString("F17");

Also: how can I get the ToString() on double to print out the same result as the debugger shows?另外:我怎样才能让ToString()在 double 上打印出与调试器显示的结果相同的结果? eg 138630.78380386264 ?例如138630.78380386264

138630.78380386264 is not exactly representable to double precision. 138630.78380386264不能完全表示为双精度。 The closest double precision number (as found here ) is 138630.783803862635977566242218017578125 , which agrees with your findings.最接近的双精度数(在此处找到)是138630.783803862635977566242218017578125 ,这与您的发现一致。

You ask why the conversion to decimal does not contain more precision.你问为什么转换为十进制不包含更多的精度。 The documentation for Convert.ToDecimal() has the answer: Convert.ToDecimal()文档有答案:

The Decimal value returned by this method contains a maximum of 15 significant digits.此方法返回的 Decimal 值最多包含 15 个有效数字。 If the value parameter contains more than 15 significant digits, it is rounded using rounding to nearest.如果 value 参数包含超过 15 个有效数字,则使用四舍五入到最接近的值进行四舍五入。 The following example illustrates how the Convert.ToDecimal(Double) method uses rounding to nearest to return a Decimal value with 15 significant digits.下面的示例说明 Convert.ToDecimal(Double) 方法如何使用舍入到最接近的值来返回具有 15 个有效数字的 Decimal 值。

The double value, rounded to nearest at 15 significant figures is 138630.783803863 , exactly as you show above.138630.783803863值(四舍五入到最接近的 15 个有效数字)是138630.783803863 ,与上面显示的完全一样。

It is a unfortunate, I think.这是不幸的,我想。 Near 139,000, a Decimal has far better precision than a Double .在 139,000 附近, DecimalDouble具有更好的精度。 But still, because of this issue, we have different Double s being projected onto the same Decimal .但是,由于这个问题,我们将不同的Double投影到同一个Decimal For example例如

double doub1 = 138630.7838038626;
double doub2 = 138630.7838038628;
Console.WriteLine(doub1 < doub2);                    // true, values differ as doubles
Console.WriteLine((decimal)doub1 < (decimal)doub2);  // false, values projected onto same decimal

In fact there are six different representable Double values between doub1 and doub2 above, so they are not the same.实际上上面的doub1doub2之间六个不同的可表示的Double值,所以它们是不一样的。

Here is a somewhat silly work-aronud:这是一个有点愚蠢的工作:

static decimal PreciseConvert(double doub)
{
  // Handle infinities and NaN-s first (throw exception)
  // Otherwise:
  return Decimal.Parse(doub.ToString("R"), NumberStyles.AllowExponent | NumberStyles.AllowDecimalPoint);
}

The "R" format string ensures that enough extra figures are included to make the mapping injective (in the domain where Decimal has superior precision). "R"格式字符串确保包含足够的额外数字以使映射成为单射(在Decimal具有较高精度的域中)。


Note that in some range, a long ( Int64 ) has a precision that is superior to that of Double .请注意,在某些范围内, long ( Int64 ) 的精度优于Double的精度。 So I checked if conversions here are made in the same way (first rounding to 15 significant decimal places).所以我检查了这里的转换是否以相同的方式进行(首先四舍五入到 15 位有效小数位)。 They are not!他们不是! So:所以:

double doub3 = 1.386307838038626e18;
double doub4 = 1.386307838038628e18;

Console.WriteLine(doub3 < doub4);              // true, values differ as doubles
Console.WriteLine((long)doub3 < (long)doub4);  // true, full precision of double used when converting to long

It seems inconsistent to use a different "rule" when the target is decimal .当目标是decimal时,使用不同的“规则”似乎不一致。

Note that, near this value 1.4e18 , because of this, (decimal)(long)doub3 produces a more accurate result than just (decimal)doub3 .请注意,在此值1.4e18附近,因此, (decimal)(long)doub3产生的结果比仅(decimal)doub3更准确。

The answers here provided you insight in the why of the question, but (even though this is many years after you asked it), here's a method to convert a double to a decimal without rounding to 15 digits.此处的答案让您深入了解了问题的原因,但是(尽管这是在您提出问题多年之后),这里有一种将double decimal转换为decimal而不舍入为 15 位数的方法。 It requires a bit of bit twiddling, though.不过,它需要一点点摆弄。

Other than casting, which throws an OverflowException if the value doesn't fit, or is NaN , INF or -INF , this method gives false in the success parameter if conversion is not possible.除了强制转换(如果值不合适或为NaNINF-INF抛出OverflowException如果无法转换,此方法会在success参数中给出false Since decimal does not support negative zero, but double does, I've ignored that and just return zero.由于decimal不支持负零,但double支持,我忽略了这一点,只返回零。

The following code is not entirely mine, but I couldn't find the original post to give credits to, sorry.以下代码不完全是我的,但我找不到原帖来表示感谢,抱歉。 Possibly it was taken from Jon Skeet's code of ToExactString , and adopted for binary exact conversion.可能它取自Jon Skeet 的ToExactString代码,并用于二进制精确转换。

const decimal DecimalEpsilon = 0.0000000000000000000000000001M;
public static decimal TryToDecimalWithInsignificand(double dbl, out bool succeeded)
{
    if (double.IsPositiveInfinity(dbl))
    {
        succeeded = false;
        return decimal.MaxValue;
    }

    if (double.IsNegativeInfinity(dbl))
    {
        succeeded = false;
        return decimal.MinValue;
    }

    if (double.IsNaN(dbl))
    {
        succeeded = false;
        return 0M;
    }

    if (dbl > (double)decimal.MaxValue)
    {
        succeeded = false;
        return decimal.MaxValue;
    }

    if (dbl < (double)decimal.MinValue)
    {
        succeeded = false;
        return decimal.MinValue;
    }

    if (dbl > 0.0 && dbl <= (double)DecimalEpsilon)
    {
        succeeded = false;
        return 0M;
    }

    if (dbl < 0.0 && dbl >= (double)-DecimalEpsilon)
    {
        succeeded = false;
        return 0M;
    }

    // start conversion
    long bits = BitConverter.DoubleToInt64Bits(dbl);
    long mantissa = bits & 0xFFFFFFFFFFFFFL;        // 52 bits
    long exponent = ((bits >> 52) & 0x7FFL) - 1023L;    // next 11 bits
    bool negative = bits < 0;
    decimal fraction = mantissa / (decimal)0x10000000000000L;
    decimal result;

    if (exponent < 0)
    {
        long div = 1 << (int)-exponent;
        result = (fraction + 1) / div;
    }
    else
    {
        long mul = 1L << (int)exponent;
        result = (fraction + 1) * mul;
    }
    succeeded = true;
    if (negative)
    {
        return -1 * result;
    }
    return result;
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM