【问题标题】:How to compute total floating-point rounding error of a set of arithmetic computations in java using Math.ulp(double)?如何使用 Math.ulp(double) 在 java 中计算一组算术计算的总浮点舍入误差?
【发布时间】:2016-03-31 18:10:57
【问题描述】:

我想使用 Java 中的 Math.ulp(double) 方法计算一系列加法、乘法和除法的浮点舍入误差。根据最后位置单元 (ULP) 上的 wiki 页面,似乎来自一个浮点计算的错误,比如 2+3 或 2*3 将是 0.5*ulp(2+3) 或 0.5*ulp( 2*3),其中 2*3 和 2+3 是浮点计算。但是,将这些错误加起来并不能解释我在最终产品中得到的实际错误。例如,说最大错误 2+3*4 = 0.5*ulp(2+[3*4]) + 0.5*ulp(3*4) 似乎并不能解释我得到的实际错误。因此,我很困惑,也许我误解了 Math.ulp(double) 或者我需要使用某种相对错误。我不知道。谁能向我解释一下,也许可以举几个浮点数和精确数的加法、乘法和除法的例子?将不胜感激。

我正在尝试为 Matrix 类计算矩阵的简化行梯形形式,并且我需要知道,经过几次计算后,我用于计算的二维数组中的某些项目是否相等到 0。如果一行全为零,我退出代码。如果其中有一个非零数,我将这个数除以它自己,然后执行高斯消元。问题是,在执行一系列操作后,浮点错误可能会蔓延,并且应该导致零的计算最终会变成一个非零数,这会扰乱我的矩阵计算。因此,我试图将发生高斯消除的条件从零更改为小于计算的误差范围,并且我正在根据对矩阵中每个项目所做的计算计算矩阵中每个项目的误差范围,并将其加在一起新的错误数组。 这是我的代码:

/**
 * Finds the reduced row echelon form of the matrix using partial pivoting
 * @return rref: The reduced row echelon form of the matrix
 */
