如何舍入双精度数字?
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.Epsilon
、Math.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!!!
}
我在 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.Epsilon
、Math.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!!!
}