如何使用Math.ulp(double)计算java中一组算术计算的总浮点舍入误差?

如何使用Math.ulp(double)计算java中一组算术计算的总浮点舍入误差?,java,floating-point,linear-algebra,floating-accuracy,Java,Floating Point,Linear Algebra,Floating Accuracy,我想使用Java中的Math.ulp(double)方法计算一系列加法、乘法和除法的浮点舍入误差。根据维基页面上的最后一位单元(ULP),一次浮点计算的误差,比如说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(doub

我想使用Java中的Math.ulp(double)方法计算一系列加法、乘法和除法的浮点舍入误差。根据维基页面上的最后一位单元(ULP),一次浮点计算的误差,比如说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),或者我需要使用某种相对错误。我不知道。有人能给我解释一下吗?也许能举几个浮点数和精确数的加法、乘法和除法的例子?非常感谢

我试图计算一个矩阵类的矩阵的简化行梯队形式,我需要知道,经过一些计算后,我用于计算的二维数组中的某些项是否等于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;
}
我的结果是数组

[[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]]
虽然我的错误计算修复了将零变成1然后用于高斯消去的问题,但我仍然有非整数的数字,因此我知道我的错误界限不准确。它可能在这种情况下工作,但如果没有正确的错误界限,在下一种情况下可能无法工作

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不提供对此功能的访问


以下是一系列与此相关的评论:

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

Java可以在中使用常量和打印结果,如果您想确切地了解发生了什么,应该使用这些常量


如果您对沿着特定计算获得最终误差的上限感兴趣,而不是对所有计算进行静态分析,那么它比将误差描述为单个绝对值要精确得多,并且需要更少的思考。在通过其他方式知道结果必须是整数的情况下,如果结果间隔仅包含一个整数,则可以确定这是唯一可能的答案。

如果您对计算高斯消去过程的误差范围感兴趣,这是一个非常复杂的问题。例如,本文给出了一个关于误差上界的公式: 新泽西州海安姆,DJ海安姆。旋转高斯消去法中的大增长因子。暹罗矩阵分析和应用杂志。1989;10(2):155

公式是:

这绝非易事

另一方面,如果您的目标是防止逐渐蔓延的浮点错误破坏您的零,那么我认为您甚至不需要创建errorArray[][]。通过使用浮点运算,然后在Math.ulp()或machine epsilon的帮助下设置精度条件,您可以做得很好。这样,你就不需要最后一个循环来“摆脱”那些讨厌的零了


您还可以使用java的
BigDecimal
,看看是否能得到更好的结果。也许,它给出的答案会有所帮助。

你是在试图计算准确的误差,而不仅仅是误差的界限吗?就ulp或其他方面而言,不太可能有一个简单的公式。(在任何情况下,+,-和*对结果小于2^52的整数都不会有任何错误。)是的,我正在尝试计算误差范围。那么您使用的公式有什么问题?这些示例中的实际误差将小于您正在计算的范围。确定浮点运算序列的(紧密)误差范围是一个非常重要的过程,从J.H.Wilkinson的“代数中的舍入误差”开始,已经编写了整本书来解决这个问题的各个方面
errorArray[iTarget][j] += 0.5 * (Math.ulp(rref.getVal(iTarget, j) * rowMultiplier) + Math.ulp(rref.getVal(iTarget, j)