我正在处理一个问题,我有大量 16 位有符号整数的集合。我必须将每个乘以 double 类型的常数因子,然后promote将其转换回整数类型。我需要检查溢出和下溢,如果发生这种情况,结果应设置为整数限制,并应打印警告,通知用户有关问题的信息。
我想出了一个使用 C 的解决方案,但不太喜欢它。我也质疑检查的效率。基本上,我正在寻找“最佳实践”方法。
int16_t *sample = malloc(2);
for (unsigned long i = 1; fread(sample, 2, 1, inputf); i++)
{
// Check overflow/underflow
if (fabs(INT16_MAX / (double) *sample) < fabs(factor))
{
if (copysign(1.0, factor) * *sample > 0)
{
printf("Overflow in sample #%li\n", i);
*sample = INT16_MAX;
}
else
{
printf("Underflow in sample #%li\n", i);
*sample = INT16_MIN;
}
}
else
{
*sample = (int16_t) (*sample * factor);
}
/* Do stuff with sample */
}
free(sample);
我已经考虑过整数的饱和/包装,但根据这个问题,依赖饱和或包装被认为是有符号整数的未定义行为。我也遇到过这个问题,但是天哪。
int16_t
的范围是[−32,768,+32,767],因为它是16位二进制补码类型(这些限制在C 2018 7.20.2.1 1中指定)。当浮点值转换为整数类型时,它会被截断 (6.3.1.4 1),因此开区间 (−32,769, +32,768) 内的所有值都有定义的结果(不会溢出),而开区间之外的值溢出(C 标准没有定义该行为)。
C 标准对
float
的要求是可以表示从 -32,769 到 +32,768 的所有整数。根据 5.2.4.2.2 14,1 邻域中可表示数字之间的间距最多为 10− 5,因此 32,769 邻域中的间距最多为 32,769•10−5 = .32769。此外,5.2.4.2.2 13 告诉我们这在 float
格式的范围内(至少 1037)。因此可以表示最大 32,769 的整数。 float
值是每 6.2.5 10 的 double
值的子集。
因此,我们可以通过以下方式执行所需的缩放、测试和转换:
float t = *sample * factor;
if (t <= -INT16_MIN - 1.f)
{
fprintf(stderr, "Warning, underflow in sample #%lu.\n", i);
*sample = INT16_MIN;
}
else if (INT16_MAX + 1.f <= t)
{
fprintf(stderr, "Warning, overflow in sample #%lu.\n", i);
*sample = INT16_MAX;
}
else
*sample = t;
上面使用
float
因为它足以满足范围,但是如果需要在 double
或乘积的表示中获得更高的精度,则可以使用 factor
。
常用于
double
的 IEEE-754 二进制 64 格式就足够了,因为它的 53 位有效数确保可以表示高达 253 的整数。
但是,C 标准本身并不能保证这一点。它仅保证 1 附近的间距为 10−9,并且 32 位
int
的值可能高达 2,147,483,647,这不足以保证可以按照与上述相同的方式进行测试。对于浮点值到整数类型的一般可移植转换,此答案提供了安全代码。
为了提高性能,代码不需要在循环中执行任何浮点数学。
在循环之前找到限制
为了简单起见,我们首先假设
factor > 0
。
#define CONVERT16(sample, factor) (int16_t)((sample) * (factor)))
#define CONVERT32(sample, factor) (int32_t)((sample) * (factor)))
// Range of allowable samples, inclusive.
int sample_min = INT16_MIN;
int sample_max = INT16_MAX;
if (factor > 1.0) {
// Rough approximation
sample_min = (int16_t) (INT16_MIN / factor);
sample_max = (int16_t) (INT16_MAX / factor);
// Refine
// This step may not be needed - will have to ponder this more.
// Yet since its is before the loop, the cost is minor.
if (sample_min > INT16_MIN && CONVERT32(sample_min - 1, factor) >= INT16_MIN) {
sample_min--;
if (sample_max < INT16_MAX && CONVERT32(sample_max + 1, factor) <= INT16_MAX) {
sample_max--;
}
}
// Now in the loop simply perform integer compares.
for (unsigned long i = 1; fread(sample, 2, 1, inputf); i++) {
// min test
if (*sample < sample_min) {
printf("Underflow in sample #%li\n", i);
*sample = INT16_MIN;
} else if (*sample > sample_max) {
printf("Overflow in sample #%li\n", i);
*sample = INT16_MAX;
}
*sample = CONVERT16(*sample, factor);
/* Do stuff with sample */
}
将 OP 留给代码,以了解何时
factor <= 0.0