按位随机舍入
bitwise stochastic rounding
我有一段 C 代码可以将 binary64 值随机舍入为 binary32。问题是我不太完全理解代码。我知道它直接对浮点数的位进行运算,但我无法理解发生了什么。能否请您与我分享一些见解?
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 = <em>(uint64_t</em>)&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
significand 有 53 位(在 main significand 字段中编码为 52),所以差值为 29。因此,清除 a double
编码中的低 29 位将产生一个可以精确表示为 a [=29 的数字=],如果指数在 float
范围内。
清除这些位的目的可能是防止在转换为 float
的过程中进行第二次向上舍入,如下所示。上一行中的加法 temp += r;
可能导致进位进入有效数的高位,因此意图可能是确保数字只增加一个单位,而不是两个。
return (float)*(double *)&temp;
与上面的初始行一样,这是将位重新解释为 double
的错误尝试。 (之后它被强制转换为 float
,这对于单独的标准 C 来说是不必要的,因为 return
语句的操作数会自动转换为函数的 return 类型,但是,如果严格使用代码检查,它可以消除有关缩小转换的警告。)正确的代码应该是 memcpy(&x, &temp, sizeof x); return x;
或 return (union { uint64_t u; double d }) { temp } .u;
.
我有一段 C 代码可以将 binary64 值随机舍入为 binary32。问题是我不太完全理解代码。我知道它直接对浮点数的位进行运算,但我无法理解发生了什么。能否请您与我分享一些见解?
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 = <em>(uint64_t</em>)&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
significand 有 53 位(在 main significand 字段中编码为 52),所以差值为 29。因此,清除 a double
编码中的低 29 位将产生一个可以精确表示为 a [=29 的数字=],如果指数在 float
范围内。
清除这些位的目的可能是防止在转换为 float
的过程中进行第二次向上舍入,如下所示。上一行中的加法 temp += r;
可能导致进位进入有效数的高位,因此意图可能是确保数字只增加一个单位,而不是两个。
return (float)*(double *)&temp;
与上面的初始行一样,这是将位重新解释为 double
的错误尝试。 (之后它被强制转换为 float
,这对于单独的标准 C 来说是不必要的,因为 return
语句的操作数会自动转换为函数的 return 类型,但是,如果严格使用代码检查,它可以消除有关缩小转换的警告。)正确的代码应该是 memcpy(&x, &temp, sizeof x); return x;
或 return (union { uint64_t u; double d }) { temp } .u;
.