我遇到了cudnnBatchNormalizationForwardTraining
函数中使用的CUDNN_BN_MIN_EPSILON值的问题(请参阅the docs here),结果是因为我传递了float
值{{ 1}}而不是double(我使用1e-5f
值来节省内存并加快计算速度),这个值一旦转换为float,就会略小于float
,这是实际的该常数的值。
经过一些反复试验,我找到了一个不错的近似值,我现在正在使用:
1e-5
我确信有更好的方法可以解决这类问题,所以问题是:
给定一个正的
const float CUDNN_BN_MIN_EPSILON = 1e-5f + 5e-13f;
值,找到最小可能double
值的最佳方式(以及#34;可靠")方式是什么(单独使用以及何时/何时)转换为float
)严格大于初始double
值?
制定此问题的另一种方法是,给定double
值double
和d1
值float
,f1
应尽可能少负值(否则它意味着d1 - (float)f1
少而不是d1,这不是我们正在寻找的东西。
我做了一些基本的试验和错误(使用f1
作为我的目标值):
1e-5
使用此近似值,最终的// Check the initial difference
> 1e-5 - 1e-5f
2,5262124918247909E-13 // We'd like a small negative value here
// Try to add the difference to the float value
> 1e-5 - (1e-5f + (float)(1e-5 - 1e-5f))
2,5262124918247909E-13 // Same, probably due to approximation
// Double the difference (as a test)
> 1e-5 - (1e-5f + (float)((1e-5 - 1e-5f) * 2))
-6,5687345259044915E-13 // OK
值为float
,看起来不错。
但是,1,00000007E-05
乘法在我的结尾完全是任意的,我不确定它是否可靠或是最佳可能的事情。
有没有更好的方法来实现这一目标?
谢谢!
编辑:这是我现在使用的(坏)解决方案,很乐意用更好的解决方案替换它!
* 2
解决方案:这是最终正确的实施(感谢John Bollinger):
/// <summary>
/// Returns the minimum possible upper <see cref="float"/> approximation of the given <see cref="double"/> value
/// </summary>
/// <param name="value">The value to approximate</param>
public static float ToApproximatedFloat(this double value)
=> (float)value + (float)((value - (float)value) * 2);
答案 0 :(得分:3)
在C:
#include <math.h>
float NextFloatGreaterThan(double x)
{
float y = x;
if (y <= x) y = nexttowardf(y, INFINITY);
return y;
}
如果您不想使用库例程,请将上面的nexttowardf(y, INFINITY)
替换为-NextBefore(-y)
,其中NextBefore
取自this answer并已修改:
double
更改为float
,将DBL_
更改为FLT_
。.625
更改为.625f
。fmax(SmallestPositive, fabs(q)*Scale)
替换为SmallestPositive < fabs(q)*Scale ? fabs(q)*Scale : SmallestPositive
。fabs(q)
替换为(q < 0 ? -q : q)
。(显然,例程可以从-NextBefore(-y)
转换为NextAfter(y)
。这是读者的练习。)
答案 1 :(得分:0)
由于您似乎对表示级详细信息感兴趣,因此您将依赖于类型float
和double
的表示形式。然而,在实践中,很可能归结为基本的&#34; binary32&#34;和&#34; binary64&#34;格式为IEEE-754。它们具有一个符号位的一般形式,几个偏置指数位和一组有效位,包括对于归一化值,一个隐含位有效数。
给定IEEE-754二进制64格式的double
,其值不小于+2 -126 ,您要做的是
以可直接检查和操作的形式获取原始double
值的位模式。例如,作为无符号的64位整数。
double d = 1e-5;
uint64_t bits;
memcpy(&bits, &d, 8);
提取并重新偏置指数字段
uint64_t exponent = ((bits >> 52) & 0x7FF) - 1023 + 127;
提取有效位并截断多余的
uint64_t significand = (bits >> 29) & 0x7fffff;
以32位无符号整数格式汇总结果
uint32_t float_bits = ((bits >> 32) & 0x80000000u)
| (exponent << 23)
| significand;
添加一个。由于您希望结果严格大于原始double
,因此无论所有截断的有效位数是否为0,这都是正确的。如果加法溢出有效位,则将正确递增指数字段。但是,它可能产生无穷大的位模式。
float_bits += 1;
将位模式存储/复制/重新解释为float
float f;
memcpy(&f, &float_bits, 4);
给定binary64格式的负double
,其大小不小于2 -126 ,请按照上述步骤操作,但从float_bits
减去1而不是添加一个。请注意,对于-2 -126 ,这会产生一个二次正常的二进制32(见下文),这是正确的结果。
IEEE 754提供非常小幅度的非零数的精确度表示。这种表示称为次正规。在某些情况下,超过给定输入binary64的最小二进制32是一个次正规,包括一些不是二进制64次正规的输入。
此外,IEEE 754提供带符号的零,-0是一种特殊情况:严格大于-0(任一格式)的最小二进制32是最小的正次正规数。注意:不是+0,因为根据IEEE 754,+ 0和-0通过正常的比较运算符进行比较。最小正,非零,次正规binary32值具有位模式0x00000001。
受这些考虑因素影响的二进制64值具有偏差的二进制64指数字段,其值小于或等于二进制64指数偏差和二进制32指数偏差之间的差值(896)。这包括具有精确为0的有偏差指数的那些,其表征二进制64零和子正规。在简单案例程序中检查重组步骤应该使您正确地得出结论,该程序将对此类输入产生错误的结果。
这些案件的代码留作练习。
具有偏置的binary64指数字段集的所有位的输入表示正无穷大(当binary64有效位数没有设置位时)或非数字(NaN)值。 Binary64 NaNs和正无穷大应该转换为它们的binary32当量。负无穷大可能应该转换为最大量级的负二进制32值。这些需要作为特殊情况处理。
这些案件的代码留作练习。