2

我发生了一个非常奇怪的行为(对我来说):

    int generate_scenario_one_pass(FILE *out, double freq_mhz) {
        unsigned int d_freq, d_freq_test;
        d_freq              = (int)(freq_mhz * 20);
        d_freq_test         = (int)(float)(freq_mhz * 20);
        printf("when freq_mhz = %.1f, d_freq = 0x%04X, d_freq_test = 0x%04X\n", freq_mhz, d_freq, d_freq_test);
    }

整个代码不在这里,但它不相关。这个函数被多次调用,值递增,从 2110.0 开始,递增 0.1。

when freq_mhz = 2110.0, d_freq = 0xA4D8, d_freq_test = 0xA4D8
when freq_mhz = 2110.1, d_freq = 0xA4DA, d_freq_test = 0xA4DA
when freq_mhz = 2110.2, d_freq = 0xA4DC, d_freq_test = 0xA4DC
when freq_mhz = 2110.3, d_freq = 0xA4DD, d_freq_test = 0xA4DE

在最后一次迭代中,d_freq是错误的!但d_freq_test具有正确的价值。所以我的问题是通过从 to 转换到 from todouble来解决的。我想知道为什么。floatfloatint

这是在 x86 CPU 上使用 MSVC++ 6.0 编译的。

4

5 回答 5

4

有许多数字不能精确地表示为浮点数 - 其中 0.1 就是其中之一(它将四舍五入到可以表示的最接近的数字 - 类似于 0.0999999999999998)。使用时double,2110.3 恰好由一个略小于 2110.3 的数字表示,因此当您乘以 20 并int强制转换为(将向下舍入)时会给出“错误”结果,而作为浮点数的 2110.3 将由比 2110.3 略大的数字,因此给出了预期的结果。

于 2012-06-05T11:32:08.173 回答
2

实际上我的双重铸造不是解决方案。

#include <stdio.h>

int main(int argc, char **argv) {
    int d_freq, d_freq_test;
    double freq_mhz = 2110.0;
    double step = 0.1;

    while (freq_mhz < 2111.0) {
        d_freq = (int)(freq_mhz * 20.0);
        d_freq_test = (int)(float)(freq_mhz * 20.0);
        printf("freq: %.1f, d_freq: 0x%04X, d_freq_test: 0x%04X\n", freq_mhz, d_freq, d_freq_test);
        freq_mhz += step;
    }

    return 0;
}

这产生(错误):

freq: 2110.0, d_freq: 0xA4D8, d_freq_test: 0xA4D8
freq: 2110.1, d_freq: 0xA4DA, d_freq_test: 0xA4DA
freq: 2110.2, d_freq: 0xA4DC, d_freq_test: 0xA4DC
freq: 2110.3, d_freq: 0xA4DD, d_freq_test: 0xA4DD <-- :(
freq: 2110.4, d_freq: 0xA4DF, d_freq_test: 0xA4DF
freq: 2110.5, d_freq: 0xA4E1, d_freq_test: 0xA4E1
freq: 2110.6, d_freq: 0xA4E3, d_freq_test: 0xA4E3
freq: 2110.7, d_freq: 0xA4E5, d_freq_test: 0xA4E5
freq: 2110.8, d_freq: 0xA4E7, d_freq_test: 0xA4E7
freq: 2110.9, d_freq: 0xA4E9, d_freq_test: 0xA4E9
freq: 2111.0, d_freq: 0xA4EB, d_freq_test: 0xA4EB

虽然这段代码:

#include <stdio.h>

int main(int argc, char **argv) {
    int d_freq, d_freq_test;
    double freq_mhz = 2110.0;
    double step = 0.1;

    while (freq_mhz < 2111.0) {
        d_freq = (int)(freq_mhz * 20.0);
        d_freq_test = (int)(float)(freq_mhz * 20.0 + 0.5);
        printf("freq: %.1f, d_freq: 0x%04X, d_freq_test: 0x%04X\n", freq_mhz, d_freq, d_freq_test);
        freq_mhz += step;
    }

    return 0;
}

产生:

freq: 2110.0, d_freq: 0xA4D8, d_freq_test: 0xA4D8
freq: 2110.1, d_freq: 0xA4DA, d_freq_test: 0xA4DA
freq: 2110.2, d_freq: 0xA4DC, d_freq_test: 0xA4DC
freq: 2110.3, d_freq: 0xA4DD, d_freq_test: 0xA4DE <-- :)
freq: 2110.4, d_freq: 0xA4DF, d_freq_test: 0xA4E0
freq: 2110.5, d_freq: 0xA4E1, d_freq_test: 0xA4E2
freq: 2110.6, d_freq: 0xA4E3, d_freq_test: 0xA4E4
freq: 2110.7, d_freq: 0xA4E5, d_freq_test: 0xA4E6
freq: 2110.8, d_freq: 0xA4E7, d_freq_test: 0xA4E8
freq: 2110.9, d_freq: 0xA4E9, d_freq_test: 0xA4EA
freq: 2111.0, d_freq: 0xA4EB, d_freq_test: 0xA4EC

哪个是对的。

所以这确实是舍入问题,一个精度问题,通过在 x20 乘法的结果上加上 0.5 来解决。

于 2012-06-05T11:39:10.383 回答
2

当你从 double 转换为 int 时,你会得到截断。

freq_mhz*20at的值2110.30x40E49BFFFFFFFFFF- 表示,即 42207.9999999999927240423858166。当您将其截断为int.999999 时,您会得到 42207(或 0xA4DD - 为什么选择以十六进制表示这些?)

如果同时转换为浮点数,则会执行舍入操作。您真正想要做的是显式调用round该值,然后转换为int.

于 2012-06-05T11:47:47.073 回答
1

因为 0.1 不能用二进制浮点数精确表示。您所看到的是近似值,由于转换导致的截断和导致的舍入而加剧了这种情况printf

解决此问题的一种方法是在转换为 int 时显式舍入而不是截断(您可以使用round())。

于 2012-06-05T11:30:29.310 回答
0

十分之一不能用二进制表示。这就像十进制的 1/3。小数点后的位数越多,距离越近,但您无法到达那里。有各种各样的应对策略,但基本上如果你想要精确的表示,浮点格式就不行了。需要定点(十进制)格式。

于 2012-06-05T11:40:43.577 回答