将重要数字四舍五入为双精度,而不是小数位

Pet*_*ber 0 c# double rounding

我需要舍入双打的有效数字。示例 Round(1.2E-20, 0) 应该变成 1.0E-20

我不能使用返回 0 的 Math.Round(1.2E-20, 0),因为 Math.Round() 不会将浮点数中的有效数字四舍五入,而是将其四舍五入为十进制数字,即 E 为 0 的双精度数。

当然,我可以这样做:

double d = 1.29E-20;
d *= 1E+20;
d = Math.Round(d, 1);
d /= 1E+20;
Run Code Online (Sandbox Code Playgroud)

这实际上有效。但这不会:

d = 1.29E-10;
d *= 1E+10;
d = Math.Round(d, 1);
d /= 1E+10;
Run Code Online (Sandbox Code Playgroud)

在这种情况下,d 是 0.00000000013000000000000002。问题是 double 在内部存储 2 的分数,它不能完全匹配 10 的分数。在第一种情况下,C# 似乎只处理 * 和 / 的指数,但在第二种情况下,它会生成实际的 * 或/ 操作,然后导致问题。

当然,我需要一个总能给出正确结果的公式,不仅仅是有时。

这意味着我不应该在四舍五入后使用任何双重运算,因为双重算术无法准确处理小数。

上述计算的另一个问题是没有返回 double 指数的 double 函数。当然可以使用 Math 库来计算它,但可能很难保证它始终与 double 内部代码的结果完全相同。

无奈之下,我考虑将双精度数转换为字符串,找到有效数字,进行四舍五入并将四舍五入的数字转换回字符串,然后最终将其转换为双精度数。丑吧?在所有情况下也可能无法正常工作:-(

是否有任何图书馆或任何建议如何正确舍入双精度数的有效数字?

PS:在声明这是一个重复的问题之前,请确保您了解SIGNIFICANT数字和小数位之间的区别

Pet*_*iho 5

问题是 double 在内部存储 2 的小数,它不能完全匹配 10 的小数

这是一个问题,是的。如果这在您的场景中很重要,您需要使用将数字存储为十进制而不是二进制的数字类型。在 .NET 中,该数字类型是decimal.

请注意,对于许多计算任务(但不是货币,例如),double类型很好。你不明白的事实正是你正在寻找的价值是没有更多的问题比任何时候使用存在的其它舍入误差double

另请注意,如果唯一目的是显示数字,则您甚至不需要自己进行四舍五入。您可以使用自定义数字格式来完成相同的操作。例如:

double value = 1.29e-10d;
Console.WriteLine(value.ToString("0.0E+0"));
Run Code Online (Sandbox Code Playgroud)

这将显示字符串1.3E-10

上述计算的另一个问题是没有返回 double 指数的 double 函数

我不确定你在这里的意思。该Math.Log10()方法正是这样做的。当然,它返回给定数字的确切指数,以 10 为底。根据您的需要,您实际上更喜欢Math.Floor(Math.Log10(value)),它为您提供将以科学记数法显示的指数值。

可能很难保证这总是与双重内部代码完全相同的结果

由于 a 的内部存储double使用 IEEE二进制格式,其中指数和尾数都存储为二进制数,因此显示的指数基数 10 永远不会“与双内码完全相同”。当然,指数是一个整数,可以精确表达。但这并不是首先存储十进制值。

在任何情况下,Math.Log10()都会返回一个有用的值。

是否有任何图书馆或任何建议如何正确舍入双精度数的有效数字?

如果您只是为了显示而需要舍入,则根本不要做任何数学运算。只需使用自定义数字格式字符串(如上所述)以您想要的方式格式化值。

如果您确实需要自己进行四舍五入,那么根据您的描述,我认为以下方法应该有效:

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;
}
Run Code Online (Sandbox Code Playgroud)