-1.0和1.0之间的双倍精度是多少?

在我看过的一些音频库中,音频样本通常表示为double或float,范围为-1.0到1.0。 在某些情况下,这很容易让分析和综合代码抽象出底层数据类型实际可能是什么(签名long int,unsigned char等)。

假设IEEE 754,我们的密度不均匀。 随着数量接近零,密度增加。 这意味着我们对接近-1和1的数字的精度较低。

如果我们可以为我们要转换的基础数据类型表示足够数量的值,则此非均匀数密度无关紧要。

例如,如果底层数据类型是unsigned char,我们只需要介于-1和1(或8位)之间的256个值 – 使用double显然不是问题。

我的问题是:我有多少精度? 我可以安全地转换为32位整数,而不会丢失吗? 为了扩展这个问题,在没有丢失的情况下,安全地转换为32位整数/从32位整数转换的值必须是多少?

谢谢!

对于IEEE双精度数,你有一个53位的尾数,这足以表示被认为是-1(0x80000000)和1 – 2 ^ -31(0x7FFFFFFF)之间的固定点数的32位整数。

浮子有24位mantissae,这是不够的。

正如Alexandre C.解释的那样, IEEE双精度具有53位尾数(存储52位且顶部位隐含),并且浮点数具有24位(存储23位,顶部位隐含)。

编辑:(感谢您的反馈,我希望这更清楚)

