我有这段C代码,它将二进制64的值随机舍入到二进制32. 问题是我不太懂代码。我知道它直接对浮点数进行操作,但我不知道发生了什么。你能告诉我一些真知灼见吗?
float function(double x){
uint64_t temp = *(uint64_t*)&x;
uint32_t r = (rand() * (0xFFFFFFFF/RAND_MAX)) % 0x1FFFFFFF;
temp += r;
temp = temp & 0xFFFFFFFFE0000000;
return (float)*(double *)&temp;
}
位掩码代表什么?(我的直觉告诉我它与指数和尾数如何以二进制格式表示有关,但我无法想象它)
为什么随机变量r是这样计算的?
如何通过代码进行交互?
uint64_t temp =(uint64_t)&x;
这是一个糟糕的尝试,以获得代表double
x
的位。这是不好的,因为它违反了C的混叠规则(C 2018 6.5 7)。正确的代码是uint64_t temp; memcpy(&temp, &x, sizeof temp);
或uint64_t temp = (union { double d; uint64_t u; }) { x } .d;
。前者将x
的字节复制到temp
中,后者使用复合文字创建一个临时对象,该对象是一个联合,它使用该对象来重新解释这些位。这两种方法都被C标准所支持。
uint32_t r = (rand() * (0xFFFFFFFF/RAND_MAX)) % 0x1FFFFFFF;
* (0xFFFFFFFF/RAND_MAX))
尝试将rand
的结果缩放到区间[0,FFFFFFFF16]。它可能做得并不完美。然后% 0x1FFFFFFF
将其减小到区间[0,1fffffff16)。注意闭合的)
和]
——这是一个不包括1FFFFFFF16的半开间隔。这里有一些问题:
- 这可能是一个打字错误;
& 0x1FFFFFFF
将干净地提取低29位,产生完全闭合间隔[0,1fffffff16]的结果。使用%
有不同的结果,没有明显的数学目的,并且强制进行耗时的划分。 - 对于
%
或&
,没有明显的理由首先缩放到FFFFFFFF16;一个人可能会直接进入期望的最终间隔。 - 这只产生阳性结果;这个数字只会增加或保持不变,而不会减少。这可能是人们所希望的,但原因尚不清楚。在这一点和其他方面缺乏文档说明代码质量不高。
temp += r;
这将随机数添加到double
的低位。有时,它会导致进位到上位。(如果上面的位都是1,也可以进到指数域)
temp = temp & 0xFFFFFFFFE0000000;
清除低29位。float
和double
常用的IEEE-754 binary32和binary64格式中,float
有效位为24位(其中23编码在主有效位字段中),double
有效位为53位(其中52编码在主有效位字段中),因此差异为29。因此,如果指数在float
范围内,那么清除double
编码中的低29位将产生一个完全可以表示为float
的数字。
清除这些位的目的可能是为了防止在转换到float
的过程中第二次向上舍入。上一行temp += r;
中的add可能导致了有效位数的上位进位,因此其意图可能是确保数字只增加一个单位,而不是两个。
return (float)*(double *)&temp;
与上面的第一行一样,将比特重新解释为double
是一个错误的尝试。(之后它被强制转换为float
,这对于标准C来说是不必要的,因为return
语句的操作数会自动转换为函数的返回类型,但是,如果使用严格的代码检查,它可能会沉默关于窄化转换的警告。)正确的代码应该是memcpy(&x, &temp, sizeof x); return x;
或return (union { uint64_t u; double d }) { temp } .u;
。