我怎么能提前知道哪些实数在 C 中使用浮点变量会有不精确的表示?

How can I know in advance which real numbers would have an imprecise representation using float variables in C?

我知道数字 159.95 不能用 C 中的 float 变量精确表示。

例如,考虑以下代码:

#include <stdio.h>
int main()
{
    float x = 159.95;
    printf("%f\n",x);
    return 0;
}

它输出 159.949997。

我想知道是否有某种方法可以提前知道哪个实际值(十进制)将以不精确的方式表示,例如 159.95 数字。

此致。

通常,float 是一个 IEEE754 二进制 32 浮点数(规范不保证这一点,在某些 compilers/systems 上可能有所不同)。此数据类型指定一个 24 位有效数字;这意味着如果你用二进制写数字,它应该不超过 24 位,不包括尾随零。

159.95's binary representation 是 10011111.11110011001100110011... 永远重复 0011,因此它需要无限多的位才能用二进制格式精确表示。

其他示例:

1073741760 的二进制表示为 111111111111111111111111000000。它在该表示中有 30 位,但只有 24 位有效位(因为余数是尾随零位)。它有一个精确的浮点表示。

1073741761 的二进制表示为 111111111111111111111111000001。它有 30 个有效位,不能精确表示为浮点数。

0.000000059604644775390625 的二进制表示为 0.000000000000000000000001。它有一个有效位,可以精确表示。

0.750000059604644775390625 的二进制表示形式为 0.110000000000000000000001,即 24 位有效位。它可以精确地表示为一个浮点数。

1.000000059604644775390625 的二进制表示形式为 1.000000000000000000000001,即 25 个有效位。它不能完全表示为浮点数。

另一个因素(适用于非常大和非常小的数字)是指数限制在 -126 到 +127 范围内。对于非正规值和其他特殊情况,这通常允许值范围从大约 2-126 到略低于 2128.

简而言之,对于 float 最常用的格式,当且仅当它可以表示为 F 的整数次方时,一个数字才可以精确表示二、2E 这样:

  • F的量级小于224,且
  • –149 ≤ E < 105.

更一般地说,C 2018 5.2.4.2.2 指定了 floating-point 类型的特征。一个floating-point数表示为sbe•sum(fk bk, 1≤kp),其中:

  • s是一个符号,+1或-1,
  • b是C实现选择的固定基数,常为2,
  • e是一个指数,它是介于最小值emin和最大 emax,由 C 实现选择,
  • p为精度,有效位数中base-b位数,
  • fk是base-b[=中的数字157=], 小于 b.
  • 的非负整数

significand是表示的小数部分,sum(fk bk, 1 ≤ kp)。它被写成一个总和,这样我们就可以表达它可能具有的可变位数。 (p 是由 C 实现设置的变量,而不是由使用 C 实现的程序员设置的变量。)当我们在基数 b[=157= 中写出一个有效数字时],它可以是一个数字,例如 .0011101010011001010101102 对于基数为 2 的 24 位有效数。请注意,在这种形式(和总和)中,有效数具有所有它的小数点后的数字。

为了更容易判断数字是否采用这种格式,我们可以调整比例,使有效数为整数,而不是在小数点后有数字:sbep•sum(fkbpk, 1≤kp).这会将上述有效数字从 .0011101010011001010101102 更改为 0011101010011001010101102。由于它有 p 个数字,它总是一个 non-negative 小于 bp 的整数.

现在我们可以弄清楚有限数是否可以用这种格式表示:

  • 得到b,p,emin,以及 emax 用于目标 C 实现。如果float使用IEEE-754 binary32,那么b就是2,p就是24,emin为-125,而emax为128。当<float.h> 被包括在内,它们被定义为 FLT_RADIXFLT_MANT_DIGITSFLT_MIN_EXPFLT_MAX_EXP
  • 忽略这个标志。将数字的绝对值写成有理数 n/d 的最简单形式。如果是整数,设d为1。
  • 如果 d 不是 b 的幂,则该数字在格式中不可表示。
  • 如果nb的倍数大于等于bp,除以b再乘d乘以d直到n不是倍数或者小于b p.
  • n大于等于bp ,该数字无法用格式表示。
  • e 满足 1/d = b ep。如果emineemax,数字可以用格式表示。否则不是。

某些 floating-point 格式可能不支持次正规数,其中 f1 为零。这由 FLT_HAS_SUBNORM 定义为零表示,需要对上述内容进行修改。

I would like to know if there is some way to know in advance which real value... would be represented in an imprecise way

简短且只有部分滑稽的答案是......所有这些!

float 类型的值大约有 2^32 = 4294967296 个。并且有无数个实数。所以,对于一个randomly-chosen实数,它可以精确表示为float类型值的概率是4294967296/∞,即0.

如果使用 double 类型,大约有 2^64 = 18446744073709551616 个,因此 randomly-chosen 实数可以精确表示为 double 的几率是18446744073709551616/∞,又是...0.

我知道我没有完全回答你问的问题,但总的来说,使用二进制 floating-point 类型通常是个坏主意,就好像它们是小数的精确表示一样。试图假设它们曾经是一个精确的表示通常会导致麻烦。通常,最好假设 floating-point 类型是实数、句点(即不假设小数)的不完美(近似)实现。如果您从不假设它们是精确的(对于真正的实数,它们实际上永远不会是),那么您永远不会在您认为它们是精确的但事实并非如此的情况下遇到麻烦。

[脚注 1:正如 Eric P. 在评论中提醒的那样,没有“randomly-chosen 实数”这样的东西,这就是为什么这是一个部分滑稽的答案。]

[脚注 2:我现在看到你的评论,你说你确实假设它们都是不精确的,但你会“想以更深入的方式理解这种现象”,在这种情况下我的回答是否定的很好,但希望其他人也这样做。我特别赞扬 Martin Rosenau 的回答,它直击问题的核心:当且仅当它的约化分母是 2 的纯幂,或者换句话说,有理数在基数 2 中可以表示为 2它的质因数分解。这就是为什么,如果你取任何你可以实际存储在 floatdouble 中的数字,然后使用 %f 和足够的数字将其打印出来,使用 properly-written printf,您会注意到数字总是以 ...625 或 ...375 之类的结尾。二进制分数就像 U.S 中仍然使用的英语尺子:一切都是二分之一、四分之一、八分之一和十六分之一以及 thirty-seconds 和 sixty-fourths.]

I would like to know if there is some way to know in advance which real value (in decimal system) would be represented in an imprecise way like the 159.95 number.

一般情况下,浮点数只能表示分母为2的幂的数

要检查一个数字是否可以表示为浮点值(任何 floating-point 类型),取小数点后的小数位,将它们解释为数字并检查它们是否可以被除乘以 5^n 而 n 是位数:

159.95 => 95, 2 位 => 95%(5*5) = 20 => 不能表示为 floating-point 值

反例:

159.625 => 625, 3位数字=> 625%(5*5*5) = 0 => 可以表示为floating-point值

您还必须考虑 floating-point 值在小数点后只有有限位数的事实:

原则上123456789完全可以用floating-point值表示(是整数),但是float位数不够!

要检查整数值是否可以用 float 精确表示,请将数字除以 2,直到结果为奇数。如果结果 < 2^24,则数字可以精确地表示为 float

在有理数的情况下,首先进行上述“可被 5^n 整除”检查。然后将数字乘以 2,直到结果为整数。检查它是否 < 2^24.