Kaydet (Commit) bcbdf676 authored tarafından Stephan Bergmann's avatar Stephan Bergmann

Revert "fdo#37341 dix unending loop in calc with Goal Seek"

This reverts commit 07112a71, which breaks
JunitTest_sc_unoapi:

> checking: [sc.ScModelObj::com::sun::star::sheet::XGoalSeek] is iface: [com.sun.star.sheet.XGoalSeek] testcode: [ifc.sheet._XGoalSeek]
> LOG> Execute: seekGoal()
> LOG> Goal Result: 16.0   Divergence: 0.0
> LOG> Goal Result: 9.0   Divergence: 1.7976931348623157E308
> LOG> Goal Result: 0.8   Divergence: 1.7976931348623157E308
> Method seekGoal() finished with state FAILED
> LOG> seekGoal(): PASSED.FAILED
üst 689e4849
...@@ -48,33 +48,18 @@ ...@@ -48,33 +48,18 @@
using namespace formula; using namespace formula;
// ----------------------------------------------------------------------- // -----------------------------------------------------------------------
/** (Goal Seek) Find a value of x that is a root of f(x)
This function is used internally for the goal seek operation. It uses the // Nach der Regula Falsi Methode
Regula Falsi (aka false position) algorithm to find a root of f(x). The
start value and the target value are to be given by the user in the
goal seek dialog. The f(x) in this case is defined as the formula in the
formula cell minus target value. This function may also perform additional
search in the horizontal directions when the f(x) is discrete in order to
ensure a non-zero slope necessary for deriving a subsequent x that is
reasonably close to the root of interest.
@change 24.10.2004 by Kohei Yoshida (kohei@openoffice.org)
@see #i28955#
@change 6 Aug 2013, fdo37341
*/
bool ScDocument::Solver(SCCOL nFCol, SCROW nFRow, SCTAB nFTab, bool ScDocument::Solver(SCCOL nFCol, SCROW nFRow, SCTAB nFTab,
SCCOL nVCol, SCROW nVRow, SCTAB nVTab, SCCOL nVCol, SCROW nVRow, SCTAB nVTab,
const OUString& sValStr, double& nX) const OUString& sValStr, double& nX)
{ {
bool bRet = false; bool bRet = false;
nX = 0.0; nX = 0.0;
if ( ValidColRow( nFCol, nFRow ) && ValidTab( nFTab ) && if (ValidColRow(nFCol, nFRow) && ValidColRow(nVCol, nVRow) &&
ValidColRow( nVCol, nVRow ) && ValidTab( nVTab ) && ValidTab(nFTab) && ValidTab(nVTab) &&
nFTab < static_cast<SCTAB>( maTabs.size() ) && maTabs[nFTab] && nFTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nFTab] &&
nVTab < static_cast<SCTAB>( maTabs.size() ) && maTabs[nVTab] ) nVTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nVTab])
{ {
CellType eFType, eVType; CellType eFType, eVType;
GetCellType(nFCol, nFRow, nFTab, eFType); GetCellType(nFCol, nFRow, nFTab, eFType);
...@@ -82,169 +67,46 @@ bool ScDocument::Solver(SCCOL nFCol, SCROW nFRow, SCTAB nFTab, ...@@ -82,169 +67,46 @@ bool ScDocument::Solver(SCCOL nFCol, SCROW nFRow, SCTAB nFTab,
// CELLTYPE_NOTE: no value, but referenced by formula // CELLTYPE_NOTE: no value, but referenced by formula
// #i108005# convert target value to number using default format, // #i108005# convert target value to number using default format,
// as previously done in ScInterpreter::GetDouble // as previously done in ScInterpreter::GetDouble
double fTargetVal = 0.0; double nTargetVal = 0.0;
sal_uInt32 nFIndex = 0; sal_uInt32 nFIndex = 0;
if ( eFType == CELLTYPE_FORMULA && eVType == CELLTYPE_VALUE && if (eFType == CELLTYPE_FORMULA && (eVType == CELLTYPE_VALUE) &&
GetFormatTable()->IsNumberFormat( sValStr, nFIndex, fTargetVal ) ) GetFormatTable()->IsNumberFormat(sValStr, nFIndex, nTargetVal))
{ {
bool bDoneIteration = false; ScSingleRefData aRefData;
ScAddress aValueAdr( nVCol, nVRow, nVTab ); aRefData.InitFlags();
ScAddress aFormulaAdr( nFCol, nFRow, nFTab ); aRefData.SetAbsCol(nVCol);
double* pVCell = GetValueCell( aValueAdr ); aRefData.SetAbsRow(nVRow);
aRefData.SetAbsTab(nVTab);
ScRange aVRange( aValueAdr, aValueAdr ); // for SetDirty
// Original value to be restored later if necessary ScTokenArray aArr;
double fSaveVal = *pVCell; aArr.AddOpCode( ocBackSolver );
aArr.AddOpCode( ocOpen );
const sal_uInt16 nMaxIter = 100; aArr.AddSingleReference( aRefData );
const double fEps = 1E-10; aArr.AddOpCode( ocSep );
const double fDelta = 1E-6;
aRefData.SetAbsCol(nFCol);
double fBestX, fXPrev; aRefData.SetAbsRow(nFRow);
double fBestF, fFPrev; aRefData.SetAbsTab(nFTab);
fBestX = fXPrev = fSaveVal;
aArr.AddSingleReference( aRefData );
ScFormulaCell* pFormula = GetFormulaCell( aFormulaAdr ); aArr.AddOpCode( ocSep );
pFormula->Interpret(); aArr.AddDouble( nTargetVal );
bool bError = ( pFormula->GetErrCode() != 0 ); aArr.AddOpCode( ocClose );
// bError always corresponds with fF aArr.AddOpCode( ocStop );
fFPrev = pFormula->GetValue() - fTargetVal; ScFormulaCell* pCell = new ScFormulaCell( this, ScAddress(), &aArr );
fBestF = fabs( fFPrev ); if (pCell)
if ( fBestF < fDelta )
bDoneIteration = true;
double fX = fXPrev + fEps;
double fF = fFPrev;
double fSlope;
sal_uInt16 nIter = 0;
bool bHorMoveError = false;
// Conform Regula Falsi Method
while ( !bDoneIteration && ( nIter++ < nMaxIter ) && !bError )
{
*pVCell = fX;
SetDirty( aVRange );
pFormula->Interpret();
bError = ( pFormula->GetErrCode() != 0 );
fF = pFormula->GetValue() - fTargetVal;
if ( fF == fFPrev && !bError )
{
// HORIZONTAL SEARCH: Keep moving x in both directions until the f(x)
// becomes different from the previous f(x). This routine is needed
// when a given function is discrete, in which case the resulting slope
// may become zero which ultimately causes the goal seek operation
// to fail. #i28955#
sal_uInt16 nHorIter = 0;
const double fHorStepAngle = 5.0;
const double fHorMaxAngle = 80.0;
int nHorMaxIter = static_cast<int>( fHorMaxAngle / fHorStepAngle );
bool bDoneHorMove = false;
while ( !bDoneHorMove && !bHorMoveError && nHorIter++ < nHorMaxIter )
{
double fHorAngle = fHorStepAngle * static_cast<double>( nHorIter );
double fHorTangent = ::rtl::math::tan( fHorAngle * F_PI / 180 );
sal_uInt16 nIdx = 0;
while( nIdx++ < 2 && !bDoneHorMove )
{
double fHorX;
if ( nIdx == 1 )
fHorX = fX + fabs( fF ) * fHorTangent;
else
fHorX = fX - fabs( fF ) * fHorTangent;
*pVCell = fHorX;
SetDirty( aVRange );
pFormula->Interpret();
bHorMoveError = ( pFormula->GetErrCode() != 0 );
if ( bHorMoveError )
break;
fF = pFormula->GetValue() - fTargetVal;
if ( fF != fFPrev )
{
fX = fHorX;
bDoneHorMove = true;
}
}
}
if ( !bDoneHorMove )
bHorMoveError = true;
}
if ( bError )
{
// move closer to last valid value (fXPrev), keep fXPrev & fFPrev
double fDiff = ( fXPrev - fX ) / 2;
if ( fabs( fDiff ) < fEps )
fDiff = ( fDiff < 0.0 ? - fEps : fEps );
fX += fDiff;
}
else if ( bHorMoveError )
break;
else if ( fabs(fF) < fDelta )
{
// converged to root
fBestX = fX;
bDoneIteration = true;
}
else
{
if ( fabs(fF) + fDelta < fBestF )
{
fBestX = fX;
fBestF = fabs( fF );
}
if ( ( fXPrev - fX ) != 0 )
{
fSlope = ( fFPrev - fF ) / ( fXPrev - fX );
if ( fabs( fSlope ) < fEps )
fSlope = fSlope < 0.0 ? -fEps : fEps;
}
else
fSlope = fEps;
fXPrev = fX;
fFPrev = fF;
fX = fX - ( fF / fSlope );
}
}
// Try a nice rounded input value if possible.
const double fNiceDelta = ( bDoneIteration && fabs( fBestX ) >= 1e-3 ? 1e-3 : fDelta );
nX = ::rtl::math::approxFloor( ( fBestX / fNiceDelta ) + 0.5 ) * fNiceDelta;
if ( bDoneIteration )
{
*pVCell = nX;
SetDirty( aVRange );
pFormula->Interpret();
if ( fabs( pFormula->GetValue() - fTargetVal ) > fabs( fF ) )
nX = fBestX;
bRet = true;
}
else if ( bError || bHorMoveError )
{ {
nX = fBestX; // FIXME FIXME FIXME this might need to be reworked now that we have formula::FormulaErrorToken and ScFormulaResult, double check !!!
OSL_FAIL("ScDocument::Solver: -> ScFormulaCell::GetValueAlways might need reimplementation");
pCell->Interpret();
sal_uInt16 nErrCode = pCell->GetErrCode();
nX = pCell->GetValueAlways();
if (nErrCode == 0) // kein fehler beim Rechnen
bRet = true;
delete pCell;
} }
*pVCell = fSaveVal;
SetDirty( aVRange );
pFormula->Interpret();
if ( !bDoneIteration )
{
SetError( nVCol, nVRow, nVTab, NOTAVAILABLE );
}
}
else
{
SetError( nVCol, nVRow, nVTab, NOTAVAILABLE );
} }
} }
return bRet; return bRet;
......
...@@ -649,6 +649,7 @@ void ScKumKapZ(); ...@@ -649,6 +649,7 @@ void ScKumKapZ();
void ScEffektiv(); void ScEffektiv();
void ScNominal(); void ScNominal();
void ScMod(); void ScMod();
void ScBackSolver();
void ScIntercept(); void ScIntercept();
double ScGetGCD(double fx, double fy); double ScGetGCD(double fx, double fy);
void ScGCD(); void ScGCD();
......
...@@ -1695,6 +1695,204 @@ void ScInterpreter::ScMod() ...@@ -1695,6 +1695,204 @@ void ScInterpreter::ScMod()
} }
} }
/** (Goal Seek) Find a value of x that is a root of f(x)
This function is used internally for the goal seek operation. It uses the
Regula Falsi (aka false position) algorithm to find a root of f(x). The
start value and the target value are to be given by the user in the
goal seek dialog. The f(x) in this case is defined as the formula in the
formula cell minus target value. This function may also perform additional
search in the horizontal directions when the f(x) is discrete in order to
ensure a non-zero slope necessary for deriving a subsequent x that is
reasonably close to the root of interest.
@change 24.10.2004 by Kohei Yoshida (kohei@openoffice.org)
@see #i28955#
*/
void ScInterpreter::ScBackSolver()
{
if ( MustHaveParamCount( GetByte(), 3 ) )
{
bool bDoneIteration = false;
ScAddress aValueAdr, aFormulaAdr;
double fTargetVal = GetDouble();
PopSingleRef( aFormulaAdr );
PopSingleRef( aValueAdr );
if (nGlobalError == 0)
{
ScRefCellValue aFCell;
double* pVCell = pDok->GetValueCell(aValueAdr);
aFCell.assign(*pDok, aFormulaAdr);
if (pVCell && aFCell.meType == CELLTYPE_FORMULA)
{
ScRange aVRange( aValueAdr, aValueAdr ); // fuer SetDirty
// Original value to be restored later if necessary
double fSaveVal = *pVCell;
const sal_uInt16 nMaxIter = 100;
const double fEps = 1E-10;
const double fDelta = 1E-6;
double fBestX, fXPrev;
double fBestF, fFPrev;
fBestX = fXPrev = fSaveVal;
ScFormulaCell* pFormula = aFCell.mpFormula;
pFormula->Interpret();
bool bError = ( pFormula->GetErrCode() != 0 );
// bError always corresponds with fF
fFPrev = pFormula->GetValue() - fTargetVal;
fBestF = fabs( fFPrev );
if ( fBestF < fDelta )
bDoneIteration = true;
double fX = fXPrev + fEps;
double fF = fFPrev;
double fSlope;
sal_uInt16 nIter = 0;
bool bHorMoveError = false;
// Nach der Regula Falsi Methode
while ( !bDoneIteration && ( nIter++ < nMaxIter ) )
{
*pVCell = fX;
pDok->SetDirty( aVRange );
pFormula->Interpret();
bError = ( pFormula->GetErrCode() != 0 );
fF = pFormula->GetValue() - fTargetVal;
if ( fF == fFPrev && !bError )
{
// HORIZONTAL SEARCH: Keep moving x in both directions until the f(x)
// becomes different from the previous f(x). This routine is needed
// when a given function is discrete, in which case the resulting slope
// may become zero which ultimately causes the goal seek operation
// to fail. #i28955#
sal_uInt16 nHorIter = 0;
const double fHorStepAngle = 5.0;
const double fHorMaxAngle = 80.0;
int nHorMaxIter = static_cast<int>( fHorMaxAngle / fHorStepAngle );
bool bDoneHorMove = false;
while ( !bDoneHorMove && !bHorMoveError && nHorIter++ < nHorMaxIter )
{
double fHorAngle = fHorStepAngle * static_cast<double>( nHorIter );
double fHorTangent = ::rtl::math::tan( fHorAngle * F_PI / 180 );
sal_uInt16 nIdx = 0;
while( nIdx++ < 2 && !bDoneHorMove )
{
double fHorX;
if ( nIdx == 1 )
fHorX = fX + fabs(fF)*fHorTangent;
else
fHorX = fX - fabs(fF)*fHorTangent;
*pVCell = fHorX;
pDok->SetDirty( aVRange );
pFormula->Interpret();
bHorMoveError = ( pFormula->GetErrCode() != 0 );
if ( bHorMoveError )
break;
fF = pFormula->GetValue() - fTargetVal;
if ( fF != fFPrev )
{
fX = fHorX;
bDoneHorMove = true;
}
}
}
if ( !bDoneHorMove )
bHorMoveError = true;
}
if ( bError )
{
// move closer to last valid value (fXPrev), keep fXPrev & fFPrev
double fDiff = ( fXPrev - fX ) / 2;
if (fabs(fDiff) < fEps)
fDiff = (fDiff < 0.0) ? - fEps : fEps;
fX += fDiff;
}
else if ( bHorMoveError )
break;
else if ( fabs(fF) < fDelta )
{
// converged to root
fBestX = fX;
bDoneIteration = true;
}
else
{
if ( fabs(fF) + fDelta < fBestF )
{
fBestX = fX;
fBestF = fabs(fF);
}
if ( ( fXPrev - fX ) != 0 )
{
fSlope = ( fFPrev - fF ) / ( fXPrev - fX );
if ( fabs( fSlope ) < fEps )
fSlope = fSlope < 0.0 ? -fEps : fEps;
}
else
fSlope = fEps;
fXPrev = fX;
fFPrev = fF;
fX = fX - ( fF / fSlope );
}
}
// Try a nice rounded input value if possible.
const double fNiceDelta = (bDoneIteration && fabs(fBestX) >= 1e-3 ? 1e-3 : fDelta);
double nX = ::rtl::math::approxFloor((fBestX / fNiceDelta) + 0.5) * fNiceDelta;
if ( bDoneIteration )
{
*pVCell = nX;
pDok->SetDirty( aVRange );
pFormula->Interpret();
if ( fabs( pFormula->GetValue() - fTargetVal ) > fabs( fF ) )
nX = fBestX;
}
else if ( bError || bHorMoveError )
{
nX = fBestX;
}
*pVCell = fSaveVal;
pDok->SetDirty( aVRange );
pFormula->Interpret();
if ( !bDoneIteration )
SetError(NOTAVAILABLE);
PushDouble(nX);
}
else
{
if ( !bDoneIteration )
SetError(NOTAVAILABLE);
PushInt(0); // falsche Zelltypen
}
}
else
{
if ( !bDoneIteration )
SetError(NOTAVAILABLE);
PushInt(0); // nGlobalError
}
}
}
void ScInterpreter::ScIntersect() void ScInterpreter::ScIntersect()
{ {
formula::FormulaTokenRef p2nd = PopToken(); formula::FormulaTokenRef p2nd = PopToken();
......
...@@ -4009,6 +4009,7 @@ StackVar ScInterpreter::Interpret() ...@@ -4009,6 +4009,7 @@ StackVar ScInterpreter::Interpret()
case ocMatMult : ScMatMult(); break; case ocMatMult : ScMatMult(); break;
case ocMatTrans : ScMatTrans(); break; case ocMatTrans : ScMatTrans(); break;
case ocMatRef : ScMatRef(); break; case ocMatRef : ScMatRef(); break;
case ocBackSolver : ScBackSolver(); break;
case ocB : ScB(); break; case ocB : ScB(); break;
case ocNormDist : ScNormDist(); break; case ocNormDist : ScNormDist(); break;
case ocExpDist : ScExpDist(); break; case ocExpDist : ScExpDist(); break;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment