比较 C # 中的双精度值

我有一个叫做 xdouble变量。 在代码中,x被赋值为 0.1,我在一个比较 x0.1的“ if”语句中检查它

if (x==0.1)
{
----
}

不幸的是,它没有输入 if语句

  1. 我应该使用 Double还是 double

  2. 这背后的原因是什么? 你能提出一个解决方案吗?

131634 次浏览

由于四舍五入的原因,比较浮点数并不总是精确的

(x == .1)

计算机真的比

(x - .1) vs 0

由于浮点数在机器上的表示方式,抽取结果并不总是能够被精确地表示出来。因此,您将得到一些非零值,并且条件的计算结果为 false

为了克服这种比较

Math.Abs(x- .1) vs some very small threshold ( like 1E-9)

使用 decimal。它没有这个“问题”。

由于浮点数在内部存储的方式,浮点数的表示方式是出了名的不准确。例如,x实际上可能是 0.09999999990.100000001,你的条件将失败。如果要确定浮点数是否相等,则需要指定它们是否在一定的公差范围内相等。

例如:

if(Math.Abs(x - 0.1) < tol) {
// Do something
}

由于计算机存储浮点值的方式,这是一个标准问题。在这里搜索“浮点问题”,你会找到大量的信息。

简而言之—— float/double 不能精确地存储 0.1,它总是有点偏离。

您可以尝试使用以十进制记数法存储数字的 decimal类型。


你想知道原因:

Float/double 是以二进制小数而非十进制小数的形式存储的:

十进制记数法中的 12.34(我们使用的)表示

1 * 101 + 2 * 100 + 3 * 10-1 + 4 * 10-2

The computer stores floating point numbers in the same way, except it uses base 2: 10.01 means

1 * 21 + 0 * 20 + 0 * 2-1 + 1 * 2-2

现在,你可能知道有些数字不能完全用我们的十进制表示法来表示。例如,十进制表示法中的 1/30.3333333…。同样的事情发生在二进制记数法中,除了不能精确表示的数字是不同的。其中就有数字 1/10。以二进制表示法,即 0.000110011001100…

因为二进制符号不能精确地存储它,所以它是以四舍五入的方式存储的。

double(小写)只是 System.Double的别名,因此它们是相同的。

因此,请参阅 二进制浮点数和.NET。 简而言之: double 并不是一个精确的类型,“ x”和“0.1”之间的细微差别就会使它失效。

由于舍入和内部表示问题,浮点值的精确比较并不总是有效。

试试不精确的比较:

if (x >= 0.099 && x <= 0.101)
{
}

另一种方法是使用十进制数据类型。

doubleDouble是相同的(doubleDouble的别名) ,可以互换使用。

将双精度值与另一个值进行比较的问题是,双精度值是近似值,而不是精确值。所以当你将 x设置为 0.1时,它实际上可能被存储为 0.100000001或类似的东西。

与检查相等性不同,您应该检查差异小于定义的最小差异(公差)。比如:

if (Math.Abs(x - 0.1) < 0.0000001)
{
...
}

1)我应该使用双倍还是双倍? ? ?

Doubledouble是一回事。 double只是一个 C # 关键字,作为类 System.Double的别名 最常见的是使用别名! string(System.String)、 int(System.Int32)也是如此

