【问题标题】:Is scientific notation safe for integer constants in C?科学记数法对于 C 中的整数常量是否安全?
【发布时间】:2014-08-14 21:29:26
【问题描述】:

一段时间以来,我一直在用科学计数法表示常数的 10 的大幂,这样我就不必数零了。例如

#define DELAY_USEC 1e6

一位同事指出这是不安全的,因为它不是整数,也不能保证总是等于 1000000准确。文档似乎证实了这一点,但我想知道它是否真的实用。有没有办法使用速记安全地声明一个十次方整数?将其转换为定义中的 int 是否安全?

【问题讨论】:

  • 您可以用整数记下 10 的幂并假设它是 1(例如通过命名约定),然后在执行代码时将其传递给真正的 10 次方,这很安全。
  • 您没有将其转换为定义中的 int。如果你想要一个整数常量来保证类型安全,并且为了简洁起见,你可以使用十六进制文字。
  • 我猜是因为你定义了一个浮点数,所以没有办法知道它是否准确,因为浮点数的精度有限。
  • 这不是您问题的答案,但请记住,您可以在定义 static const int SEC_TO_MILLI = 1000; static const int SEC_TO_MICRO = 1000 * SEC_TO_MILLI; 之类的常量时使用数学,这通常可以帮助我避免需要复杂的容易出错的数字文字。
  • 另一个问题是,在每个表达式中,它所属的部分都将以“双”精度计算,这可能不是您想要的。

标签: c++ c gcc


【解决方案1】:

理论上,不会。两种语言都没有指定如何表示浮点值,或者可以精确表示哪些值。 (更新:显然,C11 确实推荐了一种表示形式。C++ 和较旧的 C 方言不推荐)。

实际上,是的,对于相当大范围的值。您可能会遇到的任何实现都将使用64-bit IEEE representation 代替double。这可以精确地表示最大为 253(大约 9x1015)的任何整数值。它当然可以表示任何可以用 32 位整数类型表示的东西。

【讨论】:

  • C11 5.2.4.2.2 确实指定了一些限制。例如。 double 必须能够保存任何 10 位小数的整数。
  • @mafso:有趣,我不知道。这意味着任何现代 C 编译器都应该准确地表示任何 32 位整数值。 C++ 中(还)没有这样的限制,除非我错过了。
  • 我不了解 C++,但 C 实际上确实指定浮点值应该尽可能使用 IEEE 754(C11 附录 F)。符合要求的实现应该带有#define 标志以指示适当的支持,因此程序员可以使用此信息。
  • @Leushenko:谢谢,我的C知识有点老了;我不知道这些天是强制的。
  • 实际上,对于 10 的正幂,您可以安全地使用高达 1e22 的大值:尽管可能令人惊讶,但它们仍然可以精确地表示为 53 位有效浮点格式(试一试使用良好的printf 实现以高精度打印它们。这是因为log2(5^22)<521e22==5^22*2^22
【解决方案2】:

您想使用用户定义的文字:

constexpr long long operator "" _k(long long l) {
    return l * 1000;
}

constexpr long long operator "" _m(long long l) {
    return l * 1000 * 1000;
}

那么你可以简单地做:

long long delay = 1_m;
long long wait = 45_k;

【讨论】:

  • 但你会想称它们为_k_m,因为没有前导下划线的文字名称是保留的。您可能还希望 _M 而不是 _m 来匹配 SI 前缀。
  • @MikeSeymour 你是对的,相应地改变了答案
  • @MikeSeymour - 下划线后跟大写字母不是保留标识符吗? (不是保留字面量,但通常为实现保留。)
  • @Mike - hmmm ...但对于“普通”保留符号,实现将被允许定义一个宏 _M(因为它是保留的)并且 混乱上用户代码。所以对于用户 def lit 来说,这似乎很奇怪。
  • @MartinBa:是的,你是对的; _M 是不允许的。我不应该这么建议的。
【解决方案3】:

您专门询问十的幂。 1e6 正好是一百万。您可以升级到1e22,而不会发生任何不好的事情。但请注意,在 C++ 和 C 中,1e6double 常量,而不是整数常量。

十的负幂是另一回事。 1e-1 是不精确的,所有低幂也是如此。

【讨论】:

  • C 不保证这一点,位 IEEE 保证
  • 并非所有低幂:5e-1 都是准确的。
  • @JamesKanze:这也不是十的幂。 (我只是修正了文本以澄清我说的是十的幂。)
  • 1e22的值从何而来?据我估计,不可表示的最小整数是2^53+1,大约是1e16
  • @tmyklebu:很公平,我没有注意到这个限制。可能值得澄清答案,以防我不是唯一一个在没有完整上下文的情况下阅读它的人。
【解决方案4】:

似乎gcc 假定使用科学计数法定义的常量作为浮点数,除非它被强制转换。

一个简单的 C 代码显示了这一点:

#include <stdio.h>

#define DELAY_USEC_FP  1e6
#define DELAY_USEC_INT (unsigned int) 1e6

int main()
{
    printf("DELAY_USEC_FP: %f\n", DELAY_USEC_FP);
    printf("DELAY_USEC_INT: %u\n",  DELAY_USEC_INT);
    return 0;
}

在 x86-64 机器上,gcc 生成此汇编代码 ($ gcc -S define.c):

[...]
; 0x4696837146684686336 = 1e6 in double-precision FP IEEE-754 format
movabsq $4696837146684686336, %rax
[...]
call    printf
movl    $1000000, %esi
[...]
call    printf
movl    $0, %eax

here 所述,10e15 和 10e22 是十个数字的最大幂,分别以简单和双精度浮点格式精确表示。

十个数的幂次方不能用 32 位或 64 位整数类型表示。

【讨论】:

    【解决方案5】:

    您永远不会在小于 INT_MAX 的值上出现舍入错误,因为 double 的规范搁置了 52 bits for you to use。您的“小数部分”只是您的整数,而您的“指数”将是 1,而浮点数不会与之抗衡。

    【讨论】:

    • 这不是正在发生的事情,因为这会给你一个非标准化的浮点数。 double 有一个隐含位,因此您不能以这种方式表示正整数。 (相反,你会得到你所说的标准化等价物。)
    • 什么意思?数字的“指数”位范围中的值实际上不会是 1,但我相信结果会有效,因此“分数”将只包含您的数字。只要你在 2^32 以下(我认为 OP 是,因为如果他不得不使用更大的常量,他可能会更加小心),那应该成立。
    • 有效数字将包含除数字的第一位以外的所有内容。高位是隐含位。
    【解决方案6】:

    这确实不安全,因为编译器会将其视为浮点数,因此精度限制为 53 位而不是 64 位整数(long int),您可以阅读有关浮点数表示的更多信息

    http://en.wikipedia.org/wiki/Floating_point

    【讨论】:

      猜你喜欢
      • 2017-06-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-08-20
      • 1970-01-01
      • 1970-01-01
      • 2020-12-30
      相关资源
      最近更新 更多