【问题标题】:Size of a float variable and compilation浮点变量的大小和编译
【发布时间】:2018-01-09 17:42:04
【问题描述】:

我很难理解gcc 在这方面的行为。对于我的架构,浮点数的大小为 4 个字节。但是我仍然可以在浮点数中存储一个 8 字节的实际值,而我的编译器对此一无所知。

例如我有:

#include <stdio.h>

int main(int argc, char** argv){

    float someFloatNumb = 0xFFFFFFFFFFFF;
    printf("%i\n", sizeof(someFloatNumb));
    printf("%f\n", someFloatNumb);
    printf("%i\n", sizeof(281474976710656));

    return 0;
}

我希望编译器会侮辱我,或者显示某种免责声明,因为我不应该这样做,至少我认为这是一种扭曲的魔法。

程序简单地运行:

4
281474976710656.000000
8

所以,如果我打印 someFloatNumb 的大小,我会得到 4 个字节,这是预期的。但受影响的值不是,如下所示。

所以我有几个问题:

  • sizeof(variable) 是否只是简单地获取变量类型并返回 sizeof(type),在这种情况下可以解释结果吗?

  • gcc 是否/可以增加类型的容量? (在幕后管理多个变量以允许我们做这类事情)

【问题讨论】:

  • sizeof(281474976710656) 只是给你sizeof(int)
  • 我仍然可以在浮点数中存储一个 8 字节的实际值 你可以吗?如何?您可以存储的只是那个 8 字节值的浮点 表示。而且你会在转换过程中丢失很多信息。
  • @cleblanc 这不是必须的,可能是 sizeof(long) 以及 sizeof(long long)
  • 您将值 0xFFFFFFFFFFFF 转换为浮点数,编译器会转换它,而不是 reinterpret_cast 变量。你注意到了吗,0xFFFFFFFFFFFF 不是偶数,因为它作为浮点数的表示 281474976710656.000000 是,这是由尾数引起的,它不能存储整数常量的最低有效位。
  • 如果编译器侮辱了我,我会将其从系统中删除。

标签: c gcc floating-point


【解决方案1】:

1)

sizeof(variable) 是否只是简单地获取变量类型并返回 sizeof(type),这在这种情况下可以解释结果吗?

除了variable-length arrayssizeof 不计算其操作数。所以是的,它只关心类型。所以sizeof(someFloatNumb) 是 4,相当于sizeof(float)。这解释了printf("%i\n", sizeof(someFloatNumb));

2)

[..] 但是我仍然可以在浮点数中存储一个 8 字节的实际值,而我的编译器对此一无所知。 gcc 是否/可以增加类型的容量? (在幕后管理多个变量以允许我们做那种事情)

没有。容量没有增长。您只是误解了浮点数是如何表示/存储的。 sizeof(float) 是 4 并不意味着 它不能存储超过 2^32(假设 1 字节 == 8 位)。见Floating point representationfloat 可以表示的最大值由常量FLT_MAX 定义(请参阅&lt;float.h&gt;)。 sizeof(someFloatNumb) 只产生对象 (someFloatNumb) 在内存中占用的字节数,不一定等于它可以表示的值的 范围。 这解释了为什么 printf("%f\n", someFloatNumb); 会按预期打印值(并且没有自动的“容量增长”)。

3)

printf("%i\n", sizeof(281474976710656));

这稍微复杂一些。正如前面(1)中所说,sizeof 只关心这里的类型。但是281474976710656的类型不一定是int。 C标准根据可以表示值的最小类型来定义整型常量的type。有关说明,请参阅 https://stackoverflow.com/a/42115024/1275169

在我的系统上,281474976710656 无法在 int 中表示,它存储在 long int 中,您的系统上也可能如此。所以你看到的基本上等同于sizeof(long)

没有可移植的方法来确定整数常量的类型。但是由于您使用的是gcc,因此您可以使用typeof 的小技巧:

typeof(281474976710656) x;
printf("%s", x); /* deliberately using '%s' to generate warning from gcc. */

生成:

警告:格式“%s”需要“char *”类型的参数,但参数 2 类型为“long int”[-Wformat=] printf("%s", x);


P.S:sizeof 生成一个size_t,其正确的格式说明符是%zu。这就是您应该在第一个和第三个 printf 语句中使用的内容。

