Double to Decimal without rounding after 15 digits
当将"高"精度的双精度转换为十进制时,由于舍入,使用convert.to decimal或casting to(decimal)会丢失精度。
例子:
1 2 3
| double d = -0.99999999999999956d;
decimal result = Convert.ToDecimal(d); // Result = -1
decimal result = (Decimal)(d); // Result = -1 |
convert.todecimal(double)返回的十进制值最多包含15个有效数字。如果值参数包含超过15个有效数字,则使用四舍五入法将其四舍五入到最近的数字。
因此,为了保持精度,我必须将double转换为一个字符串,然后调用convert.todecimal(string):
1
| decimal result = System.Convert.ToDecimal(d.ToString("G20")); // Result = -0.99999999999999956d |
这个方法是可行的,但我想避免使用字符串变量,以便将双精度转换为十进制,而不在15位之后舍入?
- 你事先知道d的范围吗?如果你知道d在一个范围内,比如[-2..2],我可以用伪代码提供一些轻量级的解决方案(我对c还不够熟悉,无法用c编写它们)。
- d将始终介于[-1,1]之间
- 我假设这是一个来自其他地方的double,你不能改变?如果它被声明为GET GO(decimal d = -0.99999999999999956m;中的十进制数),它将保持这个精度。
- 你说得对,Chris,我不能更改double值,因为它是由另一个方法返回的,我不能更改。
- 最接近-0.9999999999956的双精度数是-0.999999999959555910790149937383054732763671875,十进制类型应该能够与-1区分开来。
- 如果您试图将double转换为十进制,您将得到一个舍入值。试试我的第一个例子。
- @PatriciaShanahan这只是我的猜测,但我完全可以想象一种来自微软的编程语言,它指定从double到该语言的十进制类型的转换产生正好15个有效的十进制数字,并将其余的数字设置为0。
- Convert.ToDecimal的msdn帮助中说,"此方法返回的十进制值最多包含15个有效数字。如果值参数包含超过15个有效数字,则使用四舍五入法将其四舍五入到最接近的数值。"
- 查看此msdn页的备注:msdn.microsoft.com/en-us/library/a69w9ca0.aspx
一种可能的解决方案是将d分解为n个双精度数的精确和,其中最后一个小且包含转换为十进制时所需的所有尾随有效数字,而第一个(n-1)则精确转换为十进制。
对于-1.0和1.0之间的源双d:
1 2 3 4 5 6 7 8 9
| 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上测试。
注意,即使C以比double更高的精度计算二进制浮点运算(它允许自己这样做),d -=的运算也是精确的。
这比从double到string的转换便宜,并且在结果中提供了一些额外的精度数字(上述四个if的精度为4位,则为else)。
备注:如果C_不允许自己以更高的精度进行浮点计算,一个好的技巧就是使用Dekker拆分将d拆分为两个值d1和d2,这两个值将每个值精确转换为十进制。唉,Dekker拆分只适用于对IEEE754乘法和加法的严格解释。
另一种方法是使用c版本的frexp来获得d的意义s和指数e,并计算(Decimal)((long) (s * 4503599627370496.0d)) * 。
- 我试过你的代码样本,但我得到-1。你能在你这边复制吗?
- @Stevenmuhr很抱歉,我没有C,甚至没有任何可以运行.NET代码的平台,但我会尝试使用ideone.com,然后回来提供一些有用的东西。
- @stevenmuhr方法1,结果:0.999999999996。您需要增加对d符号的处理。ideone.com/mevinx公司
- 工作很好。比使用字符串快3倍。但它不适用于负值
- @Stevenmuhr我建议使用这个来转换绝对值,然后如果输入为负数,就否定结果。
- @Stevenmuhr我增加了对d负值的处理,但是我们两个,你不应该是C程序员吗?
- + 1。如你所说,我的答案对这个范围内的其他值无效,所以我删除了它。
- @Matthewstrawbridge是的,如果知道d接近-1或1,则更简单。希望运营商会说如果是这样的话(然后你的解决方案更快更准确)。
有两种方法,一种适用于小于2^63的值,另一种适用于大于2^53的值。
将较小的值拆分为整数和小数部分。整数部分可以精确地转换成long,然后转换成Decimal[注意,直接转换成Decimal可能不精确!]小数部分可以精确乘以9007199254740992.0(2^53),转换为long,然后再转换为Decimal,然后除以9007199254740992.0m。将该除法的结果加在整数部分后,会得到一个Decimal值,该值在正确的最小有效位内[可能不是pr特别是四舍五入,但仍将远远优于内置转换!]
对于较大的值,乘以(1.0/2814749776710656.0)(2^-48),取结果的整数部分,再乘以2814749776710656.0,然后从原始结果中减去。将除法和减法所得的整数结果转换为Decimal(应精确转换),将前者乘以2814749776710656M,再加上后者。