Bre*_*ett 3 c precision double casting
在我看过的一些音频库中,音频样本通常表示为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 doubles有 53 位尾数(52 位存储和最高位隐含),浮点数有 24 位(23 位存储,最高位隐含)。
编辑:(感谢您的反馈,我希望这更清楚)
当整数转换为 double 时double f = (double)1024;,该数以适当的指数 (1023+10) 保存,并且相同的位模式有效地存储为原始整数(实际上 IEEE 二进制浮点数不存储最高位。IEEE 浮点数)通过调整指数,点数被“标准化”为最高位 = 1,然后最高位 1 被修剪掉,因为它是“隐含的”,这节省了一些存储空间)。
一个 32 位整数将需要一个 double 来完美地保存它的值,而一个 8 位整数将完美地保存在一个浮点数中。那里没有信息丢失。它可以转换回整数而不会丢失。损失发生在算术和分数上。
除非代码这样做,否则整数不会映射到 +/-1。当代码将存储为双精度的 32 位整数相除以将其映射到范围 +/-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 <stdio.h>
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;
}
Run Code Online (Sandbox Code Playgroud)
输出是:
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]
Run Code Online (Sandbox Code Playgroud)
这表明一个完整的 32 位 int 被精确表示,而 0.1 则不是。它表明 printf 并没有准确打印浮点数,而是舍入或截断(需要注意的事情)。它还说明了 0.1 表示中的错误量在 1,000,000 次加法操作中没有累积到足够大的值以导致 printf 打印它。它表明可以通过舍入恢复原始整数,但不能通过赋值来恢复,因为赋值会截断。它表明减法运算可以“放大”错误(减法之后剩下的就是错误),因此应该仔细分析算术。
把它放在音乐的背景下,采样率可能是 96KHz。在错误累积到足以导致前 32 位包含超过 1 位错误之前,需要超过 10 秒的添加时间。
更远。创建 Ogg 和 Vorbis 的 Christopher “Monty” Montgomery 在一篇关于音乐、采样率和样本分辨率的文章中认为 24 位对于音频应该绰绰有余24/192 音乐下载......以及为什么它们没有意义
摘要
double 完美地保存了 32 位整数。有 N/M 形式的有理十进制数(其中 M 和 N 可以用 32 位整数表示),它们不能用二进制分数位的有限序列表示。因此,当一个整数被映射到范围 +/-1,因此被转换为一个有理数 (N/M) 时,一些数字不能用双精度小数部分中的有限位数来表示,所以错误会蔓延在。
这些错误通常非常小,位于最低位,因此远低于高 32 位。所以它们可以使用四舍五入在整数和双精度之间来回转换,双精度表示的错误不会导致整数错误。但是,算术可以改变错误。错误构造的算术会导致错误迅速增长,并可能增长到原始整数值已被破坏的幅度。
其他想法:如果精度很重要,还有其他方法可以使用双打。它们都不像映射到 +/-1 那样方便。我能想到的一切都需要跟踪算术运算,最好使用 C++ 包装类来完成。这将大大减慢计算速度,因此可能毫无意义。
这是通过将算术包装在跟踪额外信息的类中来进行“自动微分”的一种非常狡猾的方式。我认为那里的想法可能会激发一种方法。它甚至可能有助于确定精度丢失的地方。