【问题标题】:Double to Decimal without rounding after 15 digitsDouble to Decimal 15 位数后不四舍五入
【发布时间】:2014-08-26 12:59:10
【问题描述】:

将“高”精度 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) 返回的 Decimal 值最多包含 15 个有效数字。如果 value 参数包含超过 15 个有效数字,则使用舍入到最接近的方式进行舍入。

所以为了保持精度,我必须将我的双精度转换为字符串,然后调用 Convert.ToDecimal(String):

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

此方法有效,但我想避免使用 String 变量来将 Double 转换为 Decimal 而无需在 15 位后四舍五入?

【问题讨论】:

  • 您是否事先知道d 的范围?如果您知道d 在 [-2..2] 之类的范围内,我可以用伪代码提供一些轻量级解决方案(我对 C# 不够熟悉,无法用 C# 编写它们)
  • d 总是在 [-1,1] 之间
  • 我假设这是来自其他地方的double,您无法更改?如果从一开始就将其声明为小数 (decimal d = -0.99999999999999956m;),它将保持该精度。
  • 你说得对,克里斯,我无法更改双精度值,因为它是通过另一种我无法更改的方法返回的
  • MSDN help for Convert.ToDecimal 表示“此方法返回的 Decimal 值最多包含 15 个有效数字。如果 value 参数包含超过 15 个有效数字,则使用舍入到最接近的方式进行舍入。”

标签: c# double decimal precision


【解决方案1】:

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

对于 -1.0 和 1.0 之间的源 double 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-else 的精确度为四位)。

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


另一种思路是使用C#版本的frexp获取d的有效位s和指数e,并计算(Decimal)((long) (s * 4503599627370496.0d)) * &lt;however one computes 2^e in Decimal&gt;

【讨论】:

  • 我已经尝试过您的代码示例,但我得到 -1。你能在你身边重现它吗?
  • @StevenMuhr 抱歉,我没有 C# 甚至没有任何可以运行 .NET 代码的平台,但我会尝试使用 ideone.com 并返回一些有用的东西。跨度>
  • @StevenMuhr Method1,结果:0.9999999999999996。您需要为d 的符号添加处理。 ideone.com/MevINX
  • 运作良好。比使用字符串快 3 倍。但它不适用于负值
  • @StevenMuhr 我已经添加了对 d 负值的处理,但是我们两个,你不应该是 C# 程序员吗?
【解决方案2】:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-12-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-03-15
    • 1970-01-01
    相关资源
    最近更新 更多