// Multiply n x m Mat A with m x l Mat B to n x l Mat R void lcl_MFastMult(const ScMatrixRef& pA, const ScMatrixRef& pB, const ScMatrixRef& pR,
SCSIZE n, SCSIZE m, SCSIZE l)
{ for (SCSIZE row = 0; row < n; row++)
{ for (SCSIZE col = 0; col < l; col++)
{ // result element(col, row) =sum[ (row of A) * (column of B)]
KahanSum fSum = 0.0; for (SCSIZE k = 0; k < m; k++)
fSum += pA->GetDouble(k,row) * pB->GetDouble(col,k);
pR->PutDouble(fSum.get(), col, row);
}
}
}
}
double ScInterpreter::ScGetGCD(double fx, double fy)
{ // By ODFF definition GCD(0,a) => a. This is also vital for the code in // ScGCD() to work correctly with a preset fy=0.0 if (fy == 0.0) return fx; elseif (fx == 0.0) return fy; else
{ double fz = fmod(fx, fy); while (fz > 0.0)
{
fx = fy;
fy = fz;
fz = fmod(fx, fy);
} return fy;
}
}
void ScInterpreter::ScGCD()
{ short nParamCount = GetByte(); if ( !MustHaveParamCountMin( nParamCount, 1 ) ) return;
double fx, fy = 0.0;
ScRange aRange;
size_t nRefInList = 0; while (nGlobalError == FormulaError::NONE && nParamCount-- > 0)
{ switch (GetStackType())
{ case svDouble : case svString: case svSingleRef:
{
fx = ::rtl::math::approxFloor( GetDouble()); if (fx < 0.0)
{
PushIllegalArgument(); return;
}
fy = ScGetGCD(fx, fy);
} break; case svDoubleRef : case svRefList :
{
FormulaError nErr = FormulaError::NONE;
PopDoubleRef( aRange, nParamCount, nRefInList); double nCellVal;
ScValueIterator aValIter( mrContext, aRange, mnSubTotalFlags ); if (aValIter.GetFirst(nCellVal, nErr))
{ do
{
fx = ::rtl::math::approxFloor( nCellVal); if (fx < 0.0)
{
PushIllegalArgument(); return;
}
fy = ScGetGCD(fx, fy);
} while (nErr == FormulaError::NONE && aValIter.GetNext(nCellVal, nErr));
}
SetError(nErr);
} break; case svMatrix : case svExternalSingleRef: case svExternalDoubleRef:
{
ScMatrixRef pMat = GetMatrix(); if (pMat)
{
SCSIZE nC, nR;
pMat->GetDimensions(nC, nR); if (nC == 0 || nR == 0)
SetError(FormulaError::IllegalArgument); else
{ double nVal = pMat->GetGcd();
fy = ScGetGCD(nVal,fy);
}
}
} break; default : SetError(FormulaError::IllegalParameter); break;
}
}
PushDouble(fy);
}
void ScInterpreter:: ScLCM()
{ short nParamCount = GetByte(); if ( !MustHaveParamCountMin( nParamCount, 1 ) ) return;
double fx, fy = 1.0;
ScRange aRange;
size_t nRefInList = 0; while (nGlobalError == FormulaError::NONE && nParamCount-- > 0)
{ switch (GetStackType())
{ case svDouble : case svString: case svSingleRef:
{
fx = ::rtl::math::approxFloor( GetDouble()); if (fx < 0.0)
{
PushIllegalArgument(); return;
} if (fx == 0.0 || fy == 0.0)
fy = 0.0; else
fy = fx * fy / ScGetGCD(fx, fy);
} break; case svDoubleRef : case svRefList :
{
FormulaError nErr = FormulaError::NONE;
PopDoubleRef( aRange, nParamCount, nRefInList); double nCellVal;
ScValueIterator aValIter( mrContext, aRange, mnSubTotalFlags ); if (aValIter.GetFirst(nCellVal, nErr))
{ do
{
fx = ::rtl::math::approxFloor( nCellVal); if (fx < 0.0)
{
PushIllegalArgument(); return;
} if (fx == 0.0 || fy == 0.0)
fy = 0.0; else
fy = fx * fy / ScGetGCD(fx, fy);
} while (nErr == FormulaError::NONE && aValIter.GetNext(nCellVal, nErr));
}
SetError(nErr);
} break; case svMatrix : case svExternalSingleRef: case svExternalDoubleRef:
{
ScMatrixRef pMat = GetMatrix(); if (pMat)
{
SCSIZE nC, nR;
pMat->GetDimensions(nC, nR); if (nC == 0 || nR == 0)
SetError(FormulaError::IllegalArgument); else
{ double nVal = pMat->GetLcm();
fy = (nVal * fy ) / ScGetGCD(nVal, fy);
}
}
} break; default : SetError(FormulaError::IllegalParameter); break;
}
}
PushDouble(fy);
}
void ScInterpreter::MakeMatNew(ScMatrixRef& rMat, SCSIZE nC, SCSIZE nR)
{
rMat->SetErrorInterpreter( this); // A temporary matrix is mutable and ScMatrix::CloneIfConst() returns the // very matrix.
rMat->SetMutable();
SCSIZE nCols, nRows;
rMat->GetDimensions( nCols, nRows); if ( nCols != nC || nRows != nR )
{ // arbitrary limit of elements exceeded
SetError( FormulaError::MatrixSize);
rMat.reset();
}
}
ScMatrixRef ScInterpreter::GetNewMat(SCSIZE nC, SCSIZE nR, bool bEmpty)
{
ScMatrixRef pMat; if (bEmpty)
pMat = new ScMatrix(nC, nR); else
pMat = new ScMatrix(nC, nR, 0.0);
MakeMatNew(pMat, nC, nR); return pMat;
}
if (nTab1 == nTab2 && pToken)
{ const ScComplexRefData& rCRef = *pToken->GetDoubleRef(); if (rCRef.IsTrimToData())
{ // Clamp the size of the matrix area to rows which actually contain data. // For e.g. SUM(IF over an entire column, this can make a big difference, // But let's not trim the empty space from the top or left as this matters // at least in matrix formulas involving IF(). // Refer ScCompiler::AnnotateTrimOnDoubleRefs() where double-refs are // flagged for trimming.
SCCOL nTempCol = nCol1;
SCROW nTempRow = nRow1;
mrDoc.ShrinkToDataArea(nTab1, nTempCol, nTempRow, nCol2, nRow2);
}
}
if (!ScMatrix::IsSizeAllocatable( nMatCols, nMatRows))
{
SetError(FormulaError::MatrixSize); return nullptr;
}
ScTokenMatrixMap::const_iterator aIter; if (pToken && ((aIter = maTokenMatrixMap.find( pToken)) != maTokenMatrixMap.end()))
{ /* XXX casting const away here is ugly; ScMatrixToken (to which the *resultofthisfunctionusuallyisassigned)shouldnotbeforcedto *carryaScConstMatrixRefthough. *TODO:amatrixalreadystoredinpTokenMatrixMapshouldbe *read-onlyandhaveacopy-on-writemechanism.Previouslyalltokens
* were modifiable so we're already better than before ... */ returnconst_cast<FormulaToken*>((*aIter).second.get())->GetMatrix();
}
if (!bCalcAsShown)
{ // Use fast array fill.
mrDoc.FillMatrix(*pMat, nTab1, nCol1, nRow1, nCol2, nRow2);
} else
{ // Use slower ScCellIterator to round values.
// TODO: this probably could use CellBucket for faster storage, see // sc/source/core/data/column2.cxx and FillMatrixHandler, and then be // moved to a function on its own, and/or squeeze the rounding into a // similar FillMatrixHandler that would need to keep track of the cell // position then.
// Set position where the next entry is expected.
SCROW nNextRow = nRow1;
SCCOL nNextCol = nCol1; // Set last position as if there was a previous entry.
SCROW nThisRow = nRow2;
SCCOL nThisCol = nCol1 - 1;
void ScInterpreter::MEMat(const ScMatrixRef& mM, SCSIZE n)
{
mM->FillDouble(0.0, 0, 0, n-1, n-1); for (SCSIZE i = 0; i < n; i++)
mM->PutDouble(1.0, i, i);
}
/* Matrix LUP decomposition according to the pseudocode of "Introduction to *Algorithms"byCormen,Leiserson,Rivest,Stein. * *Addedscalingfornumericstability. * *GivenannxnnonsingularmatrixA,findapermutationmatrixP,aunit *lower-triangularmatrixL,andanupper-triangularmatrixUsuchthatPA=LU. *ComputeLandU"inplace"inthematrixA,theoriginalcontentis *destroyed.NotethatthediagonalelementsoftheUtriangularmatrix *replacethediagonalelementsoftheL-unitmatrix(thatareeach==1).The *permutationmatrixPisanarray,whereP[i]=jmeansthatthei-throwofP *containsa1incolumnj.Additionallykeeptrackofthenumberof *permutations(rowexchanges). * *Returns0ifasingularmatrixisencountered,else+1ifanevennumberof *permutationsoccurred,or-1ifodd,whichisthesignofthedeterminant. *Thismaybeusedtocalculatethedeterminantbymultiplyingthesignwith *theproductofthediagonalelementsoftheLUmatrix.
*/ staticint lcl_LUP_decompose( ScMatrix* mA, const SCSIZE n,
::std::vector< SCSIZE> & P )
{ int nSign = 1; // Find scale of each row.
::std::vector< double> aScale(n); for (SCSIZE i=0; i < n; ++i)
{ double fMax = 0.0; for (SCSIZE j=0; j < n; ++j)
{ double fTmp = fabs( mA->GetDouble( j, i)); if (fMax < fTmp)
fMax = fTmp;
} if (fMax == 0.0) return0; // singular matrix
aScale[i] = 1.0 / fMax;
} // Represent identity permutation, P[i]=i for (SCSIZE i=0; i < n; ++i)
P[i] = i; // "Recursion" on the diagonal.
SCSIZE l = n - 1; for (SCSIZE k=0; k < l; ++k)
{ // Implicit pivoting. With the scale found for a row, compare values of // a column and pick largest. double fMax = 0.0; double fScale = aScale[k];
SCSIZE kp = k; for (SCSIZE i = k; i < n; ++i)
{ double fTmp = fScale * fabs( mA->GetDouble( k, i)); if (fMax < fTmp)
{
fMax = fTmp;
kp = i;
}
} if (fMax == 0.0) return0; // singular matrix // Swap rows. The pivot element will be at mA[k,kp] (row,col notation) if (k != kp)
{ // permutations
SCSIZE nTmp = P[k];
P[k] = P[kp];
P[kp] = nTmp;
nSign = -nSign; // scales double fTmp = aScale[k];
aScale[k] = aScale[kp];
aScale[kp] = fTmp; // elements for (SCSIZE i=0; i < n; ++i)
{ double fMatTmp = mA->GetDouble( i, k);
mA->PutDouble( mA->GetDouble( i, kp), i, k);
mA->PutDouble( fMatTmp, i, kp);
}
} // Compute Schur complement. for (SCSIZE i = k+1; i < n; ++i)
{ double fNum = mA->GetDouble( k, i); double fDen = mA->GetDouble( k, k);
mA->PutDouble( fNum/fDen, k, i); for (SCSIZE j = k+1; j < n; ++j)
mA->PutDouble( ( mA->GetDouble( j, i) * fDen -
fNum * mA->GetDouble( j, k) ) / fDen, j, i);
}
} #ifdef DEBUG_SC_LUP_DECOMPOSITION
fprintf( stderr, "\n%s\n", "lcl_LUP_decompose(): LU"); for (SCSIZE i=0; i < n; ++i)
{ for (SCSIZE j=0; j < n; ++j)
fprintf( stderr, "%8.2g ", mA->GetDouble( j, i));
fprintf( stderr, "\n%s\n", "");
}
fprintf( stderr, "\n%s\n", "lcl_LUP_decompose(): P"); for (SCSIZE j=0; j < n; ++j)
fprintf( stderr, "%5u ", (unsigned)P[j]);
fprintf( stderr, "\n%s\n", ""); #endif
bool bSingular=false; for (SCSIZE i=0; i<n && !bSingular; i++)
bSingular = (mA->GetDouble(i,i)) == 0.0; if (bSingular)
nSign = 0;
return nSign;
}
/* Solve a LUP decomposed equation Ax=b. LU is a combined matrix of L and U *triangularsandPthepermutationvectorasobtainedfrom *lcl_LUP_decompose().Bistheright-handsideinputvector,Xisusedto *returnthesolutionvector.
*/ staticvoid lcl_LUP_solve( const ScMatrix* mLU, const SCSIZE n, const ::std::vector< SCSIZE> & P, const ::std::vector< double> & B,
::std::vector< double> & X )
{
SCSIZE nFirst = SCSIZE_MAX; // Ax=b => PAx=Pb, with decomposition LUx=Pb. // Define y=Ux and solve for y in Ly=Pb using forward substitution. for (SCSIZE i=0; i < n; ++i)
{
KahanSum fSum = B[P[i]]; // Matrix inversion comes with a lot of zeros in the B vectors, we // don't have to do all the computing with results multiplied by zero. // Until then, simply lookout for the position of the first nonzero // value. if (nFirst != SCSIZE_MAX)
{ for (SCSIZE j = nFirst; j < i; ++j)
fSum -= mLU->GetDouble( j, i) * X[j]; // X[j] === y[j]
} elseif (fSum != 0)
nFirst = i;
X[i] = fSum.get(); // X[i] === y[i]
} // Solve for x in Ux=y using back substitution. for (SCSIZE i = n; i--; )
{
KahanSum fSum = X[i]; // X[i] === y[i] for (SCSIZE j = i+1; j < n; ++j)
fSum -= mLU->GetDouble( j, i) * X[j]; // X[j] === x[j]
X[i] = fSum.get() / mLU->GetDouble( i, i); // X[i] === x[i]
} #ifdef DEBUG_SC_LUP_DECOMPOSITION
fprintf( stderr, "\n%s\n", "lcl_LUP_solve():"); for (SCSIZE i=0; i < n; ++i)
fprintf( stderr, "%8.2g ", X[i]);
fprintf( stderr, "%s\n", ""); #endif
}
// 4th argument is the step number (optional) double nSteps = 1.0; if (nParamCount == 4)
nSteps = GetDoubleWithDefault(nSteps);
// 3d argument is the start number (optional) double nStart = 1.0; if (nParamCount >= 3)
nStart = GetDoubleWithDefault(nStart);
// 2nd argument is the col number (optional)
sal_Int32 nColumns = 1; if (nParamCount >= 2)
{
nColumns = GetInt32WithDefault(nColumns); if (nColumns < 1)
{
PushIllegalArgument(); return;
}
}
// 1st argument is the row number (required)
sal_Int32 nRows = GetInt32WithDefault(1); if (nRows < 1)
{
PushIllegalArgument(); return;
}
if (nGlobalError != FormulaError::NONE)
{
PushError(nGlobalError); return;
}
void ScInterpreter::ScSumProduct()
{ short nParamCount = GetByte(); if ( !MustHaveParamCountMin( nParamCount, 1) ) return;
// XXX NOTE: Excel returns #VALUE! for reference list and 0 (why?) for // array of references. We calculate the proper individual arrays if sizes // match.
if (nBinSize != aBinIndexOrder.size())
{
PushIllegalArgument(); return;
}
SCSIZE j;
SCSIZE i = 0; for (j = 0; j < nBinSize; ++j)
{
SCSIZE nCount = 0; while (i < nDataSize && aDataArray[i] <= aBinArray[j])
{
++nCount;
++i;
}
pResMat->PutDouble(static_cast<double>(nCount), aBinIndexOrder[j]);
}
pResMat->PutDouble(static_cast<double>(nDataSize-i), j);
PushMatrix(pResMat);
}
namespace {
// Helper methods for LINEST/LOGEST and TREND/GROWTH // All matrices must already exist and have the needed size, no control tests // done. Those methods, which names start with lcl_T, are adapted to case 3, // where Y (=observed values) is given as row. // Remember, ScMatrix matrices are zero based, index access (column,row).
// <A;B> over all elements; uses the matrices as vectors of length M double lcl_GetSumProduct(const ScMatrixRef& pMatA, const ScMatrixRef& pMatB, SCSIZE nM)
{
KahanSum fSum = 0.0; for (SCSIZE i=0; i<nM; i++)
fSum += pMatA->GetDouble(i) * pMatB->GetDouble(i); return fSum.get();
}
// Special version for use within QR decomposition. // Euclidean norm of column index C starting in row index R; // matrix A has count N rows. double lcl_GetColumnEuclideanNorm(const ScMatrixRef& pMatA, SCSIZE nC, SCSIZE nR, SCSIZE nN)
{
KahanSum fNorm = 0.0; for (SCSIZE row=nR; row<nN; row++)
fNorm += (pMatA->GetDouble(nC,row)) * (pMatA->GetDouble(nC,row)); return sqrt(fNorm.get());
}
// Euclidean norm of row index R starting in column index C; // matrix A has count N columns. double lcl_TGetColumnEuclideanNorm(const ScMatrixRef& pMatA, SCSIZE nR, SCSIZE nC, SCSIZE nN)
{
KahanSum fNorm = 0.0; for (SCSIZE col=nC; col<nN; col++)
fNorm += (pMatA->GetDouble(col,nR)) * (pMatA->GetDouble(col,nR)); return sqrt(fNorm.get());
}
// Special version for use within QR decomposition. // Maximum norm of column index C starting in row index R; // matrix A has count N rows. double lcl_GetColumnMaximumNorm(const ScMatrixRef& pMatA, SCSIZE nC, SCSIZE nR, SCSIZE nN)
{ double fNorm = 0.0; for (SCSIZE row=nR; row<nN; row++)
{ double fVal = fabs(pMatA->GetDouble(nC,row)); if (fNorm < fVal)
fNorm = fVal;
} return fNorm;
}
// Maximum norm of row index R starting in col index C; // matrix A has count N columns. double lcl_TGetColumnMaximumNorm(const ScMatrixRef& pMatA, SCSIZE nR, SCSIZE nC, SCSIZE nN)
{ double fNorm = 0.0; for (SCSIZE col=nC; col<nN; col++)
{ double fVal = fabs(pMatA->GetDouble(col,nR)); if (fNorm < fVal)
fNorm = fVal;
} return fNorm;
}
// Special version for use within QR decomposition. // <A(Ca);B(Cb)> starting in row index R; // Ca and Cb are indices of columns, matrices A and B have count N rows. double lcl_GetColumnSumProduct(const ScMatrixRef& pMatA, SCSIZE nCa, const ScMatrixRef& pMatB, SCSIZE nCb, SCSIZE nR, SCSIZE nN)
{
KahanSum fResult = 0.0; for (SCSIZE row=nR; row<nN; row++)
fResult += pMatA->GetDouble(nCa,row) * pMatB->GetDouble(nCb,row); return fResult.get();
}
// <A(Ra);B(Rb)> starting in column index C; // Ra and Rb are indices of rows, matrices A and B have count N columns. double lcl_TGetColumnSumProduct(const ScMatrixRef& pMatA, SCSIZE nRa, const ScMatrixRef& pMatB, SCSIZE nRb, SCSIZE nC, SCSIZE nN)
{
KahanSum fResult = 0.0; for (SCSIZE col=nC; col<nN; col++)
fResult += pMatA->GetDouble(col,nRa) * pMatB->GetDouble(col,nRb); return fResult.get();
}
// no mathematical signum, but used to switch between adding and subtracting double lcl_GetSign(double fValue)
{ return (fValue >= 0.0 ? 1.0 : -1.0 );
}
/* Calculates a QR decomposition with Householder reflection. *ForeachNxKmatrixAexistsadecompositionA=Q*Rwithanorthogonal *NxNmatrixQandaNxKmatrixR. *Q=H1*H2*...*HkwithHouseholdermatricesH.Suchahouseholdermatrixcan *bebuildfromavectorubyH=I-(2/u'u)*(uu').Thisvectorsuarereturned *inthecolumnsofmatrixA,overwritingtheoldcontent. *ThematrixRhasaquadricupperpartKxKwithvaluesintheupperright *triangleandzerosinallotherelements.HerethediagonalelementsofR *arestoredinthevectorRandtheotherupperrightelementsintheupper *rightofthematrixA. *Thefunctionreturnsfalse,ifcalculationbreaks.Butbecauseofround-off *errorssingularityisoftennotdetected.
*/ bool lcl_CalculateQRdecomposition(const ScMatrixRef& pMatA,
::std::vector< double>& pVecR, SCSIZE nK, SCSIZE nN)
{ // ScMatrix matrices are zero based, index access (column,row) for (SCSIZE col = 0; col <nK; col++)
{ // calculate vector u of the householder transformation constdouble fScale = lcl_GetColumnMaximumNorm(pMatA, col, col, nN); if (fScale == 0.0)
{ // A is singular returnfalse;
} for (SCSIZE row = col; row <nN; row++)
pMatA->PutDouble( pMatA->GetDouble(col,row)/fScale, col, row);
// same with transposed matrix A, N is count of columns, K count of rows bool lcl_TCalculateQRdecomposition(const ScMatrixRef& pMatA,
::std::vector< double>& pVecR, SCSIZE nK, SCSIZE nN)
{ double fSum ; // ScMatrix matrices are zero based, index access (column,row) for (SCSIZE row = 0; row <nK; row++)
{ // calculate vector u of the householder transformation constdouble fScale = lcl_TGetColumnMaximumNorm(pMatA, row, row, nN); if (fScale == 0.0)
{ // A is singular returnfalse;
} for (SCSIZE col = row; col <nN; col++)
pMatA->PutDouble( pMatA->GetDouble(col,row)/fScale, col, row);
// Calculates means of the columns of matrix X. X is a RxC matrix; // ResMat is a 1xC matrix (=row). void lcl_CalculateColumnMeans(const ScMatrixRef& pX, const ScMatrixRef& pResMat,
SCSIZE nC, SCSIZE nR)
{ for (SCSIZE i=0; i < nC; i++)
{
KahanSum fSum =0.0; for (SCSIZE k=0; k < nR; k++)
fSum += pX->GetDouble(i,k); // GetDouble(Column,Row)
pResMat ->PutDouble( fSum.get()/static_cast<double>(nR),i);
}
}
// Calculates means of the rows of matrix X. X is a RxC matrix; // ResMat is a Rx1 matrix (=column). void lcl_CalculateRowMeans(const ScMatrixRef& pX, const ScMatrixRef& pResMat,
SCSIZE nC, SCSIZE nR)
{ for (SCSIZE k=0; k < nR; k++)
{
KahanSum fSum = 0.0; for (SCSIZE i=0; i < nC; i++)
fSum += pX->GetDouble(i,k); // GetDouble(Column,Row)
pResMat ->PutDouble( fSum.get()/static_cast<double>(nC),k);
}
}
void lcl_CalculateColumnsDelta(const ScMatrixRef& pMat, const ScMatrixRef& pColumnMeans,
SCSIZE nC, SCSIZE nR)
{ for (SCSIZE i = 0; i < nC; i++) for (SCSIZE k = 0; k < nR; k++)
pMat->PutDouble( ::rtl::math::approxSub
(pMat->GetDouble(i,k) , pColumnMeans->GetDouble(i) ) , i, k);
}
void lcl_CalculateRowsDelta(const ScMatrixRef& pMat, const ScMatrixRef& pRowMeans,
SCSIZE nC, SCSIZE nR)
{ for (SCSIZE k = 0; k < nR; k++) for (SCSIZE i = 0; i < nC; i++)
pMat->PutDouble( ::rtl::math::approxSub
( pMat->GetDouble(i,k) , pRowMeans->GetDouble(k) ) , i, k);
}
// Fill default values in matrix X, transform Y to log(Y) in case LOGEST|GROWTH, // determine sizes of matrices X and Y, determine kind of regression, clone // Y in case LOGEST|GROWTH, if constant. bool ScInterpreter::CheckMatrix(bool _bLOG, sal_uInt8& nCase, SCSIZE& nCX,
SCSIZE& nCY, SCSIZE& nRX, SCSIZE& nRY, SCSIZE& M,
SCSIZE& N, ScMatrixRef& pMatX, ScMatrixRef& pMatY)
{
nCX = 0;
nCY = 0;
nRX = 0;
nRY = 0;
M = 0;
N = 0;
pMatY->GetDimensions(nCY, nRY); const SCSIZE nCountY = nCY * nRY; for ( SCSIZE i = 0; i < nCountY; i++ )
{ if (!pMatY->IsValue(i))
{
PushIllegalArgument(); returnfalse;
}
}
// The third parameter may not be missing in ODF, if the forth parameter // is present. But Excel allows it with default true, we too. if (nParamCount >= 3)
{ if (IsMissing())
{
Pop();
bConstant = true; // PushIllegalParameter(); if ODF behavior is desired // return;
} else
bConstant = GetBool();
} else
bConstant = true;
ScMatrixRef pMatX; if (nParamCount >= 2)
{ if (IsMissing())
{ //In ODF1.2 empty second parameter (which is two ;; ) is allowed
Pop();
pMatX = nullptr;
} else
{
pMatX = GetMatrix();
}
} else
pMatX = nullptr;
ScMatrixRef pMatY = GetMatrix(); if (!pMatY)
{
PushIllegalParameter(); return;
}
// 1 = simple; 2 = multiple with Y as column; 3 = multiple with Y as row
sal_uInt8 nCase;
SCSIZE nCX, nCY; // number of columns
SCSIZE nRX, nRY; //number of rows
SCSIZE K = 0, N = 0; // K=number of variables X, N=number of data samples if (!CheckMatrix(_bRKP,nCase,nCX,nCY,nRX,nRY,K,N,pMatX,pMatY))
{
PushIllegalParameter(); return;
}
// Enough data samples? if ((bConstant && (N<K+1)) || (!bConstant && (N<K)) || (N<1) || (K<1))
{
PushIllegalParameter(); return;
}
ScMatrixRef pResMat; if (bStats)
pResMat = GetNewMat(K+1,5, /*bEmpty*/true); else
pResMat = GetNewMat(K+1,1, /*bEmpty*/true); if (!pResMat)
{
PushError(FormulaError::CodeOverflow); return;
} // Fill unused cells in pResMat; order (column,row) if (bStats)
{ for (SCSIZE i=2; i<K+1; i++)
{
pResMat->PutError( FormulaError::NotAvailable, i, 2);
pResMat->PutError( FormulaError::NotAvailable, i, 3);
pResMat->PutError( FormulaError::NotAvailable, i, 4);
}
}
// Uses sum(x-MeanX)^2 and not [sum x^2]-N * MeanX^2 in case bConstant. // Clone constant matrices, so that Mat = Mat - Mean is possible. double fMeanY = 0.0; if (bConstant)
{
ScMatrixRef pNewX = pMatX->CloneIfConst();
ScMatrixRef pNewY = pMatY->CloneIfConst(); if (!pNewX || !pNewY)
{
PushError(FormulaError::CodeOverflow); return;
}
pMatX = std::move(pNewX);
pMatY = std::move(pNewY); // DeltaY is possible here; DeltaX depends on nCase, so later
fMeanY = lcl_GetMeanOverAll(pMatY, N); for (SCSIZE i=0; i<N; i++)
{
pMatY->PutDouble( ::rtl::math::approxSub(pMatY->GetDouble(i),fMeanY), i );
}
}
if (nCase==1)
{ // calculate simple regression double fMeanX = 0.0; if (bConstant)
{ // Mat = Mat - Mean
fMeanX = lcl_GetMeanOverAll(pMatX, N); for (SCSIZE i=0; i<N; i++)
{
pMatX->PutDouble( ::rtl::math::approxSub(pMatX->GetDouble(i),fMeanX), i );
}
} double fSumXY = lcl_GetSumProduct(pMatX,pMatY,N); double fSumX2 = lcl_GetSumProduct(pMatX,pMatX,N); if (fSumX2==0.0)
{
PushNoValue(); // all x-values are identical return;
} double fSlope = fSumXY / fSumX2; double fIntercept = 0.0; if (bConstant)
fIntercept = fMeanY - fSlope * fMeanX;
pResMat->PutDouble(_bRKP ? exp(fIntercept) : fIntercept, 1, 0); //order (column,row)
pResMat->PutDouble(_bRKP ? exp(fSlope) : fSlope, 0, 0);
double fR2 = fSSreg / (fSSreg + fSSresid);
pResMat->PutDouble(fR2, 0, 2);
}
}
PushMatrix(pResMat);
} else// calculate multiple regression;
{ // Uses a QR decomposition X = QR. The solution B = (X'X)^(-1) * X' * Y // becomes B = R^(-1) * Q' * Y if (nCase ==2) // Y is column
{
::std::vector< double> aVecR(N); // for QR decomposition // Enough memory for needed matrices?
ScMatrixRef pMeans = GetNewMat(K, 1, /*bEmpty*/true); // mean of each column
ScMatrixRef pMatZ; // for Q' * Y , inter alia if (bStats)
pMatZ = pMatY->Clone(); // Y is used in statistic, keep it else
pMatZ = pMatY; // Y can be overwritten
ScMatrixRef pSlopes = GetNewMat(1,K, /*bEmpty*/true); // from b1 to bK if (!pMeans || !pMatZ || !pSlopes)
{
PushError(FormulaError::CodeOverflow); return;
} if (bConstant)
{
lcl_CalculateColumnMeans(pMatX, pMeans, K, N);
lcl_CalculateColumnsDelta(pMatX, pMeans, K, N);
} if (!lcl_CalculateQRdecomposition(pMatX, aVecR, K, N))
{
PushNoValue(); return;
} // Later on we will divide by elements of aVecR, so make sure // that they aren't zero. bool bIsSingular=false; for (SCSIZE row=0; row < K && !bIsSingular; row++)
bIsSingular = aVecR[row] == 0.0; if (bIsSingular)
{
PushNoValue(); return;
} // Z = Q' Y; for (SCSIZE col = 0; col < K; col++)
{
lcl_ApplyHouseholderTransformation(pMatX, col, pMatZ, N);
} // B = R^(-1) * Q' * Y <=> B = R^(-1) * Z <=> R * B = Z // result Z should have zeros for index>=K; if not, ignore values for (SCSIZE col = 0; col < K ; col++)
{
pSlopes->PutDouble( pMatZ->GetDouble(col), col);
}
lcl_SolveWithUpperRightTriangle(pMatX, aVecR, pSlopes, K, false); double fIntercept = 0.0; if (bConstant)
fIntercept = fMeanY - lcl_GetSumProduct(pMeans,pSlopes,K); // Fill first line in result matrix
pResMat->PutDouble(_bRKP ? exp(fIntercept) : fIntercept, K, 0 ); for (SCSIZE i = 0; i < K; i++)
pResMat->PutDouble(_bRKP ? exp(pSlopes->GetDouble(i))
: pSlopes->GetDouble(i) , K-1-i, 0);
if (bStats)
{ double fSSreg = 0.0; double fSSresid = 0.0; // re-use memory of Z;
pMatZ->FillDouble(0.0, 0, 0, 0, N-1); // Z = R * Slopes
lcl_ApplyUpperRightTriangle(pMatX, aVecR, pSlopes, pMatZ, K, false); // Z = Q * Z, that is Q * R * Slopes = X * Slopes for (SCSIZE colp1 = K; colp1 > 0; colp1--)
{
lcl_ApplyHouseholderTransformation(pMatX, colp1-1, pMatZ,N);
}
fSSreg =lcl_GetSumProduct(pMatZ, pMatZ, N); // re-use Y for residuals, Y = Y-Z for (SCSIZE row = 0; row < N; row++)
pMatY->PutDouble(pMatY->GetDouble(row) - pMatZ->GetDouble(row), row);
fSSresid = lcl_GetSumProduct(pMatY, pMatY, N);
pResMat->PutDouble(fSSreg, 0, 4);
pResMat->PutDouble(fSSresid, 1, 4);
// standard error of estimate = root mean SSE double fRMSE = sqrt(fSSresid / fDegreesFreedom);
pResMat->PutDouble(fRMSE, 1, 2);
// standard error of slopes // = RMSE * sqrt(diagonal element of (R' R)^(-1) ) // standard error of intercept // = RMSE * sqrt( Xmean * (R' R)^(-1) * Xmean' + 1/N) // (R' R)^(-1) = R^(-1) * (R')^(-1). Do not calculate it as // a whole matrix, but iterate over unit vectors.
KahanSum aSigmaIntercept = 0.0; double fPart; // for Xmean * single column of (R' R)^(-1) for (SCSIZE col = 0; col < K; col++)
{ //re-use memory of MatZ
pMatZ->FillDouble(0.0,0,0,0,K-1); // Z = unit vector e
pMatZ->PutDouble(1.0, col); //Solve R' * Z = e
lcl_SolveWithLowerLeftTriangle(pMatX, aVecR, pMatZ, K, false); // Solve R * Znew = Zold
lcl_SolveWithUpperRightTriangle(pMatX, aVecR, pMatZ, K, false); // now Z is column col in (R' R)^(-1) double fSigmaSlope = fRMSE * sqrt(pMatZ->GetDouble(col));
pResMat->PutDouble(fSigmaSlope, K-1-col, 1); // (R' R) ^(-1) is symmetric if (bConstant)
{
fPart = lcl_GetSumProduct(pMeans, pMatZ, K);
aSigmaIntercept += fPart * pMeans->GetDouble(col);
}
} if (bConstant)
{ double fSigmaIntercept = fRMSE
* sqrt( (aSigmaIntercept + 1.0 / static_cast<double>(N) ).get() );
pResMat->PutDouble(fSigmaIntercept, K, 1);
} else
{
pResMat->PutError( FormulaError::NotAvailable, K, 1);
}
double fR2 = fSSreg / (fSSreg + fSSresid);
pResMat->PutDouble(fR2, 0, 2);
}
}
PushMatrix(pResMat);
} else// nCase == 3, Y is row, all matrices are transposed
{
::std::vector< double> aVecR(N); // for QR decomposition // Enough memory for needed matrices?
ScMatrixRef pMeans = GetNewMat(1, K, /*bEmpty*/true); // mean of each row
ScMatrixRef pMatZ; // for Q' * Y , inter alia if (bStats)
pMatZ = pMatY->Clone(); // Y is used in statistic, keep it else
pMatZ = pMatY; // Y can be overwritten
ScMatrixRef pSlopes = GetNewMat(K,1, /*bEmpty*/true); // from b1 to bK if (!pMeans || !pMatZ || !pSlopes)
{
PushError(FormulaError::CodeOverflow); return;
} if (bConstant)
{
lcl_CalculateRowMeans(pMatX, pMeans, N, K);
lcl_CalculateRowsDelta(pMatX, pMeans, N, K);
}
if (!lcl_TCalculateQRdecomposition(pMatX, aVecR, K, N))
{
PushNoValue(); return;
}
// Later on we will divide by elements of aVecR, so make sure // that they aren't zero. bool bIsSingular=false; for (SCSIZE row=0; row < K && !bIsSingular; row++)
bIsSingular = aVecR[row] == 0.0; if (bIsSingular)
{
PushNoValue(); return;
} // Z = Q' Y for (SCSIZE row = 0; row < K; row++)
{
lcl_TApplyHouseholderTransformation(pMatX, row, pMatZ, N);
} // B = R^(-1) * Q' * Y <=> B = R^(-1) * Z <=> R * B = Z // result Z should have zeros for index>=K; if not, ignore values for (SCSIZE col = 0; col < K ; col++)
{
pSlopes->PutDouble( pMatZ->GetDouble(col), col);
}
lcl_SolveWithUpperRightTriangle(pMatX, aVecR, pSlopes, K, true); double fIntercept = 0.0; if (bConstant)
fIntercept = fMeanY - lcl_GetSumProduct(pMeans,pSlopes,K); // Fill first line in result matrix
pResMat->PutDouble(_bRKP ? exp(fIntercept) : fIntercept, K, 0 ); for (SCSIZE i = 0; i < K; i++)
pResMat->PutDouble(_bRKP ? exp(pSlopes->GetDouble(i))
: pSlopes->GetDouble(i) , K-1-i, 0);
if (bStats)
{ double fSSreg = 0.0; double fSSresid = 0.0; // re-use memory of Z;
pMatZ->FillDouble(0.0, 0, 0, N-1, 0); // Z = R * Slopes
lcl_ApplyUpperRightTriangle(pMatX, aVecR, pSlopes, pMatZ, K, true); // Z = Q * Z, that is Q * R * Slopes = X * Slopes for (SCSIZE rowp1 = K; rowp1 > 0; rowp1--)
{
lcl_TApplyHouseholderTransformation(pMatX, rowp1-1, pMatZ,N);
}
fSSreg =lcl_GetSumProduct(pMatZ, pMatZ, N); // re-use Y for residuals, Y = Y-Z for (SCSIZE col = 0; col < N; col++)
pMatY->PutDouble(pMatY->GetDouble(col) - pMatZ->GetDouble(col), col);
fSSresid = lcl_GetSumProduct(pMatY, pMatY, N);
pResMat->PutDouble(fSSreg, 0, 4);
pResMat->PutDouble(fSSresid, 1, 4);
// The third parameter may be missing in ODF, although the forth parameter // is present. Default values depend on data not yet read.
ScMatrixRef pMatNewX; if (nParamCount >= 3)
{ if (IsMissing())
{
Pop();
pMatNewX = nullptr;
} else
pMatNewX = GetMatrix();
} else
pMatNewX = nullptr;
//In ODF1.2 empty second parameter (which is two ;; ) is allowed //Defaults will be set in CheckMatrix
ScMatrixRef pMatX; if (nParamCount >= 2)
{ if (IsMissing())
{
Pop();
pMatX = nullptr;
} else
{
pMatX = GetMatrix();
}
} else
pMatX = nullptr;
ScMatrixRef pMatY = GetMatrix(); if (!pMatY)
{
PushIllegalParameter(); return;
}
// 1 = simple; 2 = multiple with Y as column; 3 = multiple with Y as row
sal_uInt8 nCase;
SCSIZE nCX, nCY; // number of columns
SCSIZE nRX, nRY; //number of rows
SCSIZE K = 0, N = 0; // K=number of variables X, N=number of data samples if (!CheckMatrix(_bGrowth,nCase,nCX,nCY,nRX,nRY,K,N,pMatX,pMatY))
{
PushIllegalParameter(); return;
}
// Enough data samples? if ((bConstant && (N<K+1)) || (!bConstant && (N<K)) || (N<1) || (K<1))
{
PushIllegalParameter(); return;
}
// Set default pMatNewX if necessary
SCSIZE nCXN, nRXN;
SCSIZE nCountXN; if (!pMatNewX)
{
nCXN = nCX;
nRXN = nRX;
nCountXN = nCXN * nRXN;
pMatNewX = pMatX->Clone(); // pMatX will be changed to X-meanX
} else
{
pMatNewX->GetDimensions(nCXN, nRXN); if ((nCase == 2 && K != nCXN) || (nCase == 3 && K != nRXN))
{
PushIllegalArgument(); return;
}
nCountXN = nCXN * nRXN; for (SCSIZE i = 0; i < nCountXN; i++) if (!pMatNewX->IsValue(i))
{
PushIllegalArgument(); return;
}
}
ScMatrixRef pResMat; // size depends on nCase if (nCase == 1)
pResMat = GetNewMat(nCXN,nRXN, /*bEmpty*/true); else
{ if (nCase==2)
pResMat = GetNewMat(1,nRXN, /*bEmpty*/true); else
pResMat = GetNewMat(nCXN,1, /*bEmpty*/true);
} if (!pResMat)
{
PushError(FormulaError::CodeOverflow); return;
} // Uses sum(x-MeanX)^2 and not [sum x^2]-N * MeanX^2 in case bConstant. // Clone constant matrices, so that Mat = Mat - Mean is possible. double fMeanY = 0.0; if (bConstant)
{
ScMatrixRef pCopyX = pMatX->CloneIfConst();
ScMatrixRef pCopyY = pMatY->CloneIfConst(); if (!pCopyX || !pCopyY)
{
PushError(FormulaError::MatrixSize); return;
}
pMatX = std::move(pCopyX);
pMatY = std::move(pCopyY); // DeltaY is possible here; DeltaX depends on nCase, so later
fMeanY = lcl_GetMeanOverAll(pMatY, N); for (SCSIZE i=0; i<N; i++)
{
pMatY->PutDouble( ::rtl::math::approxSub(pMatY->GetDouble(i),fMeanY), i );
}
}
if (nCase==1)
{ // calculate simple regression double fMeanX = 0.0; if (bConstant)
{ // Mat = Mat - Mean
fMeanX = lcl_GetMeanOverAll(pMatX, N); for (SCSIZE i=0; i<N; i++)
{
pMatX->PutDouble( ::rtl::math::approxSub(pMatX->GetDouble(i),fMeanX), i );
}
} double fSumXY = lcl_GetSumProduct(pMatX,pMatY,N); double fSumX2 = lcl_GetSumProduct(pMatX,pMatX,N); if (fSumX2==0.0)
{
PushNoValue(); // all x-values are identical return;
} double fSlope = fSumXY / fSumX2; double fHelp; if (bConstant)
{ double fIntercept = fMeanY - fSlope * fMeanX; for (SCSIZE i = 0; i < nCountXN; i++)
{
fHelp = pMatNewX->GetDouble(i)*fSlope + fIntercept;
pResMat->PutDouble(_bGrowth ? exp(fHelp) : fHelp, i);
}
} else
{ for (SCSIZE i = 0; i < nCountXN; i++)
{
fHelp = pMatNewX->GetDouble(i)*fSlope;
pResMat->PutDouble(_bGrowth ? exp(fHelp) : fHelp, i);
}
}
} else// calculate multiple regression;
{ if (nCase ==2) // Y is column
{
::std::vector< double> aVecR(N); // for QR decomposition // Enough memory for needed matrices?
ScMatrixRef pMeans = GetNewMat(K, 1, /*bEmpty*/true); // mean of each column
ScMatrixRef pSlopes = GetNewMat(1,K, /*bEmpty*/true); // from b1 to bK if (!pMeans || !pSlopes)
{
PushError(FormulaError::CodeOverflow); return;
} if (bConstant)
{
lcl_CalculateColumnMeans(pMatX, pMeans, K, N);
lcl_CalculateColumnsDelta(pMatX, pMeans, K, N);
} if (!lcl_CalculateQRdecomposition(pMatX, aVecR, K, N))
{
PushNoValue(); return;
} // Later on we will divide by elements of aVecR, so make sure // that they aren't zero. bool bIsSingular=false; for (SCSIZE row=0; row < K && !bIsSingular; row++)
bIsSingular = aVecR[row] == 0.0; if (bIsSingular)
{
PushNoValue(); return;
} // Z := Q' Y; Y is overwritten with result Z for (SCSIZE col = 0; col < K; col++)
{
lcl_ApplyHouseholderTransformation(pMatX, col, pMatY, N);
} // B = R^(-1) * Q' * Y <=> B = R^(-1) * Z <=> R * B = Z // result Z should have zeros for index>=K; if not, ignore values for (SCSIZE col = 0; col < K ; col++)
{
pSlopes->PutDouble( pMatY->GetDouble(col), col);
}
lcl_SolveWithUpperRightTriangle(pMatX, aVecR, pSlopes, K, false);
// Fill result matrix
lcl_MFastMult(pMatNewX,pSlopes,pResMat,nRXN,K,1); if (bConstant)
{ double fIntercept = fMeanY - lcl_GetSumProduct(pMeans,pSlopes,K); for (SCSIZE row = 0; row < nRXN; row++)
pResMat->PutDouble(pResMat->GetDouble(row)+fIntercept, row);
} if (_bGrowth)
{ for (SCSIZE i = 0; i < nRXN; i++)
pResMat->PutDouble(exp(pResMat->GetDouble(i)), i);
}
} else
{ // nCase == 3, Y is row, all matrices are transposed
::std::vector< double> aVecR(N); // for QR decomposition // Enough memory for needed matrices?
ScMatrixRef pMeans = GetNewMat(1, K, /*bEmpty*/true); // mean of each row
ScMatrixRef pSlopes = GetNewMat(K,1, /*bEmpty*/true); // row from b1 to bK if (!pMeans || !pSlopes)
{
PushError(FormulaError::CodeOverflow); return;
} if (bConstant)
{
lcl_CalculateRowMeans(pMatX, pMeans, N, K);
lcl_CalculateRowsDelta(pMatX, pMeans, N, K);
} if (!lcl_TCalculateQRdecomposition(pMatX, aVecR, K, N))
{
PushNoValue(); return;
} // Later on we will divide by elements of aVecR, so make sure // that they aren't zero. bool bIsSingular=false; for (SCSIZE row=0; row < K && !bIsSingular; row++)
bIsSingular = aVecR[row] == 0.0; if (bIsSingular)
{
PushNoValue(); return;
} // Z := Q' Y; Y is overwritten with result Z for (SCSIZE row = 0; row < K; row++)
{
lcl_TApplyHouseholderTransformation(pMatX, row, pMatY, N);
} // B = R^(-1) * Q' * Y <=> B = R^(-1) * Z <=> R * B = Z // result Z should have zeros for index>=K; if not, ignore values for (SCSIZE col = 0; col < K ; col++)
{
pSlopes->PutDouble( pMatY->GetDouble(col), col);
}
lcl_SolveWithUpperRightTriangle(pMatX, aVecR, pSlopes, K, true);
// Fill result matrix
lcl_MFastMult(pSlopes,pMatNewX,pResMat,1,K,nCXN); if (bConstant)
{ double fIntercept = fMeanY - lcl_GetSumProduct(pMeans,pSlopes,K); for (SCSIZE col = 0; col < nCXN; col++)
pResMat->PutDouble(pResMat->GetDouble(col)+fIntercept, col);
} if (_bGrowth)
{ for (SCSIZE i = 0; i < nCXN; i++)
pResMat->PutDouble(exp(pResMat->GetDouble(i)), i);
}
}
}
PushMatrix(pResMat);
}
void ScInterpreter::ScMatRef()
{ // In case it contains relative references resolve them as usual.
Push( *pCur );
ScAddress aAdr;
PopSingleRef( aAdr );
ScRefCellValue aCell(mrDoc, aAdr);
if (aCell.getType() != CELLTYPE_FORMULA)
{
PushError( FormulaError::NoRef ); return;
}
if (aCell.getFormula()->IsRunning())
{ // Twisted odd corner case where an array element's cell tries to // access the top left matrix while it is still running, see tdf#88737 // This is a hackish workaround, not a general solution, the matrix // isn't available anyway and FormulaError::CircularReference would be set.
PushError( FormulaError::RetryCircular ); return;
}
const ScMatrix* pMat = aCell.getFormula()->GetMatrix(); if (pMat)
{
SCSIZE nCols, nRows;
pMat->GetDimensions( nCols, nRows );
SCSIZE nC = static_cast<SCSIZE>(aPos.Col() - aAdr.Col());
SCSIZE nR = static_cast<SCSIZE>(aPos.Row() - aAdr.Row()); #if0 // XXX: this could be an additional change for tdf#145085 to not // display the URL in a voluntary entered 2-rows array context. // However, that might as well be used on purpose to implement a check // on the URL, which existing documents may have done, the more that // before the accompanying change of // ScFormulaCell::GetResultDimensions() the cell array was forced to // two rows. Do not change without compelling reason. Note that this // repeating top cell is what Excel implements, but it has no // additional value so probably isn't used there. Exporting to and // using in Excel though will lose this capability. if (aCell.mpFormula->GetCode()->IsHyperLink())
{ // Row 2 element is the URL that is not to be displayed, fake a // 1-row cell-text-only matrix that is repeated.
assert(nRows == 2);
nR = 0;
} #endif if ((nCols <= nC && nCols != 1) || (nRows <= nR && nRows != 1))
PushNA(); else
{ const ScMatrixValue nMatVal = pMat->Get( nC, nR);
ScMatValType nMatValType = nMatVal.nType;
if (ScMatrix::IsNonValueType( nMatValType))
{ if (ScMatrix::IsEmptyPathType( nMatValType))
{ // result of empty false jump path
nFuncFmtType = SvNumFormatType::LOGICAL;
PushInt(0);
} elseif (ScMatrix::IsEmptyType( nMatValType))
{ // Not inherited (really?) and display as empty string, not 0.
PushTempToken( new ScEmptyCellToken( false, true));
} else
PushString( nMatVal.GetString() );
} else
{ // Determine nFuncFmtType type before PushDouble().
mrDoc.GetNumberFormatInfo(mrContext, nCurFmtType, nCurFmtIndex, aAdr);
nFuncFmtType = nCurFmtType;
nFuncFmtIndex = nCurFmtIndex;
PushDouble(nMatVal.fVal); // handles DoubleError
}
}
} else
{ // Determine nFuncFmtType type before PushDouble().
mrDoc.GetNumberFormatInfo(mrContext, nCurFmtType, nCurFmtIndex, aAdr);
nFuncFmtType = nCurFmtType;
nFuncFmtIndex = nCurFmtIndex; // If not a result matrix, obtain the cell value.
FormulaError nErr = aCell.getFormula()->GetErrCode(); if (nErr != FormulaError::NONE)
PushError( nErr ); elseif (aCell.getFormula()->IsValue())
PushDouble(aCell.getFormula()->GetValue()); else
{
svl::SharedString aVal = aCell.getFormula()->GetString();
PushString( aVal );
}
}
}
¤ Diese beiden folgenden Angebotsgruppen bietet das Unternehmen0.68Angebot
(Wie Sie bei der Firma Beratungs- und Dienstleistungen beauftragen können 2026-10-01)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.