进行64位除法的单元测试

时间:2018-12-17 11:32:45

标签: unit-testing 64-bit integer-division

我负责对64位除法功能(我们已经在PPC的汇编程序中进行了编码,没关系)进行单元测试。

到目前为止,我有:

  • 最大值div最大值
  • 最大值div最小值
  • 最大值div减去最大值
  • 最大值div zer0
  • 零div最大值
  • 6 div 2
  • 6 div -2
  • -6 div 2
  • -6 div -2
  • 1 div 1
  • 11 div 4

要对这个新代码进行彻底的单元测试,我缺少什么?

1 个答案:

答案 0 :(得分:0)

在不了解实现的情况下,可以选择可能暴露典型错误源的模式,例如在位或单词之间传播进位失败或四舍五入的情况。典型模式:

  1. 2的幂:2 i
  2. 2的幂减1:2 i -1
  3. 2的幂乘以1:2 i +1
  4. 任意两个2的幂的和:2 i + 2 j
  5. 任何两个2的幂的差:2 i -2 j
  6. 一个模式1到5的补码。
  7. 任何模式1.到5.的二进制补码。

显然,模式类之间存在重叠,这允许使用“烟雾”测试形式的很少的测试向量进行快速检查,以及使用大量的测试向量进行深度检查。由于除法有两个论点,因此除数和除数的模式也应从不同的模式类创建。

以我的经验,这些简单的模式对于清除包括整数除法在内的所有算术运算中的错误都非常有效。在经典注释中讨论了这种测试模式的应用:

N。 L. Schryer,“计算机浮点算术单元的测试”。 1981年2月4日,AT&T贝尔实验室计算科学技术报告第89号,(online)

在我的x64机器上,使用以下所有C模式代码对简单的64位按位除法实现进行测试需要花费一分钟以上的时间:

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <limits.h>

int64_t v[32768]; /* FIXME: size appropriately */

#define ADDcc(a,b,cy,t0,t1) \
  (t0=(b), t1=(a), t0=t0+t1, cy=t0<t1, t0=t0)
#define ADDC(a,b,cy,t0,t1) \
  (t0=(b)+cy, t1=(a), t0+t1)

/* bit-wise non-restoring two's complement division */
void my_div_core (int64_t dividend, int64_t divisor, int64_t *quot, int64_t *rem)
{
    const int operand_bits = (int) (sizeof (int64_t) * CHAR_BIT);
    uint64_t d = (uint64_t)divisor;
    uint64_t nd = 0 - d; /* -divisor */
    uint64_t r, q = 0; /* remainder, quotient */
    uint64_t dd = d;  /* divisor */
    uint64_t cy, t0, t1;
    int i;

    q = dividend;
    r = (dividend < 0) ? (~0) : 0;  // sign-extend
    for (i = operand_bits - 1; i >= 0; i--) {
        if ((int64_t)(r ^ dd) < 0) {
            /* shift 128-bit quantity q:r left by 1 bit */
            q = ADDcc (q, q, cy, t0, t1);
            r = ADDC  (r, r, cy, t0, t1);
            r += dd;
            q += 0; /* record quotient bit -1 (as 0) */
        } else {
            /* shift 128-bit quantity q:r left by 1 bit */
            q = ADDcc (q, q, cy, t0, t1);
            r = ADDC  (r, r, cy, t0, t1);
            r -= dd;
            q += 1; /* record quotient bit +1 (as 1) */
        }
    }

    /* convert quotient from digit set {-1,1} to plain two's complement */
    q = (q << 1) + 1;

    /* fix up cases where we worked past a partial remainder of zero */
    if (r == d) { /* remainder equal to divisor */
        q = q + 1;
        r = 0;
    } else if (r == nd) { /* remainder equal to -divisor */
        q = q - 1;
        r = 0;
    }

    /* for truncating division, remainder must have same sign as dividend */
    if (r && ((int64_t)(dividend ^ r) < 0)) {
        if ((int64_t)q < 0) {
            q = q + 1;
            r = r - d;
        } else {
            q = q - 1;
            r = r + d;
        }
    }

    *quot = (int64_t)q;
    *rem  = (int64_t)r;
}

int64_t my_div (int64_t dividend, int64_t divisor)
{
    int64_t quot, rem;
    my_div_core (dividend, divisor, &quot, &rem);
    return quot;
}

