From fc6f23d8ad9a63fed08bd4363f35b2918992b59c Mon Sep 17 00:00:00 2001 From: "Pedro F. Giffuni" Date: Sun, 23 Aug 2026 11:35:56 -0500 Subject: [PATCH 1/3] calc/solver: Update error reporting for CoinMP The solver component was made originally for lp_solve, which did basically the same as CoinMP, but handled differently some errors, in particular timeouts. Be more in line with CoinMP error reporting. By the way we set up the problem internally it is unlikely some of these will come up but defining them will make easier to understand what could go wrong. --- main/sccomp/source/solver/solver.cxx | 19 +++++++++++++++++-- main/sccomp/source/solver/solver.hrc | 8 ++++++-- main/sccomp/source/solver/solver.src | 21 +++++++++++++++++++-- 3 files changed, 42 insertions(+), 6 deletions(-) diff --git a/main/sccomp/source/solver/solver.cxx b/main/sccomp/source/solver/solver.cxx index 3e3d7f0f11b..985caf49370 100644 --- a/main/sccomp/source/solver/solver.cxx +++ b/main/sccomp/source/solver/solver.cxx @@ -568,6 +568,14 @@ void SAL_CALL SolverComponent::solve() throw(uno::RuntimeException) // solve model nResult = CoinCheckProblem( hProb ); + if (nResult != SOLV_CALL_SUCCESS) + { + // report invalid model + + maStatus = lcl_GetResourceString( RID_ERROR_INVALIDMODEL ); + CoinUnloadProblem(hProb); + return; + } nResult = CoinOptimizeProblem( hProb, 0 ); mbSuccess = ( nResult == SOLV_CALL_SUCCESS ); @@ -586,8 +594,15 @@ void SAL_CALL SolverComponent::solve() throw(uno::RuntimeException) maStatus = lcl_GetResourceString( RID_ERROR_INFEASIBLE ); else if ( nSolutionStatus == 2 ) maStatus = lcl_GetResourceString( RID_ERROR_UNBOUNDED ); - // TODO: detect timeout condition and report as RID_ERROR_TIMEOUT - // (currently reported as infeasible) + else if ( nSolutionStatus == 3 ) + maStatus = lcl_GetResourceString( RID_ERROR_ITERATIONLIMIT ); + else if ( nSolutionStatus == 4 ) + maStatus = lcl_GetResourceString( RID_ERROR_SOLVERERROR ); + else if ( nSolutionStatus == 5 ) + maStatus = lcl_GetResourceString( RID_ERROR_USERSTOP ); + else if ( nSolutionStatus >= 6 ) + maStatus = lcl_GetResourceString( RID_ERROR_UNKNOWN ); + } CoinUnloadProblem( hProb ); diff --git a/main/sccomp/source/solver/solver.hrc b/main/sccomp/source/solver/solver.hrc index 2a6965848cd..84d9169ff4c 100644 --- a/main/sccomp/source/solver/solver.hrc +++ b/main/sccomp/source/solver/solver.hrc @@ -35,7 +35,11 @@ #define RID_ERROR_NONLINEAR (SOLVER_RESOURCE_START + 7) #define RID_ERROR_EPSILONLEVEL (SOLVER_RESOURCE_START + 8) #define RID_ERROR_INFEASIBLE (SOLVER_RESOURCE_START + 9) -#define RID_ERROR_UNBOUNDED (SOLVER_RESOURCE_START + 10) -#define RID_ERROR_TIMEOUT (SOLVER_RESOURCE_START + 11) +#define RID_ERROR_UNBOUNDED (SOLVER_RESOURCE_START + 11) +#define RID_ERROR_ITERATIONLIMIT (SOLVER_RESOURCE_START + 12) +#define RID_ERROR_SOLVERERROR (SOLVER_RESOURCE_START + 13) +#define RID_ERROR_USERSTOP (SOLVER_RESOURCE_START + 14) +#define RID_ERROR_UNKNOWN (SOLVER_RESOURCE_START + 15) +#define RID_ERROR_INVALIDMODEL (SOLVER_RESOURCE_START + 16) #endif diff --git a/main/sccomp/source/solver/solver.src b/main/sccomp/source/solver/solver.src index f6cab594c15..03200029dc5 100644 --- a/main/sccomp/source/solver/solver.src +++ b/main/sccomp/source/solver/solver.src @@ -52,6 +52,7 @@ String RID_PROPERTY_NONLINEARTEST { Text [ en-US ] = "Run strict linearity checks"; }; + String RID_ERROR_NONLINEAR { Text [ en-US ] = "The model seems nonlinear (see options)."; @@ -68,7 +69,23 @@ String RID_ERROR_UNBOUNDED { Text [ en-US ] = "The model is unbounded."; }; -String RID_ERROR_TIMEOUT +String RID_ERROR_ITERATIONLIMIT +{ + Text [ en-US ] = "Iteration limit reached."; +}; +String RID_ERROR_SOLVERERROR +{ + Text [ en-US ] = "Numerical solver error."; +}; +String RID_ERROR_USERSTOP +{ + Text [ en-US ] = "User/callback cancellation."; +}; +String RID_ERROR_UNKNOWN +{ + Text [ en-US ] = "Unrecognized status."; +}; +String RID_ERROR_INVALIDMODEL { - Text [ en-US ] = "The time limit was reached."; + Text [ en-US ] = "Invalid internal generated model."; }; From c6461c6d88bfd5b55f07751355dfcd486719744e Mon Sep 17 00:00:00 2001 From: "Pedro F. Giffuni" Date: Tue, 25 Aug 2026 23:41:55 -0500 Subject: [PATCH 2/3] Calc: use STD C++ for Complex functions. At first many of these calculations were handwritten and could overflow. Later we started using the available Boost for some edge cases which was a good solution to make up for the lack of such function in some of the supported platforms. The latest versions of Boost though, are deprecating Boost complex functions in favor of the C++ standard library and its now time to catch up. As a result of this change, the results of IMSECH and IMCSCH don't match the examples in the the help but matches the results in Excel. --- .../source/analysis/analysishelper.cxx | 211 +++--------------- 1 file changed, 31 insertions(+), 180 deletions(-) diff --git a/main/scaddins/source/analysis/analysishelper.cxx b/main/scaddins/source/analysis/analysishelper.cxx index 5b56a270b4e..e6b1b153ce6 100644 --- a/main/scaddins/source/analysis/analysishelper.cxx +++ b/main/scaddins/source/analysis/analysishelper.cxx @@ -1943,10 +1943,7 @@ double Complex::Arg( void ) const THROWDEF_RTE_IAE if( Num.real() == 0.0 && Num.imag() == 0.0 ) THROW_IAE; - double phi = acos( Num.real() / Abs() ); - - if( Num.imag() < 0.0 ) - phi = -phi; + double phi = std::atan2(Num.imag(), Num.real()); return phi; } @@ -1965,68 +1962,34 @@ void Complex::Power( double fPower ) THROWDEF_RTE_IAE THROW_IAE; } - double p, phi; - - p = Abs(); - - phi = acos( Num.real() / p ); - if( Num.imag() < 0.0 ) - phi = -phi; - - p = pow( p, fPower ); - phi *= fPower; - - Num = double_complex (cos( phi ) * p, sin( phi ) * p); + Num = std::pow(Num , fPower); } void Complex::Sqrt( void ) { - static const double fMultConst = 0.7071067811865475; // ...2440084436210485 = 1/sqrt(2) - double p = Abs(); - double i_ = sqrt( p - Num.real() ) * fMultConst; - Num = double_complex (sqrt( p + Num.real() ) * fMultConst, ( Num.imag() < 0.0 )? -i_ : i_); + Num = std::sqrt(Num); } void Complex::Sin( void ) THROWDEF_RTE_IAE { - double r = Num.real(), i = Num.imag() ; + double r = Num.real(); if( !::rtl::math::isValidArcArg( r ) ) THROW_IAE; - if( i ) - { - double r_; - - r_ = sin( r ) * cosh( i ); - i = cos( r ) * sinh( i ); - r = r_; - } - else - r = sin( r ); - Num = double_complex ( r, i ); + Num = std::sin( Num ); } void Complex::Cos( void ) THROWDEF_RTE_IAE { - double r = Num.real(), i = Num.imag() ; + double r = Num.real(); if( !::rtl::math::isValidArcArg( r ) ) THROW_IAE; - if( i ) - { - double r_; - - r_ = cos( r ) * cosh( i ); - i = -( sin( r ) * sinh( i ) ); - r = r_; - } - else - r = cos( r ); - Num = double_complex ( r, i ); + Num = std::cos ( Num ); } @@ -2035,23 +1998,15 @@ void Complex::Div( const Complex& z ) THROWDEF_RTE_IAE if( z.Num.real() == 0 && z.Num.imag() == 0 ) THROW_IAE; - double a1 = Num.real(); - double a2 = z.Num.real(); - double b1 = Num.imag(); - double b2 = z.Num.imag(); - - double f = 1.0 / ( a2 * a2 + b2 * b2 ); - - Num = f * double_complex ( a1 * a2 + b1 * b2 , a2 * b1 - a1 * b2 ); + Num /= z.Num; - if( !c ) c = z.c; } void Complex::Exp( void ) { - double fE = exp( Num.real() ); - Num = fE * double_complex ( cos( Num.imag() ), sin( Num.imag() ) ); + + Num = std::exp( Num ); } @@ -2061,16 +2016,7 @@ void Complex::Ln( void ) THROWDEF_RTE_IAE if( r == 0.0 && i == 0.0 ) THROW_IAE; - double fAbs = Abs(); - sal_Bool bNegi = i < 0.0; - - i = acos( r / fAbs ); - - if( bNegi ) - i = -i; - - r = log( fAbs ); - Num = double_complex ( r, i ); + Num = std::log ( Num ); } @@ -2095,168 +2041,73 @@ void Complex::Tan(void) THROWDEF_RTE_IAE { if( !::rtl::math::isValidArcArg( 2.0 * r ) ) THROW_IAE; - double fScale =1.0 / ( cos( 2.0 * r ) + cosh( 2.0 * i )); - r = sin( 2.0 * r ) * fScale; - i = sinh( 2.0 * i ) * fScale; } else { if( !::rtl::math::isValidArcArg( r ) ) THROW_IAE; - r = tan( r ); } - Num = double_complex ( r, i ); + Num = std::tan ( Num ); } void Complex::Sec( void ) THROWDEF_RTE_IAE { - double r = Num.real(), i = Num.imag() ; - if( i ) - { - if( !::rtl::math::isValidArcArg( 2 * r ) ) - THROW_IAE; - double fScale = 1.0 / (cosh( 2.0 * i) + cos ( 2.0 * r)); - double r_; - r_ = 2.0 * cos( r ) * cosh( i ) * fScale; - i = 2.0 * sin( r ) * sinh( i ) * fScale; - r = r_; - } - else - { - if( !::rtl::math::isValidArcArg( r ) ) - THROW_IAE; - r = 1.0 / cos( r ); - } - Num = double_complex ( r, i ); + + Cos(); + Num = 1.0 / Num; } void Complex::Csc( void ) THROWDEF_RTE_IAE { - double r = Num.real(), i = Num.imag() ; - if( i ) - { - if( !::rtl::math::isValidArcArg( 2 * r ) ) - THROW_IAE; - double fScale = 1.0 / (cosh( 2.0 * i) - cos ( 2.0 * r)); - double r_; - r_ = 2.0 * sin( r ) * cosh( i ) * fScale; - i = -2.0 * cos( r ) * sinh( i ) * fScale; - r = r_; - } - else - { - if( !::rtl::math::isValidArcArg( r ) ) - THROW_IAE; - r = 1.0 / sin( r ); - } - Num = double_complex ( r, i ); + + Sin(); + Num = 1.0 / Num; } void Complex::Cot(void) THROWDEF_RTE_IAE { - double r = Num.real(), i = Num.imag() ; - if ( i ) - { - if( !::rtl::math::isValidArcArg( 2.0 * r ) ) - THROW_IAE; - double fScale =1.0 / ( cosh( 2.0 * i ) - cos( 2.0 * r ) ); - r = sin( 2.0 * r ) * fScale; - i = - ( sinh( 2.0 * i ) * fScale ); - } - else - { - if( !::rtl::math::isValidArcArg( r ) ) - THROW_IAE; - r = 1.0 / tan( r ); - } - Num = double_complex ( r, i ); + + Tan(); + Num = 1.0 / Num; } void Complex::Sinh( void ) THROWDEF_RTE_IAE { - double r = Num.real(), i = Num.imag() ; + double r = Num.real(); if( !::rtl::math::isValidArcArg( r ) ) THROW_IAE; - if( i ) - { - double r_; - r_ = sinh( r ) * cos( i ); - i = cosh( r ) * sin( i ); - r = r_; - } - else - r = sinh( r ); - Num = double_complex ( r, i ); + Num = std::sinh( Num ); } void Complex::Cosh( void ) THROWDEF_RTE_IAE { - double r = Num.real(), i = Num.imag() ; + double r = Num.real(); if( !::rtl::math::isValidArcArg( r ) ) THROW_IAE; - if( i ) - { - double r_; - r_ = cosh( r ) * cos( i ); - i = sinh( r ) * sin( i ); - r = r_; - } - else - r = cosh( r ); - Num = double_complex ( r, i ); + Num = std::cosh( Num ); } void Complex::Sech(void) THROWDEF_RTE_IAE { - double r = Num.real(), i = Num.imag() ; - if ( i ) - { - if( !::rtl::math::isValidArcArg( 2.0 * r ) ) - THROW_IAE; - double fScale =1.0 / ( cosh( 2.0 * r ) + cos( 2.0 * i )); - double r_; - r_ = 2.0 * cosh( r ) * cos( i ) * fScale; - i = - (2.0 * sinh( r ) * sin( i ) * fScale ); - r = r_ ; - } - else - { - if( !::rtl::math::isValidArcArg( r ) ) - THROW_IAE; - r = 1.0 / cosh( r ); - } - Num = double_complex ( r, i ); + + Cosh(); + Num = 1.0 / Num; } void Complex::Csch(void) THROWDEF_RTE_IAE { - double r = Num.real(), i = Num.imag() ; - if ( i ) - { - if( !::rtl::math::isValidArcArg( 2.0 * r ) ) - THROW_IAE; - double fScale =1.0 / ( cosh( 2.0 * r ) - cos( 2.0 * i )); - double r_; - r_ = 2.0 * sinh( r ) * cos( i ) * fScale; - i = - ( 2.0 * cosh( r ) * sin( i ) * fScale ); - r = r_ ; - } - else - { - if( !::rtl::math::isValidArcArg( r ) ) - THROW_IAE; - r = 1.0 / sinh( r ); - } - Num = double_complex ( r, i ); + + Sinh(); + Num = 1.0 / Num; } From e3e520e948524d65994f7a3abb170433fe6eba15 Mon Sep 17 00:00:00 2001 From: "Pedro F. Giffuni" Date: Tue, 25 Aug 2026 23:51:58 -0500 Subject: [PATCH 3/3] Calc: Replace some uses of Boost math functions for their C++ counterparts. For the cases of acosh, asinh, atanh, expm1 and log1p we were making use of the Boost library which was/is available and of good quality. Nowadays these are included in all reasonable standard C++ libraries and it makes sense to use them for consistency. --- main/sc/source/core/tool/interpr1.cxx | 10 ++++------ main/sc/source/core/tool/interpr2.cxx | 11 +++++------ main/sc/source/core/tool/interpr3.cxx | 22 ++++++++++------------ 3 files changed, 19 insertions(+), 24 deletions(-) diff --git a/main/sc/source/core/tool/interpr1.cxx b/main/sc/source/core/tool/interpr1.cxx index 74ee0b8a94a..51d7277357a 100644 --- a/main/sc/source/core/tool/interpr1.cxx +++ b/main/sc/source/core/tool/interpr1.cxx @@ -72,9 +72,7 @@ #include "doubleref.hxx" #include "queryparam.hxx" -#include -#include -#include +#include #define SC_DOUBLE_MAXVALUE 1.7e307 @@ -1811,7 +1809,7 @@ void ScInterpreter::ScCotHyp() void ScInterpreter::ScArcSinHyp() { RTL_LOGFILE_CONTEXT_AUTHOR( aLogger, "sc", "er", "ScInterpreter::ScArcSinHyp" ); - PushDouble( ::boost::math::asinh( GetDouble())); + PushDouble( std::asinh( GetDouble())); } void ScInterpreter::ScArcCosHyp() @@ -1821,7 +1819,7 @@ void ScInterpreter::ScArcCosHyp() if (fVal < 1.0) PushIllegalArgument(); else - PushDouble( ::boost::math::acosh( fVal)); + PushDouble( std::acosh( fVal)); } void ScInterpreter::ScArcTanHyp() @@ -1831,7 +1829,7 @@ void ScInterpreter::ScArcTanHyp() if (fabs(fVal) >= 1.0) PushIllegalArgument(); else - PushDouble( ::boost::math::atanh( fVal)); + PushDouble( std::atanh( fVal)); } diff --git a/main/sc/source/core/tool/interpr2.cxx b/main/sc/source/core/tool/interpr2.cxx index bdeebb69385..a26b2d98720 100644 --- a/main/sc/source/core/tool/interpr2.cxx +++ b/main/sc/source/core/tool/interpr2.cxx @@ -50,8 +50,7 @@ #include #include -#include -#include +#include using namespace formula; // STATIC DATA ----------------------------------------------------------- @@ -1136,11 +1135,11 @@ double ScInterpreter::ScGetRmz(double fRate, double fNper, double fPv, else { if (fPaytype > 0.0) // payment in advance - fPayment = (fFv + fPv * exp( fNper * ::boost::math::log1p(fRate) ) ) * fRate / - (::boost::math::expm1( (fNper + 1) * ::boost::math::log1p(fRate) ) - fRate); + fPayment = (fFv + fPv * exp( fNper * std::log1p(fRate) ) ) * fRate / + (std::expm1( (fNper + 1) * std::log1p(fRate) ) - fRate); else // payment in arrear - fPayment = (fFv + fPv * exp(fNper * ::boost::math::log1p(fRate) ) ) * fRate / - ::boost::math::expm1( fNper * ::boost::math::log1p(fRate) ); + fPayment = (fFv + fPv * exp(fNper * std::log1p(fRate) ) ) * fRate / + std::expm1( fNper * std::log1p(fRate) ); } return -fPayment; } diff --git a/main/sc/source/core/tool/interpr3.cxx b/main/sc/source/core/tool/interpr3.cxx index dfc05ef2c80..a1e3f3919e1 100644 --- a/main/sc/source/core/tool/interpr3.cxx +++ b/main/sc/source/core/tool/interpr3.cxx @@ -44,9 +44,7 @@ #include #include -#include -#include -#include +#include using ::std::vector; using namespace formula; @@ -857,8 +855,8 @@ double ScInterpreter::GetBeta(double fAlpha, double fBeta) fLanczos *= sqrt((fABgm/(fA+fgm))/(fB+fgm)); double fTempA = fB/(fA+fgm); // (fA+fgm)/fABgm = 1 / ( 1 + fB/(fA+fgm)) double fTempB = fA/(fB+fgm); - double fResult = exp(-fA * ::boost::math::log1p(fTempA) - -fB * ::boost::math::log1p(fTempB)-fgm); + double fResult = exp(-fA * std::log1p(fTempA) + -fB * std::log1p(fTempB)-fgm); fResult *= fLanczos; return fResult; } @@ -886,8 +884,8 @@ double ScInterpreter::GetLogBeta(double fAlpha, double fBeta) fLogLanczos += 0.5*(log(fABgm)-log(fA+fgm)-log(fB+fgm)); double fTempA = fB/(fA+fgm); // (fA+fgm)/fABgm = 1 / ( 1 + fB/(fA+fgm)) double fTempB = fA/(fB+fgm); - double fResult = -fA * ::boost::math::log1p(fTempA) - -fB * ::boost::math::log1p(fTempB)-fgm; + double fResult = -fA * std::log1p(fTempA) + -fB * std::log1p(fTempB)-fgm; fResult += fLogLanczos; return fResult; } @@ -908,7 +906,7 @@ double ScInterpreter::GetBetaDistPDF(double fX, double fA, double fB) return HUGE_VAL; } if (fX <= 0.01) - return fB + fB * ::boost::math::expm1((fB-1.0) * ::boost::math::log1p(-fX)); + return fB + fB * std::expm1((fB-1.0) * std::log1p(-fX)); else return fB * pow(0.5-fX+0.5,fB-1.0); } @@ -947,7 +945,7 @@ double ScInterpreter::GetBetaDistPDF(double fX, double fA, double fB) // normal cases; result x^(a-1)*(1-x)^(b-1)/Beta(a,b) const double fLogDblMax = log( ::std::numeric_limits::max()); const double fLogDblMin = log( ::std::numeric_limits::min()); - double fLogY = (fX < 0.1) ? ::boost::math::log1p(-fX) : log(0.5-fX+0.5); + double fLogY = (fX < 0.1) ? std::log1p(-fX) : log(0.5-fX+0.5); double fLogX = log(fX); double fAm1LogX = (fA-1.0) * fLogX; double fBm1LogY = (fB-1.0) * fLogY; @@ -1028,13 +1026,13 @@ double ScInterpreter::GetBetaDist(double fXin, double fAlpha, double fBeta) return pow(fXin, fAlpha); if (fAlpha == 1.0) // 1.0 - pow(1.0-fX,fBeta) is not accurate enough - return -::boost::math::expm1(fBeta * ::boost::math::log1p(-fXin)); + return -std::expm1(fBeta * std::log1p(-fXin)); //FIXME: need special algorithm for fX near fP for large fA,fB double fResult; // I use always continued fraction, power series are neither // faster nor more accurate. double fY = (0.5-fXin)+0.5; - double flnY = ::boost::math::log1p(-fXin); + double flnY = std::log1p(-fXin); double fX = fXin; double flnX = log(fXin); double fA = fAlpha; @@ -1145,7 +1143,7 @@ void ScInterpreter::ScFisher() if (fabs(fVal) >= 1.0) PushIllegalArgument(); else - PushDouble( ::boost::math::atanh( fVal)); + PushDouble( std::atanh( fVal)); } void ScInterpreter::ScFisherInv()