C++ 中的双双算术工作,OpenGL 中的相同代码没有

Double-double-arithmetic in C++ working, same code in OpenGL does not

提问人:Paul Aner 提问时间:1/1/2022 最后编辑:Rabbid76Paul Aner 更新时间:1/2/2022 访问量:230

问:

我已经在 OpenGL 中实现了一个 mandelbrot-explorer。对于更深的缩放,我正在尝试实现双倍算术。我在 C++ 中有工作代码,但是 - 如果我没有忽略某些东西 - 完全相同的代码只会在 GLSL 中产生双精度。

用于乘法的 C++ 代码:

igdd quickTwoSum(double a, double b)
    {
        igdd Temp;
        Temp.hi = a + b;
        Temp.lo = b - (Temp.hi - a);
        return Temp;
    }

igdd Split64(double d)
    {
        const double SPLITTER = (1 << 29) + 1;
        double t = d * SPLITTER;
        igdd Temp;
        Temp.hi = t - (t - d);
        Temp.lo = d - Temp.hi;
        return Temp;
    }

igdd twoProd(double a, double b)
    {
        igdd p;
        p.hi = a * b;
        igdd aS = Split64(a);
        igdd bS = Split64(b);
        p.lo = ((aS.hi * bS.hi - p.hi) + aS.hi * bS.lo + aS.lo * bS.hi + aS.lo * bS.lo);
        return p;
    }

igdd operator*(igdd a)
    {
        igdd Temp;
        Temp = twoProd(a.hi, this->hi);
        Temp.lo += a.hi * this->lo + a.lo * this->hi;
        Temp = quickTwoSum(Temp.hi, Temp.lo);
        return Temp;
    }

Open-GL 代码:

dvec2 Split64(double d)
{
    const double SPLITTER = (1 << 29) + 1;
    double t = d * SPLITTER;
    dvec2 result;
    result.x = t - (t - d);
    result.y = d - result.x;

    return result;
}

dvec2 quickTwoSum(double a, double b)
{
    dvec2 result;
    result.x = a + b;
    result.y = b - (result.x - a);
    return result;
}

dvec2 twoProd(double a, double b)
{
    dvec2 p;
    p.x = a * b;
    dvec2 aS = Split64(a);
    dvec2 bS = Split64(b);
    p.y = (aS.x * bS.x - p.x) + aS.x * bS.y + aS.y * bS.x + aS.y * bS.y;
    return p;
}

dvec2 df128_mul(dvec2 a, dvec2 b)
{
    dvec2 p;

    p = twoProd(a.x, b.x);
    p.y += a.x * b.y + a.y * b.x;
    p = quickTwoSum(p.x, p.y);
    return p;
}

出于测试目的,我在 C++ 中都这样做了

igdd Testdd; // The double-double class in C++
Testdd.lo = 0.0000000000000000000000001;
Testdd.hi = 1.0;
std::cout.precision(17);
for (int i = 0; i < 95; i++)
{
    Testdd = Testdd * Testdd;
    std::cout << std::fixed << Testdd.hi << std::endl;
}

...和 OpenGL

dvec2 XR;
XR.y = 0.0000000000000000000000001;
XR.x = 1.0;
for (i = 0; i < 95; i++) // I tried different values for 95 here
{
    XR = df128_mul(XR, XR);
}

如果我继续对数字进行平方,在 C++ 中的某个时候,您可以看到它变大(在最初它太多的零之后)。GLSL 总是产生一个(或零 - 我在输出前做了 number-1)。

那么,GLSL 中的双打(或者也许)是否发生了一些奇怪的事情,或者我是否忽略了什么?另外:当用双双计算曼德布罗特集时,CPU 真的很慢。GPU 生成完全正确的图像(直到我放大到大约 10^-14),并且速度快得令人怀疑......dvec2

C++ GLSL 双算术

评论

1赞 Thomas 1/1/2022
OpenGL 不是一种语言。你是说GLSL?
0赞 Paul Aner 1/1/2022
是的,当然。
3赞 derhass 1/1/2022
请注意,虽然使用后缀表示单精度文字,并且没有后缀的默认值为 double precison,但 GLSL 使用 float 作为默认精度,并且您必须使用后缀表示双精度。C++flf
1赞 Paul Aner 1/1/2022
@derhass好吧,我不知道。但是在我的代码中,唯一可能产生差异的两行是 XR.y = 0.000000000000000000000000001;XR.x = 1.0;我把“lf”放在后面,但这并没有区别......
1赞 mystery 1/3/2022
“也许默认情况下使用等价物” 没错,GLSL 默认执行所有这些优化,而 C 和 C++ 默认不执行。但正如@PaulAner自己对这个问题的回答所看到的,有一些方法可以禁用它。-ffast-math

答:

2赞 Paul Aner 1/2/2022 #1

我终于找到了问题所在。编译器优化了所需的精度。我找到了另一个产生完全相同结果的乘法代码(双精度 - 不是“双精度”)。出于某种原因,该代码似乎对那个家伙有用,但对我不起作用。

解决问题的是使用“精确”关键字。我用它涂抹了所有东西,现在它可以工作了 - 当然要慢得多:

precise dvec2 quickTwoSum(precise double a, precise double b)
{
    precise dvec2 result;
    result.x = a + b;
    result.y = b - (result.x - a);
    return result;
}

precise dvec2 twoProd(precise double a, precise double b)
{
    precise dvec2 p;
    p.x = a * b;
    precise dvec2 aS = Split64(a);
    precise dvec2 bS = Split64(b);
    p.y = (aS.x * bS.x - p.x) + aS.x * bS.y + aS.y * bS.x + aS.y * bS.y;
    return p;
}

precise dvec2 df128_mul(precise dvec2 a, precise dvec2 b)
{
    precise dvec2 p;
    p = twoProd(a.x, b.x);
    p.y += a.x * b.y + a.y * b.x;
    p = quickTwoSum(p.x, p.y);

    return p;
}