C# 双精度到十进制,15位后不四舍五入

C# 双精度到十进制,15位后不四舍五入,c#,double,decimal,precision,C#,Double,Decimal,Precision,将“高”精度双精度转换为十进制时,由于舍入,我使用Convert.ToDecimal或casting to(Decimal)会丢失精度 例如: double d = -0.99999999999999956d; decimal result = Convert.ToDecimal(d); // Result = -1 decimal result = (Decimal)(d); // Result = -1 Convert.ToDecimal(双精度)返回的十进制值最多包含15位有效数字。如果

将“高”精度双精度转换为十进制时,由于舍入,我使用Convert.ToDecimal或casting to(Decimal)会丢失精度

例如:

double d = -0.99999999999999956d;
decimal result = Convert.ToDecimal(d); // Result = -1
decimal result = (Decimal)(d); // Result = -1
Convert.ToDecimal(双精度)返回的十进制值最多包含15位有效数字。如果值参数包含的有效数字超过15位,则使用舍入到最接近值对其进行舍入

因此,为了保持精度,我必须将double转换为字符串,然后调用convert.ToDecimal(字符串):


此方法可行,但我希望避免使用字符串变量将双精度转换为十进制,而不在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;
十进制t=0M;
bool b=d<0;
如果(b)d=-d;
如果(d>=0.5){d-=0.5;t=0.5M;}
如果(d>=0.25){d-=0.25;t+=0.25M;}
如果(d>=0.125){d-=0.125;t+=0.125M;}
如果(d>=0.0625){d-=0.0625;t+=0.0625M;}
t+=转换为特定值(d);
如果(b)t=-t;

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

这比从
double
到string的转换更便宜,并且在结果中提供了几个额外的精度数字(以上四个if-then ELSE的精度为四位)

备注:如果C#不允许自己以更高的精度进行浮点计算,一个很好的技巧是使用Dekker拆分将
d
拆分为两个值
d1
d2
,将每个值精确转换为十进制。唉,Dekker分裂只能在严格解释IEEE 754乘法和加法的情况下工作



另一个想法是使用C#的版本来获取
d
的有效位
s
和指数
e
,并计算
(十进制)(长)(s*4503599627370496.0d))*
有两种方法,其中一种方法适用于小于2^63的值,另一个将用于大于2^53的值

将较小的值拆分为整数和小数部分。整数部分可以精确地转换为
long
,然后再转换为
Decimal
[注意,直接转换为
Decimal
可能不精确!]小数部分可以精确地乘以9007199254740992.0(2^53),转换为
long
,然后再转换为
Decimal
,然后除以9007199254740992.0m。将该除法的结果与整数部分相加,应产生一个
十进制
值,该值在正确的最低有效位范围内[它可能无法精确四舍五入,但仍比内置转换要好得多!]


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

您是否提前知道
d
的范围?如果你知道
d
在[-2..2]d这样的范围内,我可以用伪代码提供一些轻量级的解决方案(我对C不太熟悉,无法用C#编写它们),我假设这是一个来自其他地方的
double
,你不能更改吗?如果它被声明为get go中的十进制(
decimal d=-0.99999999999956m;
),它将保持该精度。你说得对,克里斯,我无法更改双精度值,因为它是通过另一个方法返回的,我无法更改双精度值“此方法返回的十进制值最多包含15位有效数字。如果值参数包含的有效数字超过15位,则使用舍入到最接近值对其进行舍入。“我尝试了你的代码示例,但得到-1。你能复制一下吗?@StevenMuhr抱歉,我没有C#或任何可以运行.NET代码的平台,但我会尝试使用ideone.com,然后返回一些有用的东西。@StevenMuhr方法1,结果:0.9999999996。您需要为
d
的符号添加处理。工作顺利。比使用字符串快3倍。但这并不是消极的values@StevenMuhr我已经添加了对
d
负值的处理,但是我们两个,你不应该是C#程序员吗?
    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;