解决浮点舍入问题C ++

时间:2011-04-11 22:01:33

标签: c++ scientific-computing matrix-multiplication floating-point-precision

我开发了一种科学应用(模拟在细胞核中移动的染色体)。染色体被分成小片段,使用4x4旋转矩阵围绕随机轴旋转。

问题在于模拟执行了数千亿次旋转,因此浮点舍入误差会呈指数级增长并逐渐增长,因此随着时间的推移,碎片往往会“漂浮”并与染色体的其余部分分离。

我在C ++中使用双精度。软件暂时在CPU上运行,但将被移植到CUDA,模拟最多可持续1个月。

我不知道我怎么能重新规范染色体,因为所有的片段被链接在一起(你可以看作是一个双重链接列表),但我认为这是最好的主意,如果可能的话。

你有什么建议吗?我觉得有点失落。

非常感谢,

小时。

编辑: 添加了简化的示例代码。 您可以假设所有矩阵数学都是经典实现。

// Rotate 1000000 times
for (int i = 0; i < 1000000; ++i)
{
    // Pick a random section start
    int istart = rand() % chromosome->length;

    // Pick the end 20 segments further (cyclic)
    int iend = (istart + 20) % chromosome->length;

    // Build rotation axis
    Vector4 axis = chromosome->segments[istart].position - chromosome->segments[iend].position;
    axis.normalize();

    // Build rotation matrix and translation vector
    Matrix4 rotm(axis, rand() / float(RAND_MAX));
    Vector4 oldpos = chromosome->segments[istart].position;

    // Rotate each segment between istart and iend using rotm
    for (int j = (istart + 1) % chromosome->length; j != iend; ++j, j %= chromosome->length)
    {
        chromosome->segments[j].position -= oldpos;
        chromosome->segments[j].position.transform(rotm);
        chromosome->segments[j].position += oldpos;
    }
}

7 个答案:

答案 0 :(得分:9)

您需要为您的系统找到一些约束,并努力将其保持在一定的合理范围内。我做了一堆分子碰撞模拟,在那些系统中总能量是守恒的,所以每一步我都要仔细检查系统的总能量,如果它变化了一些阈值,那么我就知道我的时间步长选择不当(太大或太小)我选择一个新的时间步骤并重新运行它。这样我就可以实时跟踪系统发生的情况。

对于这个模拟,我不知道你有多少守恒量,但如果你有一个,你可以尝试保持不变。请记住,缩短时间步长并不总能提高精度,您需要使用精度来优化步长。我已经进行了几周CPU时间的数值模拟,并且保存量总是在10 ^ 8中的1个部分内,所以有可能,你只需要玩一些。

另外,正如Tomalak所说,也许会尝试始终将您的系统引用到开始时间而不是上一步。因此,不要总是移动你的染色体,将染色体保持在它们的起始位置,并与它们一起存储一个转换矩阵,将你带到当前位置。计算新旋转时,只需修改变换矩阵即可。这可能看起来很愚蠢,但有时这很有效,因为误差平均为0.

例如,假设我有一个位于(x,y)的粒子和我计算的每一步(dx,dy)并移动粒子。逐步的方式就是这样做

t0 (x0,y0)
t1 (x0,y0) + (dx,dy) -> (x1, y1)
t2 (x1,y1) + (dx,dy) -> (x2, y2)
t3 (x2,y2) + (dx,dy) -> (x3, y3)
t4 (x3,30) + (dx,dy) -> (x4, y4)
...

如果你总是引用t0,你可以这样做

t0 (x0, y0) (0, 0)
t1 (x0, y0) (0, 0) + (dx, dy) -> (x0, y0) (dx1, dy1)
t2 (x0, y0) (dx1, dy1) + (dx, dy) -> (x0, y0) (dx2, dy2)
t3 (x0, y0) (dx2, dy2) + (dx, dy) -> (x0, y0) (dx3, dy3)

所以在任何时候,tn,要获得你必须做的真实位置(x0,y0)+(dxn,dyn)

