[英]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 附近,
Decimal
比Double
具有更好的精度。 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.实际上上面的
doub1
和doub2
之间有六个不同的可表示的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.除了强制转换(如果值不合适或为
NaN
、 INF
或-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.