如何舍入双精度数字?

How to round the precision digits for double?

我在 C# 中使用 double。 Double 的精度约为 15-17 位。

在我的程序中,用户输入:0.011 - 0.001,结果显示 0.0099999999999999985 --> 这在用户眼中很难看 - 他们会质疑我的数学能力。

我对内部结果为 0.0099999999999999985 没问题,但是在显示时,我想找到一种方法来修复精度数字。例如:

double a = 0.011;
double b = 0.001;
double result, display;

result = a - b;//result = 0.0099999999999999985

display = ConvertForDisplay(result);//I want display = 0.01

我看到三星智能手机(例如 Galaxy Tab SM-T295)上的内置 Android 应用程序“计算器”也使用 double(因为我输入 10^309 并给出错误) 但是当我输入数学 0.011 - 0.001 时,它正确地显示 0.01.

我想到了Math.Round (double value, int digits),但是digits必须在[0,15]范围内,同时我还需要显示1E-200这样的数字。

我在互联网上搜索过,但我只是看到有关如何处理 floating point accuracy problems 的问题(例如,不要使用 ==,而是使用 Math.Abs(a-b) < double.EpsilonMath.Round,...)。但我找不到如何舍入精度数字(例如,值 0.0099999999999999985 具有精度数字 99999999999999985,当这些数字包含超过 15 个“9”时,则应舍入显示)。

那么解决这个问题的正确方法是什么(例如将 0.0099999999999999985 转换为 0.01)?

额外:当我输入 9999999999999999d 时,C# 给我 1E+16,我知道这是因为 C# 看到它可以容纳的所有精度数字都是 9,所以它四舍五入到 10,但有什么方法可以让它保留所有 9 - 我看到我上面提到的 Android 应用程序“计算器”也有这种行为,所以我认为这是额外的 - 不是必须修复的, 但我想知道它是否可以修复只是为了好玩。

更新:

我看到了行为:(1.021 - 1.01) = 0.010999999999999899

但是 (1.021 - 1.01) + 1 = 1.011

所以当我加 1 时,double 会触发它的“内部舍入”功能(我猜),它舍入到我想要的数字。也许这会导致我的解决方案?

还有一个有趣的发现:将值转换为 float 也可以给出我想要的数字,例如:(float)(1.021 - 1.01) = 0.011

我重新考虑这个问题,我用小数作为工具来解决这个问题如何。这个想法是使用小数来解决数学问题(如果范围适合小数),然后将小数转换回双精度。这是代码:

const int LEFT = 0;
const int RIGHT = 1;

private static bool IsConvertableToDecimal(double value)
{
    const double MAX = (double)decimal.MaxValue;
    const double MIN = (double)decimal.MinValue;
    const double EXP_MIN = -1E-28;
    const double EXP_MAX = 1E-28;

    return
        (value >= EXP_MAX && value < MAX) ||
        value == 0 ||
        (value > MIN && value <= EXP_MIN);
}

private static double BasicFunc(double[] arr,
    Func<double, double, double> funcDouble,
    Func<decimal, decimal, decimal> funcDecima)
{
    var result = funcDouble(arr[LEFT], arr[RIGHT]);

    //Check if can convert to decimal for better accurancy
    if (IsConvertableToDecimal(result) &&
        IsConvertableToDecimal(arr[LEFT]) &&
        IsConvertableToDecimal(arr[RIGHT]))
        result = (double)(funcDecima((decimal)arr[LEFT], (decimal)arr[RIGHT]));

    return result;
}

public static double ADD(double[] arr)
    => BasicFunc(arr, (x, y) => x + y, decimal.Add);
public static double SUBTRACT(double[] arr)
    => BasicFunc(arr, (x, y) => x - y, decimal.Subtract);
public static double MULT(double[] arr)
    => BasicFunc(arr, (x, y) => x * y, decimal.Multiply);
public static double DIV(double[] arr)
    => BasicFunc(arr, (x, y) => x / y, decimal.Divide);
    
//Usage ================
void Test()
{
var arr = new double[2];
arr[0] = 0.011;
arr[1] = 0.001;
var result = SUBTRACT(arr);
Print(result);//Result = 0.01 ==> Success!!!
}