也请参阅 内置类型表(C # 引用)

Double (在某些语言中称为 float)由于舍入问题而充满了问题,只有在需要近似值时才使用它。

Decimal 数据类型可以完成您想要的任务。

中的小数和小数是相同的。NET C # 中的 Double 和 Double 类型也是如此,它们都引用相同的类型(如您所见,十进制和 Double 非常不同)。

注意 Decimal 数据类型有一些与之相关的成本,所以在查看循环等时要小心使用它。

来自 文件:

比较中的精确性 应谨慎使用等于法,因为两个明显等价的值可能因为两个值的精度不同而不相等。下面的示例报告 Double 值.3333和用1除以3返回的 Double 是不等的。

...

一种推荐的技术是定义两个值之间可接受的差值(比如其中一个值的0.01%) ,而不是比较是否相等。如果两个值之间的差异的绝对值小于或等于该余量,差异可能是由于精度差异,因此,两个值可能是相等的。下面的示例使用此技术比较.33333和1/3,前面的代码示例发现这两个 Double 值不等。

因此,如果您真的需要一个 double,您应该使用文档中描述的技术。 如果可以,将它改为小数。 会慢一点,但是您不会有这种类型的问题。

一般来说:

双重表示在大多数情况下已经足够好了,但是在某些情况下可能会惨败。如果需要完全精确,可以使用十进制值(如在金融应用程序中)。

大多数关于双精度数的问题并不是直接比较出来的,它通常是几个数学运算累积的结果,由于舍入和小数误差(特别是乘法和除法) ,这些数学运算会以指数形式扰乱数值。

检查你的逻辑,如果代码是:

x = 0.1


if (x == 0.1)

如果 X 值是通过更复杂的方法或操作计算出来的,那么调试器使用的 ToString 方法很有可能使用了一种智能舍入,也许你也可以这样做(如果这样做太冒险,回到使用十进制) :

if (x.ToString() == "0.1")

你需要 X-Y上的 Math.Absvalue的组合来比较。

您可以使用以下扩展方法方法

public static class DoubleExtensions
{
const double _3 = 0.001;
const double _4 = 0.0001;
const double _5 = 0.00001;
const double _6 = 0.000001;
const double _7 = 0.0000001;


public static bool Equals3DigitPrecision(this double left, double right)
{
return Math.Abs(left - right) < _3;
}


public static bool Equals4DigitPrecision(this double left, double right)
{
return Math.Abs(left - right) < _4;
}


...

因为除了 ToString之外,你很少调用双精度的方法,所以我相信它是相当安全的扩展。

然后你可以比较 xy

if(x.Equals4DigitPrecision(y))

从 Java 代码库中获得一个技巧,尝试使用 .CompareTo并测试零比较。这假设 .CompareTo函数以精确的方式考虑到浮点数相等性。比如说,

System.Math.PI.CompareTo(System.Math.PI) == 0

这个谓词应该返回 true

// number of digits to be compared
public int n = 12
// n+1 because b/a tends to 1 with n leading digits
public double MyEpsilon { get; } = Math.Pow(10, -(n+1));


public bool IsEqual(double a, double b)
{
// Avoiding division by zero
if (Math.Abs(a)<= double.Epsilon || Math.Abs(b) <= double.Epsilon)
return Math.Abs(a - b) <=  double.Epsilon;


// Comparison
return Math.Abs(1.0 - a / b) <=  MyEpsilon;
}

解释

使用除法 a/b 完成的主比较函数应该朝向1。但为什么是“组织”?它只是把一个数字作为引用来定义第二个数字。比如说

a = 0.00000012345
b = 0.00000012346
a/b = 0.999919002
b/a = 1.000081004
(a/b)-1 = 8.099789405475458e-5‬
1-(b/a) = 8.100445524503848e-5‬

或者

a=12345*10^8
b=12346*10^8
a/b = 0.999919002
b/a = 1.000081004
(a/b)-1 = 8.099789405475458e-5‬
1-(b/a) = 8.100445524503848e-5‬

通过除法,我们摆脱了尾随或前导零(或相对较小的数字) ,它们污染了我们对数字精度的判断。在这个例子中,比较的顺序是10 ^ -5,我们有4个数字精度,因为在开始的代码中,我写了与10 ^ (n + 1)的比较,其中 n 是数字精度。

瓦伦丁 · 库苏布的答案补充到 以上:

我们可以使用一个单一的方法,支持提供第 n 个精度数:

public static bool EqualsNthDigitPrecision(this double value, double compareTo, int precisionPoint) =>
Math.Abs(value - compareTo) < Math.Pow(10, -Math.Abs(precisionPoint));

注意: 此方法是为了简单而构建的,没有添加批量,也没有考虑到性能。

要比较浮点、 double 或 float 类型,请使用 CSharp 的特定方法:

if (double1.CompareTo(double2) > 0)
{
// double1 is greater than double2
}
if (double1.CompareTo(double2) < 0)
{
// double1 is less than double2
}
if (double1.CompareTo(double2) == 0)
{
// double1 equals double2
}

Https://learn.microsoft.com/en-us/dotnet/api/system.double.compareto?view=netcore-3.1

官方的 MS 帮助,特别是感兴趣的“精度比较”部分的上下文中的问题。 Https://learn.microsoft.com/en-us/dotnet/api/system.double.equals

// Initialize two doubles with apparently identical values
double double1 = .333333;
double double2 = (double) 1/3;
// Define the tolerance for variation in their values
double difference = Math.Abs(double1 * .00001);


// Compare the values
// The output to the console indicates that the two values are equal
if (Math.Abs(double1 - double2) <= difference)
Console.WriteLine("double1 and double2 are equal.");
else
Console.WriteLine("double1 and double2 are unequal.");

我的双重比较的扩展方法:

public static bool IsEqual(this double value1, double value2, int precision = 2)
{
var dif = Math.Abs(Math.Round(value1, precision) - Math.Round(value2, precision));
while (precision > 0)
{
dif *= 10;
precision--;
}


return dif < 1;
}