int main (void)
{
    int64_t dividend, divisor, quot, ref;
    int i, j, patterns, idx = 0, nbrBits = sizeof (uint64_t) * CHAR_BIT;

    /* pattern class 1: 2**i */
    for (i = 0; i < nbrBits; i++) {
        v [idx] = (int64_t)((uint64_t)1 << i);
        idx++;
    }
    /* pattern class 2: 2**i-1 */
    for (i = 0; i < nbrBits; i++) {
        v [idx] = (int64_t)(((uint64_t)1 << i) - 1);
        idx++;
    }
    /* pattern class 3: 2**i+1 */
    for (i = 0; i < nbrBits; i++) {
        v [idx] = (int64_t)(((uint64_t)1 << i) + 1);
        idx++;
    }
    /* pattern class 4: 2**i + 2**j */
    for (i = 0; i < nbrBits; i++) {
        for (j = 0; j < nbrBits; j++) {
            v [idx] = (int64_t)(((uint64_t)1 << i) + ((uint64_t)1 << j));
            idx++;
        }
    }
    /* pattern class 5: 2**i - 2**j */
    for (i = 0; i < nbrBits; i++) {
        for (j = 0; j < nbrBits; j++) {
            v [idx] = (int64_t)(((uint64_t)1 << i) - ((uint64_t)1 << j));
            idx++;
        }
    }
    patterns = idx;
    /* pattern class 6: one's complement of pattern classes 1 through 5 */
    for (i = 0; i < patterns; i++) {
        v [idx] = ~v [i];
        idx++;
    }
    /* pattern class 7: two's complement of pattern classes 1 through 5 */
    for (i = 0; i < patterns; i++) {
        v [idx] = -v [i];
        idx++;
    }
    patterns = idx;

    for (i = 0; i < patterns; i++) {
        for (j = 0; j < patterns; j++) {
            dividend = v [i];
            divisor  = v [j];
            /* exclude cases with undefined results: division by zero, overflow*/
            if (!((divisor == 0LL) || 
                  ((dividend == (-0x7fffffffffffffffLL - 1LL)) && (divisor == -1LL)))) {
                quot = my_div (dividend, divisor);
                ref = dividend / divisor;
                if (quot != ref) {
                    printf ("error @ (%016llx, %016llx): quot = %016llx  ref=%016llx\n", 
                            dividend, divisor, quot, ref);
                    return EXIT_FAILURE;
                }
            }
        }
    }
    printf ("64-bit division: test passed\n");
    return EXIT_SUCCESS;
}

除了上述模式测试之外,您还需要添加 targeted 个伪随机测试向量,尤其要关注以下场景:

  1. 小除数
  2. 小商
  3. 小余数

生成第2类和第3类测试向量的一种合适方法是,通过从随机除数中进行乘法(选择一个小的随机乘数)和乘法加法(选择一个小的随机加数)来构造除数。

通常,现代计算机的速度足以允许2 32 和2 48 个测试用例,具体取决于系统的速度和参考实现的速度。您可能要使用大量基于模式的,有针对性的随机和纯随机文本向量,以有合理的机会创建正确的64位除法。让测试运行一天或至少一整夜。

使用大量随机测试向量需要高质量的PRNG(伪随机数生成器)。乔治·马尔萨里亚(George Marsaglia)教授的KISS64将是一个最低标准:

/*
  From: geo <gmars...@gmail.com>
  Newsgroups: sci.math,comp.lang.c,comp.lang.fortran
  Subject: 64-bit KISS RNGs
  Date: Sat, 28 Feb 2009 04:30:48 -0800 (PST)

  This 64-bit KISS RNG has three components, each nearly
  good enough to serve alone.    The components are:
  Multiply-With-Carry (MWC), period (2^121+2^63-1)
  Xorshift (XSH), period 2^64-1
  Congruential (CNG), period 2^64
*/

static uint64_t kiss64_x = 1234567890987654321ULL;
static uint64_t kiss64_c = 123456123456123456ULL;
static uint64_t kiss64_y = 362436362436362436ULL;
static uint64_t kiss64_z = 1066149217761810ULL;
static uint64_t kiss64_t;

#define MWC64  (kiss64_t = (kiss64_x << 58) + kiss64_c, \
                kiss64_c = (kiss64_x >> 6), kiss64_x += kiss64_t, \
                kiss64_c += (kiss64_x < kiss64_t), kiss64_x)
#define XSH64  (kiss64_y ^= (kiss64_y << 13), kiss64_y ^= (kiss64_y >> 17), \
                kiss64_y ^= (kiss64_y << 43))
#define CNG64  (kiss64_z = 6906969069ULL * kiss64_z + 1234567ULL)
#define KISS64 (MWC64 + XSH64 + CNG64)