双精度到十进制,15位后没有舍入

时间:2014-07-05 13:37:06

标签: c# double decimal precision

将“高”精度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 个答案:

答案 0 :(得分:4)

一种可能的解决方案是将d分解为n个双精度的精确总和,其中最后一个很小并包含转换为十进制时所需的所有尾随有效数字,以及第一个(n-1) )其中完全转换为十进制。

对于介于-1.0和1.0之间的源双d

    decimal t = 0M;
    bool b = d < 0;
    if (b) d = -d;
    if (d >= 0.5) { d -= 0.5; t = 0.5M; }
    if (d >= 0.25) { d -= 0.25; t += 0.25M; }
    if (d >= 0.125) { d -= 0.125; t += 0.125M; }
    if (d >= 0.0625) { d -= 0.0625; t += 0.0625M; }
    t += Convert.ToDecimal(d);
    if (b) t = -t;

Test it on ideone.com.

请注意,操作d -=是准确的,即使C#计算二进制浮点运算的精度高于double(它允许自己这样做)。

这比从double到字符串的转换便宜,并且在结果中提供了一些额外的精度数字(上述四个if-then-elses的四位精度)。

备注:如果C#不允许自己以更高的精度进行浮点计算,那么一个好的技巧就是使用Dekker拆分将d分成两个值d1和{{ 1}}将每个精确转换为十进制。唉,Dekker分裂仅适用于对IEEE 754乘法和加法的严格解释。


另一个想法是使用C#的frexp版本来获取d2的有效数s和指数e,并计算d

答案 1 :(得分:1)

有两种方法,其中一种适用于低于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,然后加上后者。