处理整数和浮点乘法中的上溢和下溢

问题描述 投票:0回答:2

我正在处理一个问题,我有大量 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);

我已经考虑过整数的饱和/包装,但根据这个问题,依赖饱和或包装被认为是有符号整数的未定义行为。我也遇到过这个问题,但是天哪。

floating-point integer overflow multiplication underflow
2个回答
0
投票

16 位整数

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,这不足以保证可以按照与上述相同的方式进行测试。对于浮点值到整数类型的一般可移植转换,此答案提供了安全代码


0
投票

为了提高性能,代码不需要在循环中执行任何浮点数学。

在循环之前找到限制

为了简单起见,我们首先假设

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

© www.soinside.com 2019 - 2024. All rights reserved.