现在像我的例子一样进行简单的翻译,你可能不会赢得很多。但对于轮换,这可以节省生命。只需保留一个与每个染色体相关的欧拉角矩阵,然后更新它而不是染色体的实际位置。至少这样他们就不会飘走了。

答案 1 :(得分:4)

编写公式,以便时间步长T的数据不仅仅来自时间步长T-1中的浮点数据。尽量确保浮点错误的产生仅限于单个时间步。

如果没有更具体的问题需要解决,很难说出更具体的内容。

答案 2 :(得分:1)

问题描述相当含糊,所以这里有一些相当含糊的建议。

选项1:

找到一些约束条件,以便(1)它们应该始终保持,(2)如果它们失败,但只是公正,很容易调整系统以便它们这样做,(3)如果它们全部保持然后你的模拟并没有变得非常疯狂,并且(4)当系统开始变得疯狂时,约束开始失败但只是轻微。例如,对于某些d,染色体的相邻位之间的距离可能最多为d,如果一些距离稍微大于d,那么您可以(例如)从一端沿着染色体走,修复通过将下一个片段与其前任以及所有后继片段一起移动而使任何距离过大。或者其他什么。

然后经常检查约束,以确保捕获时任何违规行为仍然很小;当你发现违规行为时,请解决问题。 (你应该安排一下,当你解决问题时,你“不仅仅满足”约束。)

如果一直检查约束是否便宜,那么你当然可以这样做。 (这样做也可能使您能够更便宜地进行修正,例如,如果这意味着任何违规行为总是很小。)

选项2:

找到一种描述系统状态的新方法,使问题不可能出现。例如,也许(我怀疑这一点)你可以为每个相邻的片段对存储一个旋转矩阵,并强制它总是一个正交矩阵,然后让片段的位置由这些旋转矩阵隐式确定。 / p>

选项3:

不是将约束视为约束,而是提供一些小的“恢复力”,这样当某些事情变得不合时,它往往会被拉回原来的状态。请注意,当没有任何问题时,恢复力为零或至少可以忽略不计,这样它们不会比原始数值误差更严重地扰乱您的结果。

答案 3 :(得分:0)

我认为这取决于您使用的编译器。

Visual Studio编译器支持/ fp开关,它告诉浮点运算的行为

你可以read more about it。基本上,/ fp:strict是最严厉的模式

答案 4 :(得分:0)

我想这取决于所需的精度,但你可以使用基于'整数'的浮点数。使用这种方法,您可以使用整数并为小数位数提供自己的偏移量。

例如,精度为4个小数点,您将拥有

浮动值 - &gt; int值 1.0000 - &gt; 10000 1.0001 - &gt; 10001 0.9999 - &gt; 09999

当你进行乘法和除法时必须小心,并且在应用精确偏移时要小心。另外,你可以很快得到溢出错误。

1.0001 * 1.0001变为10001 * 10001/10000

答案 5 :(得分:0)

如果我正确读取了这段代码,任何两个相邻染色体片段之间的距离都不会发生变化。在这种情况下,在主循环计算每对相邻点之间的距离之前,在主循环之后,如果需要,移动每个点以与前一点保持适当的距离。

您可能需要在主循环期间多次强制执行此约束,具体取决于具体情况。

答案 6 :(得分:0)

基本上,您需要避免从这些(不精确的)矩阵运算符中积累错误,并且在大多数应用程序中有两种主要方法。

  1. 您可以在N次操作之后写出操作员将明确指出的内容,而不是将位置写为多次操作的初始位置。例如,假设您有一个位置x,并且您正在添加一个值e(您无法准确表示。)比计算x + = e好多了;大量的时间是计算x + EN;其中EN是一种更准确的方式来表示N次后操作发生的情况。您应该考虑是否有一些方法可以更准确地表示多次旋转的动作。
  2. 稍微更人为的是采取新发现的点并预测与旋转中心的预期半径之间的任何差异。这将保证它不会漂移(但不一定能保证旋转角度准确。)