【问题标题】:float variable doesn't meet the conditions (C)浮点变量不满足条件 (C)
【发布时间】:2013-11-09 20:14:58
【问题描述】:

我试图让用户输入一个介于 1.00000 到 0.00001 之间的数字,而边缘不包含在浮点变量中。我可以假设用户在点后输入的数字不超过 5 个。 现在,这是我写的:

printf("Enter required Leibniz gap.(Between 0.00001 to 1.00000)\n");
scanf("%f", &gap);
while ((gap < 0.00002) || (gap > 0.99999))
{
printf("Enter required Leibniz gap.(Between 0.00001 to 1.00000)\n");
scanf("%f", &gap);
}

现在,当我输入可能的最小数字时:0.00002 会卡在 while 循环中。 当我运行调试器时,我看到 0.00002 与这个值一起存储在浮点变量中:1.99999995e-005 任何人都可以为我澄清我做错了什么?为什么 0.00002 不满足条件?这是什么“1.99999995e-005”。

【问题讨论】:

  • gap的类型是什么?
  • 您的条件与您的用户说明不符! 0.00001
  • 提示中的范围与您检查的数字不一致。 0.00002 不是0.00001 的直接继承者;它们之间有很多值。解决此问题后,您会发现值 0.00001 无法以二进制浮点数精确表示。
  • 为什么不gap &lt;= 0.00001 || gap &gt;= 1.0
  • 1.99999995e-005 表示1.99999995 * 10^(-5)。这就是浮点数在计算机中的表示方式:指数(从 1 到 2 的数字)和尾数(10 的幂)。因此,您无法以二进制格式准确表示某些数字,因此您会丢失一些数据,并且在您的情况下,“四舍五入”数字不符合您的条件

标签: c variables floating-point while-loop


【解决方案1】:

这里的问题是您使用的是 float 变量 (gap),但您将它与 double 常量 (0.00002) 进行比较。常量是double,因为除非另有说明,否则 C 中的浮点常量是双精度的。

一个潜在的问题是数字0.00002floatdouble 中都无法表示。 (它根本不能用二进制浮点表示,因为它的二进制扩展是无限长的,就像 &frac13; 的十进制扩展一样。)所以当你在程序中编写 0.00002 时,C 编译器会用 double 值替换它非常接近0.00002。同样,当scanf 将数字0.00002 读入float 变量时,它会替换一个非常接近0.00002float 值。由于double 数字的位数比floats 多,因此double 的值比float 的值更接近0.00002

当您比较两个精度不同的浮点值时,编译器会将精度较低的值转换为精度更高的完全相同的值。 (可表示为double 的值集是可表示为float 的值集的超集,因此始终可以找到与float 的值相同的double。)这就是执行gap &lt; 0.00002 时发生的情况:gap 被转换为相同值的double,并与双精度(接近)0.00002 进行比较。由于这两个值实际上都略小于 0.00002,并且double 更接近,因此float 小于double

您可以通过多种方式解决此问题。首先,您可以通过将gap 设置为double 并将scanf 格式更改为%lf,或者将gapfloat 进行比较来避免转换:

while (gap < 0.00002F || gap > 0.99999F) {

但这并不完全正确,有几个原因。首先,实际上并不能保证 C 编译器所做的浮点转换与标准库 (scanf) 所做的转换相同,并且标准允许编译器使用“最接近的可表示值,或者与最近的可表示值紧邻的较大或较小的可表示值,以实现定义的方式选择。” (它也没有详细说明 scanf 产生的值,但建议它是最接近的可表示值。)碰巧的是,gccglibc(Linux 上使用的 C 编译器和标准库)都产生最接近的可表示值,但其他实现不会。

无论如何,根据您的错误消息,您希望该值介于0.000011.00000 之间。所以你的测试应该是这样的:

while (gap <= 0.00001F || gap >= 1.0000F) { ...

(假设您将gap 保留为float。)

上述任何解决方案都可以。就我个人而言,我会将gap 设为double,以使比较更直观,并将比较更改为与0.000011.0000 进行比较。

顺便说一句,E-05 后缀的意思是“-5 的 10 次方”(E 代表 Exponent)。你会看到很多;这是编写浮点常量的标准方式。

【讨论】:

    【解决方案2】:

    floats 无法为每个可能的数字存储精确值(0-1 之间的无限数字因此不可能)。由于您正在经历的实现,将 0.00002 分配给浮点数将具有不同但非常接近的数字。精度随着数量的增加而降低。

    所以你不能直接比较两个接近的浮点数并得到健康的结果。

    更多关于浮点的信息可以在on this Wikipedia page找到。

    您可以做的是模拟定点数学。有一个int n = 100000; 在内部表示1.00000(1000 -> 0.001 等)并进行相应的计算或使用定点数学库。

    【讨论】:

    • floats 是精确值。它是不精确的操作。从像“0.00002”这样的字符串到float 通常会导致不精确的转换,而不是不精确的float 结果。
    【解决方案3】:

    单精度浮点数的小数部分可以表示从-2到2-2^-23的数字,并且小数部分的最小量化步长为2^-23。因此,如果某个值不能用这样的步骤表示,那么它会根据IEEE 754 rounding rules 用最接近的值表示:

    0.00002*32768 = 0.655360043     // floating point exponent is chosen.
    0.655360043/(2^-23) = 5497558.5 // is not an integer multiplier 
                                    // of quantization step, so the 
    5497558*(2^-23) = 0.655359983   // nearest value is chosen
    5497559*(2^-23) = 0.655360103   // from these two variants
    

    第一个变量等于十进制格式的 1.999969797×10⁻⁵,第二个变量等于 1.999999948×10⁻⁵(只是为了比较 - 如果我们选择 5497560,我们得到 2.000000677×10⁻⁵)。因此可以选择第二个变量作为结果,其值不等于 0.00002。
    浮点数的总精度也取决于指数值(取值从-128到127):它可以通过小数部分量化步长和指数值相乘来计算。如果总精度为 0.00002,则为 (2^-23)×(2^-15) = 3.6×(10^-12)。这意味着如果我们向 0.00002 添加一个小于该值一半的值,那么 0.00002 将保持不变。一般来说,这意味着有意义的浮点数的个数是从 1×exponent 到 2×(10^-23)×exponent。
    这就是为什么一种非常流行的方法是使用大于量化步长的某个 epsilon 值来比较两个浮点数。

    【讨论】:

    • FLT_EPSILON 值是从 1.0f 开始的量化步长。 2.0f以上量化步长为FLT_EPSILON*2.0f
    • @chux 感谢您的评论。在我的答案中添加了更多详细信息。
    【解决方案4】:

    正如一些 cmets 所说,由于浮点数的表示方式,您会看到这样的错误。 解决方案是将其转换为

    gap + 1e-8 < 0.0002 
    

    这为您提供了一个足够小的容忍窗口,足以让大多数情况下您想通过,而大多数情况您不想失败

    【讨论】:

      猜你喜欢
      • 2017-04-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-08-02
      • 2016-08-10
      • 1970-01-01
      • 2015-06-03
      • 2022-01-22
      相关资源
      最近更新 更多