【发布时间】:2009-09-09 10:14:25
【问题描述】:
我有一个名为x 的double 变量。
在代码中,x 被赋值为0.1,我在比较x 和0.1 的“if”语句中检查它
if (x==0.1)
{
----
}
很遗憾它没有进入if语句
我应该使用
Double还是double?这背后的原因是什么?您能为此提出解决方案吗?
【问题讨论】:
-
你能添加你的双重声明吗?
我有一个名为x 的double 变量。
在代码中,x 被赋值为0.1,我在比较x 和0.1 的“if”语句中检查它
if (x==0.1)
{
----
}
很遗憾它没有进入if语句
我应该使用Double还是double?
这背后的原因是什么?您能为此提出解决方案吗?
【问题讨论】:
由于计算机如何存储浮点值,这是一个标准问题。在此处搜索“浮点问题”,您会发现大量信息。
简而言之——float/double 不能精确存储0.1。总会有一点偏差。
您可以尝试使用decimal 类型,它以十进制表示法存储数字。因此0.1 将可以精确表示。
你想知道原因:
Float/double 存储为二进制分数,而不是十进制分数。举例说明:
12.34 在十进制表示法中(我们使用的)是什么意思
1 * 101 + 2 * 100 + 3 * 10-1 + 4 * 10-2支持>
计算机以相同的方式存储浮点数,只是它使用基数2:10.01 表示
1 * 21 + 0 * 20 + 0 * 2-1 + 1 * 2-2支持>
现在,您可能知道有些数字无法用我们的十进制表示法完全表示。例如,十进制的1/3 是0.3333333…。同样的事情发生在二进制符号中,除了不能精确表示的数字是不同的。其中有号码1/10。二进制表示,即0.000110011001100…。
由于二进制不能精确存储,所以采用四舍五入的方式存储。因此你的问题。
【讨论】:
x = 0.01; 来分配x,然后与文字0.01 进行比较,x 和0.01 的实际底层二进制值不会相同(再次,假设没有计算用于分配x)。因此,(x == 0.01) 应该可以工作,不是吗?但是,一旦您使用 x 执行一些算术运算,所有的赌注都将被取消。另外,我会更明确:if (x == 0.01d) 明确声明0.01 是双精度(而不是将0.01 从float 转换为double)——这可能是问题所在。跨度>
0.01 与0.01 进行比较,我会得到true。
x = 0.01; if ( x == 0.01),那么比较应该是正确的。好吧,假设没有任何隐式的浮点到双精度数据类型转换(尽管可能在那时)。但 OP 没有具体说明他是如何获得第一个 0.01 的。它很可能确实来自计算。
x 值。如果该值是通过迭代计算分配的(意思是最终结果约为 0.01),那么这可能是“预期的”。 :)
double 和Double 相同(double 是Double 的别名),可以互换使用。
将双精度值与另一个值进行比较的问题在于,双精度值是近似值,而不是精确值。因此,当您将x 设置为0.1 时,它实际上可能存储为0.100000001 或类似的东西。
您应该检查差异是否小于定义的最小差异(容差),而不是检查是否相等。类似的东西:
if (Math.Abs(x - 0.1) < 0.0000001)
{
...
}
【讨论】:
您需要在X-Y 上组合Math.Abs 和value 进行比较。
您可以使用以下扩展方法方法
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 之外,您很少在 double 上调用方法,我相信它的扩展非常安全。
那你可以比较x和ylike
if(x.Equals4DigitPrecision(y))
【讨论】:
由于舍入,不能总是精确地比较浮点数。比较
(x == .1)
电脑真的会比较
(x - .1) vs 0
由于浮点数在机器上的表示方式,不能总是精确地表示提取的结果。因此,您会得到一些非零值,并且条件评估为 false。
为了克服这种比较
Math.Abs(x- .1) vs some very small threshold ( like 1E-9)
【讨论】:
比较精确 应谨慎使用 Equals 方法,因为由于两个值的精度不同,两个明显等效的值可能不相等。以下示例报告 Double 值 .3333 和 1 除以 3 返回的 Double 不相等。
...
一种推荐的技术不是比较相等性,而是定义两个值之间可接受的差异范围(例如其中一个值的 0.01%)。如果两个值之间的差值的绝对值小于或等于该余量,则差异可能是由于精度差异造成的,因此这些值很可能相等。下面的示例使用这种技术来比较 .33333 和 1/3,这是前面的代码示例发现不相等的两个 Double 值。
所以如果你真的需要一个替身,你应该使用文档中描述的技术。 如果可以,请将其更改为小数。 会慢一些,但你不会遇到这种问题。
【讨论】:
使用@987654321@。它没有这个“问题”。
【讨论】:
众所周知,由于舍入和内部表示问题,浮点值的精确比较并不总是有效。
尝试不精确的比较:
if (x >= 0.099 && x <= 0.101)
{
}
另一种选择是使用十进制数据类型。
【讨论】:
double(小写)只是System.Double 的别名,所以它们是相同的。
原因见Binary floating point and .NET。 简而言之:双精度类型不是精确类型,“x”和“0.1”之间的微小差异会将其丢弃。
【讨论】:
Double(在某些语言中称为 float)由于舍入问题而存在问题,仅当您需要近似值时才适用。
Decimal 数据类型可以满足您的需求。
对于引用 decimal 和 Decimal 在 .NET C# 中是相同的,就像 double 和 Double 类型一样,它们都指代相同的类型(尽管如您所见,decimal 和 double 非常不同)。
请注意,Decimal 数据类型有一些与之相关的成本,因此如果您正在查看循环等,请谨慎使用它。
【讨论】:
官方 MS 帮助,特别是对问题上下文中的“比较中的精确度”部分感兴趣。 https://docs.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.");
【讨论】:
1) 我应该使用 Double 还是 double???
Double 和 double 是一回事。 double 只是一个 C# 关键字,用作 System.Double 类的别名
最常见的是使用别名! string (System.String)、int(System.Int32) 也一样
【讨论】:
从 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 是数字精度。
【讨论】:
添加到 Valentin Kuzub 的回答 above:
我们可以使用支持提供第 n 个精度数的单一方法:
public static bool EqualsNthDigitPrecision(this double value, double compareTo, int precisionPoint) =>
Math.Abs(value - compareTo) < Math.Pow(10, -Math.Abs(precisionPoint));
注意:此方法是为了简单而构建的,没有增加体积,也没有考虑到性能。
【讨论】:
作为一般规则:
双重表示在大多数情况下已经足够好,但在某些情况下可能会失败。如果您需要完整的精度(如在金融应用程序中),请使用十进制值。
双精度数的大多数问题并非来自直接比较,它曾经是数个数学运算累积的结果,这些运算会因舍入和小数误差(尤其是乘法和除法)而以指数方式干扰值。
检查你的逻辑,如果代码是:
x = 0.1
if (x == 0.1)
它不应该失败,失败很简单,如果 X 值是通过更复杂的方式或操作计算的,那么调试器使用的 ToString 方法很可能正在使用智能舍入,也许你可以做同样的事情(如果那是回到使用小数太冒险了):
if (x.ToString() == "0.1")
【讨论】:
由于浮点数在内部存储的方式,浮点数表示是出了名的不准确。例如。 x 实际上可能是 0.0999999999 或 0.100000001 并且您的条件将失败。如果要确定浮点数是否相等,则需要指定它们是否在一定的容差范围内相等。
即:
if(Math.Abs(x - 0.1) < tol) {
// Do something
}
【讨论】:
我的双重比较的扩展方法:
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;
}
【讨论】:
比较浮点、双精度或浮点类型,使用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://docs.microsoft.com/en-us/dotnet/api/system.double.compareto?view=netcore-3.1
【讨论】: