【问题标题】:In C and Objective-C, should we use 0.5f or 0.5?在 C 和 Objective-C 中,我们应该使用 0.5f 还是 0.5?
【发布时间】:2012-05-07 06:11:21
【问题描述】:

我看到一个作者使用了很多地方:

sprite.anchorPoint = CGPointMake(1, 0.5f);

也就是说,为什么不使用0.5 而不是0.5f——使用0.5f 有优势吗?

【问题讨论】:

标签: objective-c c floating-point


【解决方案1】:

0.5 是 double 常量,而 0.5f 是 float 常量。

使用0.5f有优势吗?

是的。您要么想为计算指定类型(读取:精度),要么避免隐式缩小(以及相关的编译器警告)。

除了避免编译器警告或只是对参数类型非常严格之外,您提供的示例并不引人注目。

然而,不那么明显的双精度提升可能代价高昂。

回答“我们应该使用 0.5f 还是 0.5”:因为每个都有适当的时间,所以您应该在程序中使用 both。您应该使用适合所需计算/精度的类型、您传递的参数的类型或适合表达式中其他值的类型。这就像整数一样——有适当的时间来考虑宽度,也有适当的时间来后缀(例如U 和L)。有时区分很重要,而(自然)有时并不重要。

【讨论】:

    【解决方案2】:

    常量 0.5 在 Objective-C 中声明了一个 double,在末尾添加了一个 f - 0.5f 将常量声明为一个(32 位)浮点数。
    看看"f" after number/float in Objective-C/C 的帖子。

    【讨论】:

    • 有趣...如果使用 0.5,最终目标代码是否会长 4 个字节或什么...(或者如果最终目标代码中没有明显的 4 个字节,那么可能是 1000 行这会显示出差异吗?
    • 公平地说,我认为不能保证float 是32 位的。但是今天几乎所有的系统都是这样做的。 [另外,@JeremyL - 查看目标代码是否有任何不同的最佳方法是编译它并查看目标代码!]
    【解决方案3】:

    结尾的f 强制常量为单精度float,因为浮点常量默认为doubles。这在几个编译器修订版之前更为重要,因为我不相信双精度到浮点数的自动类型转换,因为编译器无法确保这样做没有副作用。结果是很多代码都保留了f 说明符,以避免在只需要单精度浮点寄存器时加载双精度浮点寄存器时消耗周期。

    【讨论】:

      【解决方案4】:

      当双常量合适时使用浮点常量会产生后果,反之亦然:

      1. 许多常量会改变值。例如,.3 不等于 .3f。当源文本包含十进制数字时,编译器必须将其转换为可以用浮点表示的值。 1999 C 标准要求将其转换为最接近的较高可表示值或最近的较低可表示值。一个好的编译器会将它转换为任一方向上最接近的值,并且在平局的情况下,将选择最低位为零的那个。 (返回这样的值称为“正确舍入”。)当正确舍入为常用的浮点格式时,.3 变为 0x1.3333333333333p-2,.3f 变为 0x1.333334p-2。 (这些是十六进制浮点常数。“0x”之后和“p”之前的部分是十六进制数字,“p”之后的部分是 2 的十进制指数。所以 0x3.cp4 是 0x3.c 乘以 2 4,即 (3 + 12/16)⋅24 = 60.)

      2. 表达式更改类型。当算术运算符的操作数同时包含 float 和 double 操作数时,float 操作数将转换为 double。这可能会导致计算值发生变化。 1999 C 标准允许编译器以比其类型要求更高的精度和范围来表示浮点值,因此浮点值可以保存在双精度寄存器中并使用双精度算术运算。但是,如果使用双精度常量,则要求编译器使用双精度运算。因此,如果浮点数 x 包含 0x1.24924ap-3(大约 1/7),那么“x + .5f”可能会产生 0x1.492492p-1,而“x + .5”必须产生 0x1.4924928p-1。

      3. 在可能会根据参数类型选择调用哪个函数的 Objective C、C++ 和其他语言中,更改参数的类型可以完全更改执行的代码。调用“foo(.5f)”可以调用分配内存的例程,而“foo(.5)”写入网络套接字。这通常是被调用对象的糟糕设计,但在某些特殊情况下,对象需要对其参数的类型敏感。此外,这在 C 中在技术上是可行的(如在 tgmath.h 中),但很少见(通常涉及使用预处理器将“foo”定义为测试其参数大小的表达式)。

      以上列表并非包罗万象。

      另一方面,代码或数据大小很少成为问题。无法保证无论您编写 .5f 还是 .5,编译器都会实际存储浮点数或双精度数。例如,如果我写“printf("%g", 3.*4.+5.)”,编译器不需要存储3、4或5。它只需要生成一个写“17”的程序” 到标准输出,它可以通过存储 3、4 和 5 并在运行时进行计算或存储 17 并在运行时将其传递给 printf 或仅存储字符串“17”并将其写入带有 puts 的标准输出,根本不存储任何数字或调用 printf。

      因此,通常情况下,使用哪种类型最重要的是正确表达您想要执行的计算,而不是担心优化。如果你写“foo(.5)”并且 foo 有一个 double 参数,编译器可能会将 .5f 存储在程序的常量中并生成代码,在运行时将 .5f 加载到一个 double 寄存器中并将其传递给 foo .我希望一个好的编译器能够以最小的形式存储常量,无需额外的执行成本即可加载。

      【讨论】:

      • 关于您的3.*4.+5. 示例和消息,您是完全正确的,实际上可以观察到一些编译器将x * 0.5 优化为x * 0.5f(x 类型为float)虽然两者始终是同一件事是一个不平凡的结果。但是请注意,C99 的 F.7.4:1 说“浮动类型的算术常量表达式,除了具有静态存储持续时间的对象的初始化程序中的表达式之外,在执行期间(好像)被评估;因此,它会受到任何可操作的浮点控制模式的影响,并引发浮点异常”。
      • 是的,如果 FENV_ACCESS (C99 7.6.1) 已打开。对于大多数编译器,默认是关闭的。
      【解决方案5】:

      您还需要考虑 0.5 在单精度和双精度浮点中都可以精确表示。这意味着它们完全相同。相应格式中可用的有效位的数量并不重要,因为值 0.5 只需要有效位中的单个位(隐式位),所有剩余位均为 0。

      因此,如果您需要节省存储空间,0.5f 更可取。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-10-27
        • 2012-12-23
        • 2010-10-25
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多