【讨论】:

    【解决方案2】:

    这不存储“8 个字节”的数据,该值由编译器转换为整数,然后转换为 float 以进行赋值:

    float someFloatNumb = 0xFFFFFFFFFFFF; // 6 bytes of data
    

    由于float 可以表示较大的值,这没什么大不了的,但是如果您只使用 32 位浮点数,则会损失很多精度。请注意这里有一个细微但重要的区别:

    float value = 281474976710656.000000;
    int value   = 281474976710655;
    

    这是因为float 在精度不足时会变成近似值。

    标准 C 类型的容量不会“增长”。为此,您必须使用"bignum" library

    【讨论】:

    • 浮点数不会“耗尽”精度。
    • @nicomp 它对它可以表示的内容有限制,具体取决于有效数字的可用位数。假设这是一个32-bit IEEE floating point value,那么这是一个侥幸,近似值如此接近预期值,然后只是因为它在原始值中都是 1 位。
    • 我想您想说的是,当从以 10 为底的数字转换为以 2 为底的数字以存储在浮点类型中时,您可能会在转换完成之前用完精度。
    【解决方案3】:

    但是我仍然可以在浮点数中存储一个 8 字节的实际值,并且我的编译器 什么也没说。

    这不是正在发生的事情。

    float someFloatNumb = 0xFFFFFFFFFFFF;
    

    0xFFFFFFFFFFFF 是一个整数常量。它的值(以十进制表示)为281474976710655,其类型可能longlong long。 (顺便说一句,该值可以存储为 48 位,但大多数系统没有 48 位整数类型,因此它可能会存储为 64 位,其中高 16 位将为零。)

    当您使用一种数值类型的表达式来初始化不同数值类型的对象时,该值被转换。这种转换不依赖于源表达式的大小,只依赖于它的数值。对于整数到浮点数的转换,结果是最接近整数值的表示。可能会有一些精度损失(在这种情况下,有)。一些编译器可能有选项来警告精度损失,但转换是完全有效的,所以默认情况下您可能不会收到警告。

    这是一个小程序来说明发生了什么:

    #include <stdio.h>
    int main(void) {
        long long ll = 0xFFFFFFFFFFFF;
        float f      = 0xFFFFFFFFFFFF;
        printf("ll = %lld\n", ll);
        printf("f  = %f\n", f);
    }
    

    我系统上的输出是:

    ll = 281474976710655
    f  = 281474976710656.000000
    

    如您所见,转换失去了一些精度。 281474976710656 是 2 的精确幂,浮点类型通常可以精确地表示它们。这两个值之间存在非常小的差异,因为您选择的整数值非常接近可以精确表示的整数值。如果我更改值:

    #include <stdio.h>
    int main(void) {
        long long ll = 0xEEEEEEEEEEEE;
        float f      = 0xEEEEEEEEEEEE;
        printf("ll = %lld\n", ll);
        printf("f  = %f\n", f);
    }
    

    明显的精度损失要大得多:

    ll = 262709978263278
    f  = 262709979381760.000000
    

    【讨论】:

      【解决方案4】:

      0xFFFFFFFFFFFF == 281474976710655

      如果你用那个值初始化一个浮点数,它最终会是
      0xFFFFFFFFFFFF +1 == 0x1000000000000 == 281474976710656 == 1

      这很容易适应 4 字节浮点数、简单尾数、小指数。
      但是它不会存储正确的值(低一个),因为它很难存储在浮点数中。

      请注意,“+1”并不意味着递增。它最终会高一倍,因为表示只能与尝试的值一一接近。您可能会认为“四舍五入到 2 的下一个幂乘以螳螂可以存储的任何内容”。顺便说一句,尾数通常被解释为 0 到 1 之间的分数。
      越来越近确实需要你在尾数中初始化的 48 位;加上将用于存储指数的任何位数;或许还有一些其他细节。

      【讨论】:

      • 0xFFFF FFFF FFFF +1 == 0x1 0000 0000 0000,你在等式中省略了一个零。
      • @nicomp 是的,已修复。谢谢你的收获。
      • 我有一个问题,为什么浮点数用这个值初始化时,值会增加?
      • 它不会增加,它最终会高一,因为表示只能与尝试的值一一接近。
      【解决方案5】:

      查看打印的值...0xFFFF...FFFF 是奇数,但您的示例中打印的值是偶数。您正在为float 变量提供int 值,该值转换为float。正如所使用的值所预期的那样,转换正在失去精度,它不适合为目标变量尾数保留的 23 位。最后你得到一个近似值是0x1000000....0000(下一个值,这是最接近你使用的值,正如@Yunnosch 在他的回答中发布的那样)

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2010-12-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-05-28
        相关资源
        最近更新 更多