Double to Decimal, 15位后不舍入

本文关键字:不舍 舍入 15位 to Decimal Double | 更新日期: 2023-09-27 18:11:29

当将"高"精度的Double类型转换为Decimal类型时,使用Convert会失去精度。ToDecimal或由于舍入而转换为(Decimal)。

的例子:

double d = -0.99999999999999956d;
decimal result = Convert.ToDecimal(d); // Result = -1
decimal result = (Decimal)(d); // Result = -1

Convert.ToDecimal(double)返回的十进制值最多包含15位有效数字。如果value参数包含超过15位有效数字,则使用舍入方法将其舍入到最接近的值。

为了保持精度,我必须将双精度类型转换为String类型然后调用convert。todecimal (String):

decimal result = System.Convert.ToDecimal(d.ToString("G20")); // Result = -0.99999999999999956d

这个方法是有效的,但我想避免使用字符串变量,以便将双精度转换为十进制,而不舍入15位后?

Double to Decimal, 15位后不舍入

一种可能的解决方案是将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;

在ideone.com上测试一下。

注意d -=的运算是精确的,即使c#以比double更高的精度计算二进制浮点运算(它允许自己这样做)。

这比从double转换为string更便宜,并且在结果中提供了一些额外的精度数字(上面四个if-then-else的精度为4位)。

注意:如果c#不允许自己进行更高精度的浮点计算,一个好的技巧是使用Dekker拆分将d拆分为两个值d1d2,将每个值精确地转换为小数。唉,Dekker分裂只适用于IEEE 754乘法和加法的严格解释。

另一种思路是使用c#版本的frexp获取d的有效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),再转换为longDecimal,然后除以9007199254740992.0m。将除法的结果添加到整数部分应该产生一个Decimal值,该值在正确的最小有效数字范围内[它可能不是精确的四舍五入,但仍然比内置转换好得多!)

对于较大的值,乘以(1.0/281474976710656.0)(2^-48),取该结果的整数部分,再乘以281474976710656.0,然后从原始结果中减去它。将除法和减法的整数结果转换为Decimal(要精确转换),将前者乘以281474976710656m,将后者相加。