【问题标题】:Unit tests for 64 bit division64位除法的单元测试
【发布时间】:2018-12-17 11:32:45
【问题描述】:

我的任务是对 64 位除法函数进行单元测试(我们已经在 PPC 的汇编程序中对其进行了编码,这并不重要)。

到目前为止,我有:

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

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

【问题讨论】:

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


    【解决方案1】:

    在没有任何实现知识的情况下,可以选择可能暴露典型错误源的模式,例如在位或字之间传播进位失败,或舍入中的极端情况。典型模式:

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

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

    根据我的经验,这些简单的模式在清除各种算术运算(包括整数除法)中的错误方面非常有效。这篇经典笔记讨论了这种测试模式的应用:

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

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

    #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 类生成测试向量的合适方法是通过随机除数的乘法(选择一个小的随机乘数)和乘加法(选择一个小的随机加数)构造被除数。

    一般而言,现代计算机的速度足以支持 232 到 248 个测试用例,具体取决于系统速度和参考实现的速度。您可能希望使用大量基于模式的、有针对性的随机和纯随机文本向量来合理地创建正确的 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)
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-09-14
      • 2017-09-23
      • 1970-01-01
      • 2010-09-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-03-29
      相关资源
      最近更新 更多