当一个整数转换为double double f = (double)1024; ,数字用适当的指数(1023 + 10)保持,并且相同的位模式被有效地存储为原始整数(实际IEEE二进制浮点不存储最高位.IEEE浮点数被’标准化’以具有顶部位= 1,通过调整指数,然后顶部1被修剪掉,因为它是“隐含的”,这节省了一点存储空间。

32位整数将需要一个double来完美地保持其值,并且8位整数将完美地保存在float中。 那里没有信息丢失。 它可以转换回整数而不会丢失。 算术和小数值会发生损失。

除非代码执行,否则整数不会映射到+/- 1。 当代码将32位整数(存储为double)存储为将其映射到+/- 1范围时,很可能会引入错误。

这种映射到+/- 1将失去一些53位精度,但错误只会在最低位,远低于原始整数所需的32位。 后续操作也可能失去精确度。 例如,将两个数相乘,结果范围超过53位精度将丢失一些位(即乘以两个数,尾数超过27个有效位)。

可能有用的浮点解释是“每个计算机科学家应该知道的关于浮点算术的内容”它解释了一些反直觉(对我而言)浮点数的行为。

例如,数字0.1 不能精确地保存在IEEE二进制浮点双精度中。

该程序可能会帮助您了解正在发生的事情:

 /* Demonstrate IEEE 'double' encoding on x86 * Show bit patterns and 'printf' output for double values * Show error representing 0.1, and accumulated error of adding 0.1 many times * G Bulmer 2012 */ #include  typedef struct { unsigned long long mantissa :52; unsigned exponent :11; unsigned sign :1; } double_bits; const unsigned exponent_offset = 1023; typedef union { double d; unsigned long long l; double_bits b; } Xlate; void print_xlate(Xlate val) { const long long IMPLIED = (1LL<<52); if (val.b.exponent == 0) { /* zero? */ printf("val: d: %19lf bits: %016llX [sign: %u exponent: zero=%u mantissa: %llX]\n", val.d, val.l, val.b.sign, val.b.exponent, val.b.mantissa); } else { printf("val: d: %19lf bits: %016llX [sign: %u exponent: 2^%4-d mantissa: %llX]\n", val.d, val.l, val.b.sign, ((int)val.b.exponent)-exponent_offset, (IMPLIED|val.b.mantissa)); } } double add_many(double d, int many) { double accum = 0.0; while (many-- > 0) { /* only works for +d */ accum += d; } return accum; } int main (int argc, const char * argv[]) { Xlate val; val.b.sign = 0; val.b.exponent = exponent_offset+1; val.b.mantissa = 0; print_xlate(val); val.d = 1.0; print_xlate(val); val.d = 0.0; print_xlate(val); val.d = -1.0; print_xlate(val); val.d = 3.0; print_xlate(val); val.d = 7.0; print_xlate(val); val.d = (double)((1LL<<31)-1LL); print_xlate(val); val.d = 2147483647.0; print_xlate(val); val.d = 10000.0; print_xlate(val); val.d = 100000.0; print_xlate(val); val.d = 1000000.0; print_xlate(val); val.d = 0.1; print_xlate(val); val.d = add_many(0.1, 100000); print_xlate(val); val.d = add_many(0.1, 1000000); print_xlate(val); val.d = add_many(0.1, 10000000); print_xlate(val); val.d = add_many(0.1,10); print_xlate(val); val.d *= 2147483647.0; print_xlate(val); int i = val.d; printf("int i=truncate(d)=%d\n", i); int j = lround(val.d); printf("int i=lround(d)=%d\n", j); val.d = add_many(0.0001,1000)-0.1; print_xlate(val); return 0; } 

输出是:

 val: d: 2.000000 bits: 4000000000000000 [sign: 0 exponent: 2^1 mantissa: 10000000000000] val: d: 1.000000 bits: 3FF0000000000000 [sign: 0 exponent: 2^0 mantissa: 10000000000000] val: d: 0.000000 bits: 0000000000000000 [sign: 0 exponent: zero=0 mantissa: 0] val: d: -1.000000 bits: BFF0000000000000 [sign: 1 exponent: 2^0 mantissa: 10000000000000] val: d: 3.000000 bits: 4008000000000000 [sign: 0 exponent: 2^1 mantissa: 18000000000000] val: d: 7.000000 bits: 401C000000000000 [sign: 0 exponent: 2^2 mantissa: 1C000000000000] val: d: 2147483647.000000 bits: 41DFFFFFFFC00000 [sign: 0 exponent: 2^30 mantissa: 1FFFFFFFC00000] val: d: 2147483647.000000 bits: 41DFFFFFFFC00000 [sign: 0 exponent: 2^30 mantissa: 1FFFFFFFC00000] val: d: 10000.000000 bits: 40C3880000000000 [sign: 0 exponent: 2^13 mantissa: 13880000000000] val: d: 100000.000000 bits: 40F86A0000000000 [sign: 0 exponent: 2^16 mantissa: 186A0000000000] val: d: 1000000.000000 bits: 412E848000000000 [sign: 0 exponent: 2^19 mantissa: 1E848000000000] val: d: 0.100000 bits: 3FB999999999999A [sign: 0 exponent: 2^-4 mantissa: 1999999999999A] val: d: 10000.000000 bits: 40C388000000287A [sign: 0 exponent: 2^13 mantissa: 1388000000287A] val: d: 100000.000001 bits: 40F86A00000165CB [sign: 0 exponent: 2^16 mantissa: 186A00000165CB] val: d: 999999.999839 bits: 412E847FFFEAE4E9 [sign: 0 exponent: 2^19 mantissa: 1E847FFFEAE4E9] val: d: 1.000000 bits: 3FEFFFFFFFFFFFFF [sign: 0 exponent: 2^-1 mantissa: 1FFFFFFFFFFFFF] val: d: 2147483647.000000 bits: 41DFFFFFFFBFFFFF [sign: 0 exponent: 2^30 mantissa: 1FFFFFFFBFFFFF] int i=truncate(d)=2147483646 int i=lround(d)=2147483647 val: d: 0.000000 bits: 3CE0800000000000 [sign: 0 exponent: 2^-49 mantissa: 10800000000000] 

这表示完全表示完整的32位int,而0.1表示不完整。 它表明printf不会精确打印浮点数,而是打圆或截断(需要警惕的事情)。 它还说明了0.1表示中的错误量不会累积到1,000,000个添加操作中足够大的值,导致printf打印它。 它表明原始整数可以通过舍入来恢复,但不能分配,因为赋值截断。 它表明减法运算可以“放大”误差(在减法之后留下的所有误差),因此应仔细分析算术。

将其置于音乐环境中,采样率可能为96KHz。 在错误累积到足以导致前32位错误包含多于1位之前,需要超过10秒的添加时间。

进一步。 创建Ogg和Vorbis的Christopher“Monty”Montgomery认为,在一篇关于音乐,采样率和样本分辨率24/192音乐下载的文章中,24位对于音频来说应该足够了......以及为什么它们毫无意义

摘要
double完美地保存32位整数。 存在N / Mforms的有理十进制数(其中M和N可以由32位整数表示),其不能由二进制分数位的有限序列表示。 因此,当一个整数映射到+/- 1的范围,因此被转换为有理数(N / M)时,某些数字不能用双倍小数部分中的有限位数表示,因此误差将会发生变化在。

这些错误通常非常小,位于最低位,因此远低于高32位。 因此,它们可以使用舍入在整数和双精度之间来回转换,并且双重表示的错误不会导致整数错误。 算术可以改变错误。 错误构造的算法可能导致错误迅速增长,并且可能会增长到原始整数值已损坏的幅度。

其他想法:如果精度至关重要,还有其他方法可以使用双精度。 它们都不像映射到+/- 1那样方便。 我能想到的一切都需要跟踪算术运算,最好用C ++包装类来完成。 这会大大减慢计算速度,因此可能毫无意义。

这是一种非常偷偷摸摸的方式,通过在跟踪额外信息的类中包装算术来进行“自动差异化” 。 我认为那里的想法可能激发一种方法。 它甚至可以帮助确定精度丢失的位置。