C语言中的浮点数减法结果为零

4

我有一份用C语言编写的代码,旨在为16位微控制器使用。该代码主要进行了大量的浮点数运算。

这些算术运算在结果为正数时工作正常,但在减法中,如果期望的结果是负数,则会得到零。

result = 0.005 - 0.001;      Is correctly computed as 0.004
result = 0.001 - 0.005;      Is always zero.

为什么浮点数会发生这种行为?


4
也许你有一个“无符号浮点数”。 :) - Greg Hewgill
5个回答

5
有趣。这很可能是浮点软件的错误。嵌入式系统通常将浮点作为选项包含在内,以便使代码大小保持不变。但我不确定这里的问题是什么,因为您的第一个语句可以工作。请问以下代码会发生什么情况:
result = 0.005 - 0.001;
result = -result;

result = 0.002 - 0.001;
result = 0.002 - 0.002;
result = 0.002 - 0.003;

result = 0.001 - 0.002;
result = 0.001 - 0.003;
result = 0.001 - 0.004;

这里的想法是收集有用的信息,以确定可能导致问题的原因,这在取证中很常见。这些计算的结果可能有助于确定实际问题。
根据您在评论中的结果:
result = 0.005 - 0.001;  // 0.004
result = -result;        // 0.000
result = 0.002 - 0.001;  // 0.001
result = 0.002 - 0.002;  // 0.000
result = 0.002 - 0.003;  // 0.000
result = 0.001 - 0.002;  // 0.000
result = 0.001 - 0.003;  // 0.000
result = 0.001 - 0.004;  // 0.000

看起来你的浮点库存在严重缺陷。还有两个问题:
  • 你是如何打印结果的(请展示实际代码)?
  • 你使用的是哪种微控制器和开发环境?
可能是你打印的方式有问题,或者是你的环境限制了一些东西。
Ajit,我想你真的需要给我们一些代码来帮助你解决问题。不一定是你真正的代码(我们理解你对发布真正代码的担忧),只要有一些能够演示问题的代码就可以了。
根据你的一些评论,比如:

Adriaan,"result" 的数据类型是 float,即 32 位表示(单精度)。我使用 CAN 作为系统接口,因此我将结果乘以 1000 并通过 CAN 总线发送。如果它是一个负数,比如 -0.003,那么我期望在 CAN 消息中得到 FF FD。我没有调试器。

我不确定我完全理解了,但我会尽力而为。
你有一个32位浮点数,例如-0.003,将其乘以1000并将其放入整数中(0xFFFD是-3的16位二进制补码表示)。因此,当您运行以下代码时会发生什么:
int main(void) {
    float w = -0.003;
    int x = (int)(w * 1000);
    int y = -3;
    int z = -32768;
    // Show us you code here for printing x, y and z.
    return 0;
}

我想让你测试一个整数的原因是它可能与浮点数完全无关。浮点值可能是完全正确的,但你打印它的方式存在问题(CAN方法)。
如果“CAN”是某种串行接口,则可能存在发送字节的限制。我可以想象一种情况,高字节用作数据包标记,因此FF实际上可能会提前结束消息。这就是为什么我还要让你测试-32768(0x8000)的原因。
很难相信STMicroelectronics会生产出如此愚蠢的运行时系统,无法处理负浮点数。对我来说,更有可能的是信息在其他地方被损坏了(例如,“打印”过程,无论那是什么)。

以下是观察结果: result = 0.005 - 0.001; = 0.004 result = -result; = 0 result = 0.002 - 0.001; = 0.001 result = 0.002 - 0.002; = 0 result = 0.002 - 0.003; = 0 result = 0.001 - 0.002; = 0 result = 0.001 - 0.003; = 0 result = 0.001 - 0.004; = 0另外 result = 0.01 - 0.04; = 0 result = 0.01 - 0.05; = 0 - user170006
1
Pax, 它是ST10系列控制器。 - user170006
1
嘿,Pax, 我解决了。正如你所说,浮点运算没有问题。问题在于将负浮点数转换为整数。 对于ST10, float w = -0.003; int x = (int)(w * 1000); 结果为零。正确的方法是:
  1. float w = -0.003; float a = -w;
  2. int b = (int)(a * 1000);
  3. int c = -b;
因此,“C”将具有与负浮点值相对应的带符号整数。感谢您的帮助!干杯 :-)
- user170006
1
@Ajit,这很奇怪,也不是C语言应该做的事情。就像在你的第一个例子中一样,它几乎是在执行int x = (int)(w) * 1000;,这将会得到零,因为它首先将w转换为整数(零),然后将其乘以1000(仍然是零)。你展示的代码应该没问题。你应该联系制造你编译器的人并让他们知道——这绝对是一个错误。 - paxdiablo

0

微控制器是否具有浮点数硬件?可能没有;微控制器通常不具备此功能。因此,这可能是浮点运算软件实现中的错误或限制。查找文档,如果有源代码,请阅读源代码。


不,这个微控制器没有浮点硬件。但是它有浮点库。从之前的一些项目来看,.lib 没有错误。 - user170006

0

你是否尝试将0.001-0.005的结果打印成5个字符的字段?如果是,结果将显示为四舍五入为0.0。


谢谢Stephen,但我将结果乘以1000以将其视为整数。 - user170006

0

您能提供更多上下文吗?在这个例子中,评估可能是由编译器完成的,因为两个常量在编译时已知。'result'的类型是什么?(即IEEE-754 half、single还是double?)您如何评估这个问题?使用调试器、if语句还是printf?我之所以问这个问题,是因为可能会有一个误导。例如,如果您使用printf格式不正确,您可能只看不到减号。当您查看二进制表示(即通过printf("%lx", result)(如果它是32位))并检查符号位时,请注意。


Adriaan,"result"的数据类型是浮点型,即32位表示(单精度)。我使用CAN作为系统接口,因此将结果乘以1000以通过CAN发送。如果它是一个负数,比如-0.003,那么我期望在CAN消息中看到FF FD。我没有调试器。 - user170006

0

这不是固有的C语言问题,所以问题存在于您没有提到的以下几点:

  • 控制器(显然是ST10)
  • 编译器(Cosmic软件?如果是,则是否符合标准)
  • 浮点库(来自编译器?)
  • 程序(最短的完整程序,可以演示它将是很好的。)

可能是你没有展示从浮点数转换为整数的类型转换吗?


似乎不是因为代码相同,当结果为正数时它可以工作。由于知识产权问题,我无法与您分享程序。对此我感到抱歉。 - user170006
你肯定可以做一个缩短版本的。不过我觉得代码的其他部分更可能是问题所在。 毕竟,你对“结果”中的内容的了解只是推断出来的。 - user159335
int x = (int)(w * 1000); 的结果为零。尝试使用 1000.0,它会进行隐式转换,将 1000 视为 const int。 - user159335

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接