C++ 第一个以单精度碰撞的数字是131072.02,这是正确的吗?(正,考虑2位数字作为尾数)

C++ 第一个以单精度碰撞的数字是131072.02,这是正确的吗?(正,考虑2位数字作为尾数),c++,floating-point,C++,Floating Point,我试图为我的音频应用程序找出float是否可以用来正确表示我将使用的参数范围 它需要的“最大”掩码用于频率参数,该参数为正,并允许最大两位数作为尾数(即从20.00 hz到22000.00 hz)。从概念上讲,以下数字将四舍五入,因此我不喜欢它们 所以我做这个来检查第一个以单精度碰撞的数字: float temp = 0.0; double valueDouble = 0.0; double increment = 1e-2; bool found = false; while(!found)

我试图为我的音频应用程序找出
float
是否可以用来正确表示我将使用的参数范围


它需要的“最大”掩码用于频率参数,该参数为正,并允许最大两位数作为尾数(即从20.00 hz到22000.00 hz)。从概念上讲,以下数字将四舍五入,因此我不喜欢它们

所以我做这个来检查第一个以单精度碰撞的数字:

float temp = 0.0;
double valueDouble = 0.0;
double increment = 1e-2;

bool found = false;
while(!found) {
    double oldValue = valueDouble;
    valueDouble += increment;
    float value = valueDouble;

    // found
    if(temp == value) {
        std::cout << "collision found: " << valueDouble << std::endl;
        std::cout << "   collide with: " << oldValue << std::endl;
        std::cout << "float stored as: " << value << std::endl;
        found = true;
    }

    temp = value;        
}
float temp=0.0;
double-valueDouble=0.0;
双增量=1e-2;
bool-found=false;
而(!found){
double oldValue=valueDouble;
valueDouble+=增量;
浮动值=双精度值;
//发现
如果(温度==值){
标准::cout定义为

  • 1符号位
  • 8个指数位:范围2^-126到2^127~10^-38到10^38)
  • 23分数(尾数)位:十进制精度(取决于指数)
在22k时,指数将表示16384=2^14的偏移量,因此23位尾数将为您提供2^14/2^23=1/2^9=0.001953125…的精度,这对于您的情况足够了


对于131072.01,指数将表示偏移量131072=2^17,因此尾数将给出2^17/2^23=1/2^6=0.015625的精度,该精度大于您的目标精度0.01

您的程序无法准确验证您想要的,但您的基本推理应该是正确的

该程序的问题是valueDouble将累积轻微的错误(因为0.01无法准确表示),而将字符串“20.01”转换为浮点数将引入轻微的舍入错误

但是这些误差应该在DBL_ε的量级上,并且比您看到的误差小得多

如果您真的想测试它,您必须将“20.00”写入“22000.00”,并使用您计划使用的scanf变体扫描它们,并验证它们是否不同

单精度冲突的第一个数字是131072.02,这是否正确?(正,考虑小数点后的两位数作为尾数)


我想知道这个推理是否正确,是吗

对于刚好小于131072.0f的值,每个连续可表示的
float
值相隔1/128

对于[131072.0f…2*131072.0f]范围内的值,每个连续可表示的
float
值相隔1/64

具有十进制文本形式“131072.xx”的值,有100个组合,但只有64个不同的
float
。出现100-64或36也就不足为奇了-见下文。对于这种形式的数字,这是第一位
float
的密度太稀疏了:
float
>中的最低有效位在此范围内为0.01




可能也会有帮助。

@JHBonarius你看到他在哪里抱怨?作为背景,请阅读以下内容:还有这些:(不是我的博客)“即从20.00 hz到22000.00 hz”-你应该使用的是一个以厘米为单位的整数。这个整数的范围是
[20002200000]
,并且没有任何舍入问题。为什么?因为
浮点值
甚至不能正确表示
0.2
!“尾数最多两位数(即从20.00 hz到22000.00 hz)”.Eh,不,那不是两位数尾数。
22000.00Hz
将是
2.200000E5
,所以尾数是7位数,指数是一位数。除了计算机使用基数2和位数的小位,而不是基数10和位数。OP测试不一致的原因解释(累积误差)这将改善这个答案。@Yakk这就是你的意思吗?因为我们处理的是相对精度,所以没有必要处理问题中发现的不精确分数。瞧,使用24位,所有高达1600万的整数都可以用单精度浮点精确表示。因为OP只需要2200000厘赫兹,这就足够了“错误累积”并不重要,因为结果(如打印)说明了该错误。错误累积只意味着
131072
不会在
13107200
的精确步骤中达到。@MSalters:错误累积确实重要(一般来说,在这种情况下不一定与特定的数字相关)。这意味着
valueDouble
中的值将不是OP打算测试的数字。理论上,可能存在某个点,测试的数字应该是a和b,但错误会导致测试a+e0和b+e1,而a和b冲突,而a+e0和b+e1不冲突,反之亦然,因此结果是错误的。冲突测试是d一个在打印任何内容之前,所以输出的转换对测试没有影响。@EricPostpischil:这没有意义。
x
x+0.01
发生碰撞,只要
x+0.01
仅在第25位不同。回想加法的工作原理:最小的数字的指数调整为最大的数字,移动其mant右边是issa。只要移动25位,你就加上0,然后发生冲突。所有具有相同大小的数字
x
都将导致相同的“移动25位,加上0”"@mAlters:首先,如果您有
x
x+.01
,它们之间的差异不超过.01,因为
x+.01
在二进制浮点中必然有舍入错误。因此
x
x+0.01
可能在第25位不同,而
x
x
+.01在第25位没有差异。第二,即使这两种情况下e对
x
x+.01
和对
x
x
+.01在第25位不同,
x
有一些累积误差,它取代了我们在程序中此时的理想y,y和y+.01在第25位可能没有差异,即使
x
x
+.0我同意,反之亦然。
int main(void) {
  volatile float previous = 0.0;
  for (long i = 1; i <= 99999999; i++) {
    volatile float f1 = i / 100.0;
    if (previous == f1) {
      volatile float f0 = nextafterf(f1, 0);
      volatile float f2 = nextafterf(f1, f1 * 2);
      printf("%f %f %f    delta fraction:%f\n", f0, f1, f2, 1.0 / (f1 - f0));
      static int count = 100 - 64;
      if (--count == 0) return 0;
    }
    previous = f1;
  }
  printf("Done\n");
}
131072.000000 131072.015625 131072.031250    delta fraction:64.000000
131072.031250 131072.046875 131072.062500    delta fraction:64.000000
131072.046875 131072.062500 131072.078125    delta fraction:64.000000
...
131072.921875 131072.937500 131072.953125    delta fraction:64.000000
131072.937500 131072.953125 131072.968750    delta fraction:64.000000
131072.968750 131072.984375 131073.000000    delta fraction:64.000000