public Matrix rref()
{
    //ref()
    Matrix ref = copy();
    int iPivot = 0, jPivot = 0, greatestPivotRow;
    double[][] errorArray = new double[height][width];
    while(iPivot < height && jPivot < width)
    {
        do
        {
            //Finds row with greatest absolute-value-of-a-number at the horizontal value of the pivot position
            greatestPivotRow = iPivot;
            for(int n = iPivot; n < height; n++)
            {
                if(Math.abs(ref.getVal(n, jPivot)) > Math.abs(ref.getVal(greatestPivotRow, jPivot)))
                    greatestPivotRow = n;
            }
            //Swaps row at pivot with that row if that number is not 0 (Or less than the floating-point error)
            //If the largest number is 0, all numbers below in the column are 0, so jPivot increments and row swapper is repeated
            if(Math.abs(ref.getVal(greatestPivotRow, jPivot)) > errorArray[greatestPivotRow][jPivot])
                ref = ref.swapRows(iPivot, greatestPivotRow);
            else
                jPivot++;
        }
        while(jPivot < width && Math.abs(ref.getVal(greatestPivotRow, jPivot)) <= errorArray[greatestPivotRow][jPivot]); 
        if(jPivot < width)
        {
            //Pivot value becomes 1
            double rowMultiplier1 = 1/ref.getVal(iPivot,jPivot);
            for(int j = jPivot; j < width; j++)
            {
                ref.matrixArray[iPivot][j] = ref.getVal(iPivot,j) * rowMultiplier1;
                errorArray[iPivot][j] += 0.5 * (Math.ulp(ref.matrixArray[iPivot][j]) + Math.ulp(rowMultiplier1));
            }
            //1st value in nth row becomes 0
            for(int iTarget = iPivot + 1; iTarget < height; iTarget++)
            {
                double rowMultiplier0 = -ref.getVal(iTarget, jPivot)/ref.getVal(iPivot, jPivot);
                for(int j = jPivot; j < width; j++)
                {
                    errorArray[iTarget][j] += 0.5 * (Math.ulp(ref.getVal(iPivot, j) * rowMultiplier0) + Math.ulp(ref.getVal(iTarget, j)
                            + ref.getVal(iPivot, j)*rowMultiplier0) + Math.ulp(rowMultiplier0));
                    ref.matrixArray[iTarget][j] = ref.getVal(iTarget, j)
                            + ref.getVal(iPivot, j)*rowMultiplier0;
                }
            }
        }
        //Shifts pivot down 1 and to the right 1
        iPivot++;
        jPivot++;
    }

    //rref
    Matrix rref = ref.copy();
    iPivot = 1;
    jPivot = 1;
    //Moves pivot along the diagonal
    while(iPivot < height && jPivot < width)
    {
        //Moves horizontal position of pivot to first nonzero number in the row (the 1)
        int m = jPivot;
        while(m < width && Math.abs(rref.getVal(iPivot, m)) < errorArray[iPivot][m])
            m++;
        if(m != width)
        {
            jPivot = m;
            //1st value in rows above pivot become 0
            for(int iTarget = 0; iTarget < iPivot; iTarget++)
            {
                double rowMultiplier = -rref.getVal(iTarget, jPivot)/rref.getVal(iPivot, jPivot);
                for(int j = jPivot; j < width; j++)
                {
                    errorArray[iTarget][j] += 0.5 * (Math.ulp(rref.getVal(iTarget, j) * rowMultiplier) + Math.ulp(rref.getVal(iTarget, j)
                            + rref.getVal(iPivot, j)*rowMultiplier) + Math.ulp(rowMultiplier));
                    rref.matrixArray[iTarget][j] = rref.getVal(iTarget, j)
                            + rref.getVal(iPivot, j)*rowMultiplier;
                }
            }
        }
        iPivot++;
        jPivot++;
    }
    //Get rid of floating-point errors in integers
    for(int i = 0; i < height; i++)
    {
        for(int j =0; j < width; j++)
        {
            if(Math.abs(rref.getVal(i, j) - (int)(rref.getVal(i, j) + 0.5)) <= errorArray[i][j])
                rref.matrixArray[i][j] = (int)(rref.getVal(i, j) + 0.5);
        }
    }
    return rref;
}

代码的最后一部分,将小于计算误差的浮点数从整数值转换为该整数值,主要是为了告诉我我的错误公式是否有效,因为我正在计算的一些矩阵结束向上,而不是整数,5.000000000000004s 等。因此,我知道如果我有一个非常接近整数但不是整数的数字,我也知道我的误差范围不够大,显然它们不是,所以我认为我做错了。

我的输入矩阵是一个带有实例变量的矩阵

double[][] matrixArray = {{1,-2,0,0,3}, {2,-5,-3,-2,6}, {0,5,15,10,0}, {2,6,18,8,6}};

我的结果是数组

[[1.0, 0.0, 0.0, -2.0000000000000013, 3.0], [0.0, 1.0, 0.0, -1.0000000000000004, 0.0], [0.0, 0.0, 1.0, 1.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0]]

虽然我的错误计算解决了将零变成一然后用于高斯消元的问题,但我仍然有不是整数的数字,所以我知道我的错误界限不准确。在这种情况下它可能有效,但如果没有正确的错误界限,它可能不会在下一个。

