我需要把双打的有效数字四舍五入。例子圆形(1.2e-20,0)应变为1.0e-20
我不能使用Math.Round(1.2e-20,0),它返回0,因为Math.Round()不将浮点中的有效数字舍入,而是将其舍入到十进制数字,即E为0的双精度数。
当然,我可以这样做:
1 2 3 4
| double d = 1.29E-20;
d *= 1E+20;
d = Math.Round(d, 1);
d /= 1E+20; |
实际上是有效的。但这并不是:
1 2 3 4
| d = 1.29E-10;
d *= 1E+10;
d = Math.Round(d, 1);
d /= 1E+10; |
在这种情况下,d是0.0000000000130000000000002。问题是,double在内部存储2的分数,这与10的分数不完全匹配。在第一种情况下,C似乎只处理*和/的指数,但在第二种情况下,它会进行实际的*或/操作,从而导致问题。
当然,我需要一个总是给出正确结果的公式,不仅是有时。
也就是说,我不应该在舍入后使用任何双精度运算,因为双精度运算不能精确地处理小数。
上述计算的另一个问题是,没有返回双精度数指数的双精度函数。当然,可以使用数学库来计算它,但是很难保证它始终与双内部代码具有完全相同的结果。
在绝望中,我考虑将一个双精度数转换成一个字符串,找到有效数字,进行四舍五入,然后将四舍五入的数字转换成一个字符串,最后将这个数字转换成双精度数。丑陋,对吧?在所有情况下也可能无法正常工作:-(
是否有任何库或建议如何正确舍入双精度数的有效位数?
附:在声明这是一个重复的问题之前,请确保您理解有效数字和小数点之间的区别。
- "2的分数"是指"2的系数"吗?
- 不用double,用Decimal怎么样?
- 十进制速度明显较慢,但更准确。还取决于您需要的值的范围。
- 把这个问题标记为重复的人,他们能提供一个链接到他们认为相同的问题吗?我在stackoverflow上做了数百个舍入问题。我找不到一个问我问题的人,很高兴能找到一个链接。
- 我不能用小数,因为我用双数写图表。它覆盖了整个双范围。它还支持缩放和滚动,如果舍入出错,将导致标签和网格线出现各种可见问题。
- @科赫博士:请提供一个链接,链接到你觉得和这里一样的问题。
- @Kenorb:请提供一个链接,链接到您认为与此处相同的问题。
- @Elmovankilmo:请提供一个链接,链接到您认为与此处相同的问题。
- @Winwaed:请提供一个链接,链接到您认为与此处相同的问题。
- @Chridam:请提供一个链接,链接到您认为与此处相同的问题。很抱歉有这么多评论。我浏览了每个人的用户页面,但不知道如何向他们发送消息。评论一次只允许对一个人讲话,所以我必须做5条评论:-(
- 对于我的问题,"把一个双到X的有效数字四舍五入"没有合适的解决办法。正如我在问题中所展示的,四舍五入然后使用任何双操作都不起作用,正如这些答案中所述。还有一个答案建议使用十进制。那张海报强调了他的解决方案在整个双值范围内都不起作用…
- 唯一的方法是,你的问题中有一个以上的问题,不一定要解决所有的问题。但它肯定以尽可能最好的方式处理您的请求,使其四舍五入到n个有效数字。就图表的需要而言,如果您有由于舍入而可见的工件,那么您的代码可能还有其他问题。你应该问一个具体的问题。最后,Decimal比double的精度高,因此您使用它的顾虑没有意义。
- @彼得尼奥:十进制的值范围是7.9*(28的10次方)。double的值范围是1.7*(308的10次方)。显然,用小数覆盖双精度数的值范围是不可能的。如果您不相信,请尝试分配double.maxvalue,您将得到一个异常:"值对于小数来说要么太大要么太小。"有意义吗?
- 是的,但是您不能缩放输入值以适应Decimal范围吗?正如我提到的,Decimal具有更高的精度,因此您不应该在转换中丢失任何重要的内容。也就是说,正如我所提到的,由于double类型的限制,您没有理由遇到可视工件,而且在任何情况下,进行更多的取整肯定不会解决这个问题。如果你在让double在图表场景中工作时遇到问题,你最好是集中精力问问题,而不是问不可能的问题。
- @彼得尼奥:我只有一个问题:如何正确地进行有效数字的双精度取整。然后我在我的问题中展示了将double转换成另一个指数为0的double的方法,使用math.round()进行舍入,然后再转换回,这是不起作用的,提供了一个数值示例。
- 问题很简单。要添加网格线,我必须计算它们的值,比如gridvalue(n)=startvalue+(n*stepvalue),其中n是第n个网格线。示例:网格值(n)=1.23e+128+(n*1.0e+127)。然后我需要比较网格线是否在值范围内,比如1.23e+128…1.27 e+ 128。问题是1.23e+128+(40*1.0e+127)在双算术中可能不等于1.27e+128,这取决于数字。所以我需要对结果进行四舍五入,然后将和与结束值进行比较,以确定是否应该显示最后一条网格线。当然,缩放和分页也面临同样的问题。
- Translating doubles some how to Decima and then translating back might face the same problems like multilying/diversing doubles to use math.round(),because the limited Decima range need to be extended using double arithmetic,which has the problems I demonstrated.它的定义不是微不足道的。It would be interesting to see some working code.Thanks for your thoughts,though.
- @Dan i am not sure if the expression"fractions of 2"is correct,but what I mean is:1/2,1/(2*2),1/(2*2*2*2*2*2*2*
- "How to get significant digits rounding of double done properly"-Your only hope is to define"properly"in a way that does not ask for the impossible.So far,as has been explained multiple times in multiple ways by multiple people,you are asking for the impossible.The EDOCX1 nipal 0 type simply is not able of producing the result you insist on.你可以是你的电脑,飞向月亮。
- 如果微软可以为双用途进行十进制点Rounding Properly,我认为这对国家来说是危险的,因为它具有重要意义,不能说已经走了。By proper,I mean that round(A.BCE-DE,1)-A.be-de=-0.A,B,C,D,E are digits,for example round(1.29E-10,1)-1.2E-10=-0.I can see at least one way to achieve that:turn 1.29E-10 to a string,remove'1',turn back,which should obviously result in 1.29E-10.如果你仍然觉得这是不可能的,请解释为什么。And a bit less agressively would be appreciated too.
The problem is that double stores internally fractions of 2, which cannot match exactly fractions of 10
这是个问题,是的。如果它在您的场景中很重要,您需要使用一个以十进制而不是二进制存储数字的数字类型。在.NET中,该数字类型是decimal。
注意,对于许多计算任务(但不是货币,例如),double类型是可以的。事实上,您没有得到您要寻找的值,这与使用double时存在的任何其他舍入错误相比,都是一个问题。
还要注意,如果唯一的目的是显示数字,您甚至不需要自己进行四舍五入。您可以使用自定义数字格式来完成相同的操作。例如:
1 2
| double value = 1.29e-10d;
Console.WriteLine(value.ToString("0.0E+0")); |
显示字符串1.3E-10;
Another problem with the calculation above is that there is no double function returning the exponent of a double
我不知道你的意思。Math.Log10()方法正好做到了这一点。当然,它返回给定数字的精确指数,以10为底。根据您的需要,您实际上更喜欢Math.Floor(Math.Log10(value)),它提供了以科学记数法显示的指数值。
it might be difficult to guarantee that this has always precisely the same result as the double internal code
由于double的内部存储使用IEEE二进制格式,其中指数和尾数都存储为二进制数,因此显示的指数基数10无论如何都不会"与双内部代码完全相同"。当然,指数是一个整数,可以精确表示。但它不像一个十进制值被存储在第一位。
在任何情况下,Math.Log10()都会返回一个有用的值。
Is there any library or any suggestion how to round the significant digits of a double properly ?
如果你只是为了显示而需要取整,那就不要做任何数学运算。只需使用自定义数字格式字符串(如上所述)来按照您想要的方式格式化值。
如果你真的需要自己做四舍五入,那么根据你的描述,我认为下面的方法应该有效:
1 2 3 4 5 6 7 8 9 10
| static double RoundSignificant(double value, int digits)
{
int log10 = (int)Math.Floor(Math.Log10(value));
double exp = Math.Pow(10, log10);
value /= exp;
value = Math.Round(value, digits);
value *= exp;
return value;
} |
- 这几乎是我自己使用的代码。但是对于使用Math.Pow的double,乘以exp或除以exp通常会导致低位出现垃圾,因此您仍然需要将数字格式化为所需的有效位数。
- @kjbartel:"所以您仍然需要将数字格式化为所需的有效位数"--是的,完全正确。这就是我建议使用decimal的原因,如果"垃圾"实际上是一个问题(例如,输出由于某种原因没有被格式化)。在使用double的所有情况下,都不可能实现精确的舍入(就像在使用double的任何其他情况下都不可能始终具有精确的十进制值一样)。
- 感谢您的详细回答。不幸的是,您的最终建议与我发布的建议相同,但问题相同,即在舍入后使用双精度运算执行任何算术运算都会破坏舍入。
- 实际上,我的代码示例与您的不同,因为它实际上适用于任意输入(即计算指数)。在任何情况下,如果您坚持使用double类型,您描述的"问题"都不可能"解决"。对于double类型来说,它根本不可能表示您希望它表示的值。
- 确实,双人间不可能精确储存1.29E-10。但我要找的是一个公式,它返回的圆(1.291e-10,1)和1.29e-10的数值完全相同。@彼得尤尼奥:我加上你的回答表示感谢。