[英]How to compute total floating-point rounding error of a set of arithmetic computations in java using Math.ulp(double)?
我想使用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]]
尽管我的错误计算解决了将零变成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)
实际误差可能超出此语句所计算的范围,因为没有什么可以防止浮点加法成为数学结果的较低近似值(乘法可能恰好是精确的,因为在每种情况下被乘数之一是二的幂) 。
在另一种编程语言中,您可以将舍入模式更改为“向上”以进行此计算,但是Java不提供对此功能的访问。
以下是一些切线相关的说明:
当数学上期望的结果是整数时,获取该整数的双精度数的常用方法是确保整个计算的1ULP误差。 您几乎永远都不会为涉及多个操作的计算获得1ULP界限,除非您采取特殊措施确保这一界限(例如Dekker乘法 )。
Java可以使用常量并以十六进制格式打印结果,如果要确切查看正在发生的情况,则应使用该常量。
如果您有兴趣在特定计算中获得最终误差的上限(而不是在所有计算中都是静态的),则间隔算术比将误差表征为单个绝对值要精确得多,并且所需的思考要少得多。 在通过其他方式得知结果必须为整数的情况下,如果结果间隔仅包含一个整数,则可以肯定这是唯一可能的答案。
如果您对计算高斯消除过程的误差范围感兴趣,那么这是一个非常复杂的问题。 例如,本文给出了误差上限的公式: Higham NJ,Higham DJ。 高斯消除中枢的大增长因素。 SIAM矩阵分析和应用杂志。 1989; 10(2):155。
这绝非易事!
另一方面,如果您的目标是防止蠕变浮点错误破坏您的零,我认为您甚至不需要创建errorArray [] [] 。 您可以通过计算浮点数然后通过Math.ulp()或机器epsilon设置精度条件来做得很好。 这样,您将不需要最终循环来“摆脱”那些讨厌的零。
您还可以使用Java的BigDecimal
查看是否获得更好的结果。 也许这个问题及其给出的答案会有所帮助。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.