powf()函数的奇怪行为

时间:2019-01-09 04:13:53

标签: matlab cuda pow

以一种意外的方式,powf的类型为int时,会对奇数基数产生奇怪的输出。例如,powf(-4,2)返回16,但powf(-5,2)返回24 !!!

在长时间的计算中找到了错误输出的根后,我发现powf函数在输出类型为integer时对奇数表现出奇怪的行为。

__global__ void intFoo( int* a) 
{
    *a = powf(*a, 2);
}
__global__ void doubleFoo( double* a) 
{
    *a = powf(*a, 2);
}

我可以在Matlab中调用该内核(例如):

!nvcc -ptx test.cu 
k1 = parallel.gpu.CUDAKernel('test.ptx', 'test.cu', 'intFoo');
k2 = parallel.gpu.CUDAKernel('test.ptx', 'test.cu', 'doubleFoo');
out1 = feval(k1, -4)
out2 = feval(k1, -5)
out3 = feval(k2, -4)
out4 = feval(k2, -5)

结果:

out1 = 16
out2 = 24 //This hasn't to be 25 !!??
out3 = 16
out4 = 25.000

编辑:

通过@Robert Crovella的建议在Matlab中进行调查后,我发现Matlab中的Command Window显示out4=25.000,而不是显示out4 = 24.9999981内容的Variables Window。

每个人都应该非常谨慎,因为与powf函数(24.9999981而不是25)的输出相关的小错误可能会传播并成为大型计算的问题< / p>

2 个答案:

答案 0 :(得分:3)

我相信这是由于feval数据类型使用不当所致。

在我看来feval将返回类型转换为与参数类型相同的类型。这是有道理的,因为返回类型是从指向该参数的传递参数的指针中提取的。

请注意,powf接受float个参数并返回一个float,而pow接受double个参数并返回一个doubleint个量在the CUDA math API中没有单独的功能(原型),因此,如果使用它们,它们将在浮点类型之间进行转换。

这就是我在纯CUDA C ++中看到的内容:

$ cat t32.cu
#include <math.h>
#include <stdio.h>

__global__ void Foo( int a, double b)
{
            float res = powf((float)a, 2);
            printf("powf_int: %d, %d, %f\n", a, (int)res, res);
            res = powf((float)b, 2);
            printf("powf_double: %f, %f, %f\n", b, (double)res, res);
            double dres = pow((double)a, 2);
            printf("pow_int: %d, %d, %f\n", a, (int)dres, dres);
            dres = pow((double)b, 2);
            printf("pow_double: %f, %f, %f\n", b, (double)dres, dres);
}

int main(){

        Foo<<<1,1>>>(-5, -5);
        cudaDeviceSynchronize();
}
$ nvcc -o t32 t32.cu
$ cuda-memcheck ./t32
========= CUDA-MEMCHECK
powf_int: -5, 24, 24.999998
powf_double: -5.000000, 24.999998, 24.999998
pow_int: -5, 25, 25.000000
pow_double: -5.000000, 25.000000, 25.000000
========= ERROR SUMMARY: 0 errors
$

请注意:

  1. CUDA powf(-5,2)返回24.999998
  2. 如果我们将其转换为int,则会被截断为24
  3. 如果我们将其转换为double,然后四舍五入到小数点后三位,则正确舍入的结果应为25.000,就像您在matlab输出中显示的一样

建议:

  1. 不要这样做
  2. 请勿将整数类型与浮点函数一起使用(尤其是强制转换结果)
  3. 如果您想平方某物,只需将其与自身相乘即可。它肯定比使用powf(x, 2)更快,并且也可能更准确。

如果您想知道“ CUDA powf(-5, 2)为什么会返回24.999998?”,请在另一个问题中提出。准确度是在programming manual中定义的,我有把握地确定这在已发布的误差范围内。

答案 1 :(得分:2)

作为Robert Crovella的answer的附录,CUDA是C ++的子集,因此提供了重载的数学函数。特别是它提供了pow()的以下四个变体:

float pow (float, int); 
double pow (double, int); 
float pow (float, float); 
double pow (double, double);

如果使用cuobjdump --dump-sass检查为这些变体生成的机器代码,则会发现使用了四种不同的实现。正如罗伯特·克罗维拉(Robert Crovella)所指出的,对于平方的特殊情况,最好只使用乘法,但是如果您愿意的话,当然可以使用pow(),如以下代码所示(为简洁起见,省略了错误检查): / p>

#include <cmath>
#include <cstdlib>
#include <cstdio>

__global__ void kernel (int ib, float fa, float fb, double da, double db)
{
    printf ("pow_float_int     = %15.8e\n", pow (fa, ib));
    printf ("pow_float_float   = %15.8e\n", pow (fa, fb));
    printf ("pow_double_int    = %23.16e\n", pow (da, ib));
    printf ("pow_double_double = %23.16e\n", pow (da, db));
}

int main (void)
{
    int ia = -5, ib = 2;
    float fa = ia, fb = ib;
    double da = ia, db = ib;

    kernel<<<1,1>>>(ib, fa, fb, da, db);
    cudaDeviceSynchronize();
    return EXIT_SUCCESS;
}

以上程序的输出应如下所示:

pow_float_int     =  2.50000000e+01
pow_float_float   =  2.49999981e+01
pow_double_int    =  2.5000000000000000e+01
pow_double_double =  2.5000000000000000e+01