将“高”精度Double转换为Decimal时,由于Rounding,我将使用Convert.ToDecimal或转换为(Decimal)而失去精度 .
示例:
double d = -0.99999999999999956d;
decimal result = Convert.ToDecimal(d); // Result = -1
decimal result = (Decimal)(d); // Result = -1
Convert.ToDecimal(double)返回的Decimal值最多包含15位有效数字 . 如果value参数包含超过15个有效数字,则使用舍入舍入为最接近的数字 .
所以为了保持我的精度,我必须将我的double转换为String然后调用Convert.ToDecimal(String):
decimal result = System.Convert.ToDecimal(d.ToString("G20")); // Result = -0.99999999999999956d
这个方法是有效的,但是我想避免使用String变量来将Double转换为Decimal而不用15位后的舍入?
2 回答
一种可能的解决方案是将
d
分解为n个双精度的精确总和,其中最后一个很小,并且包含转换为十进制时所需的所有尾随有效数字,并且第一个(n-1)精确地转换为十进制 .对于-1.0和1.0之间的源double
d
:Test it on ideone.com.
注意,即使C#以比
double
(它允许自己做的那样)更高的精度计算二进制浮点运算,操作d -=
也是准确的 .这比从
double
到字符串的转换便宜,并且在结果中提供了一些额外的精度数字(对于上述四个if-then-elses,精度为4位) .备注:如果C#不允许自己以更高的精度进行浮点计算,那么一个好的技巧就是使用Dekker拆分将
d
拆分为两个值d1
和d2
,它们将精确地转换为十进制 . 唉,Dekker分裂仅适用于对IEEE 754乘法和加法的严格解释 .另一个想法是使用C#的frexp版本来获取
s
的有效数s
和指数e
,并计算(Decimal)((long) (s * 4503599627370496.0d)) * <however one computes 2^e in Decimal>
.有两种方法,其中一种方法适用于低于2 ^ 63的值,另一种方法适用于大于2 ^ 53的值 .
将较小的值拆分为整数和小数部分 . 整数部分可以精确地转换为
long
然后Decimal
[注意直接转换为Decimal
可能不准确!]小数部分可以精确乘以9007199254740992.0(2 ^ 53),转换为long
然后Decimal
,然后除以9007199254740992.0m . 将该除法的结果添加到整数部分应该产生Decimal
值,该值在正确的一个最低位数内[它可能不是精确舍入的,但仍然比内置转换好得多! ]对于较大的值,乘以(1.0 / 281474976710656.0)(2 ^ -48),取该结果的整数部分,再乘以281474976710656.0,并从原始结果中减去它 . 将除数和减法的整数结果转换为
Decimal
(它们应该精确转换),将前者乘以281474976710656m,然后加上后者 .