【问题讨论】:

  • 您是否尝试计算 exact 错误,而不仅仅是错误的界限?就 ulp 或任何东西而言,不太可能有一个简单的公式。 (在任何情况下,结果
  • 是的,我正在尝试计算误差范围。
  • 那么您使用的公式有什么问题?这些示例中的实际误差将小于您计算的范围。
  • 确定浮点运算序列的(严格)错误界限是一个非常重要的过程,并且已经编写了整本书来解决这个问题的各个方面,来自 JH Wilkinson 的“舍入错误”在代数过程中”(1965 年)到 Nicholas J. Higham 的“数值算法第二版的准确性和稳定性”。 (2002 年)。因此,我觉得这个问题太宽泛了,而且只与编程无关,但暂时不要进行近距离投票(也许有人可以将答案压缩成典型的 SO 答案格式)。
  • @abeta201 为了使您的问题更具体,您可能希望显示完整的代码,列出每个输入的可能范围,并说明您的预期和观察到的界限。

标签: java floating-point linear-algebra floating-accuracy


【解决方案1】:

如果您对计算高斯消除过程的误差范围感兴趣,那将是一个非常复杂的问题。例如,本文给出了误差上限的公式: Higham NJ,Higham DJ。旋转高斯消除中的大增长因子。 SIAM 矩阵分析和应用杂志。 1989;10(2):155.

公式为:

这绝不简单!

另一方面,如果您的目标是防止缓慢的浮点错误破坏您的零,我认为您甚至不需要创建 errorArray[][]。您可以通过浮点计算然后在 Math.ulp() 或机器 epsilon 的帮助下设置精度条件来完成。这样一来,您最终就不需要最后的循环来“摆脱”那些讨厌的零了!

你也可以使用java的BigDecimal,看看能不能得到更好的结果。也许this question 及其给出的答案会有所帮助。

【讨论】:

  • 不知道要不要进入无限矩阵范数。我只是想知道我对一系列加法、乘法或除法的最大误差计算是否准确。我想这是你提到的“精确条件”,我需要一个例子。我没有使用 Math.ulp 的经验。不过,结束循环只是为了确保我的函数正常工作——我在条件语句中避免了讨厌的零。 (虽然结束循环确实让我的结果更漂亮,但我不得不说)。我的代码可以正常工作,但这并不意味着我的错误分析正确。
【解决方案2】:

2+3*4 = 0.5*ulp(2+[3*4]) + 0.5*ulp(3*4)

错误复合。像利息一样,最终的误差可以成倍增长。您的示例中的操作是精确的,因此很难看出您在抱怨什么(当然您确实得到了 14 个?)。您是否考虑了导致计算中涉及的常数不是数学值而是它们的 0.5ULP 近似值的表示错误?

除了在以必要的精度静态计算时误差呈指数增长之外,还有一个问题是您使用不准确的浮点数学来计算误差:

errorArray[iTarget][j] += 0.5 * (Math.ulp(rref.getVal(iTarget, j) * rowMultiplier) + Math.ulp(rref.getVal(iTarget, j)

实际误差可能不止由该语句计算得出,因为没有什么能阻止浮点加法成为数学结果的较低近似值(乘法恰好可能是精确的,因为其中一个被乘数是 2 的幂每个案例)。

在另一种编程语言中,您可以将舍入模式更改为“向上”以进行此计算,但 Java 不提供对此功能的访问。


这里有一堆相关的评论:

当数学上预期的结果是整数时,获得作为该整数的 double 的通常方法是确保整个计算的 1ULP 错误。对于涉及多个操作的计算,您几乎永远不会获得 1ULP 界限,除非您采取特殊步骤来确保此界限(例如 Dekker multiplication)。

Java 可以使用常量并在hexadecimal format 中打印结果,如果您想查看到底发生了什么,您应该使用它。

如果您有兴趣在特定计算中获得最终误差的上限,而不是静态地为所有计算,那么interval arithmetic 比将误差表征为单个绝对值更准确,并且需要很多少思考。在您通过其他方式知道结果必须是整数的上下文中,如果结果区间仅包含一个整数,您肯定会知道这是唯一可能的答案。

【讨论】:

    猜你喜欢
    • 2013-10-13
    • 2015-08-13
    • 2018-09-01
    • 2012-04-29
    • 2021-09-01
    • 2019-03-27
    • 1970-01-01
    • 1970-01-01
    • 2015-09-02
    相关资源
    最近更新 更多