sc/source/core/tool/interpr2.cxx |   49 +++++++++++++++++++++++++++++++++------
 1 file changed, 42 insertions(+), 7 deletions(-)

New commits:
commit f0f2ceaf978a0eee8e3c1fcfe6dec546ab8f07d6
Author:     Eike Rathke <er...@redhat.com>
AuthorDate: Sat Apr 15 01:38:39 2023 +0200
Commit:     Adolfo Jayme Barrientos <fit...@ubuntu.com>
CommitDate: Thu Apr 20 09:51:17 2023 +0200

    Resolves: tdf#138220 tdf#154792 Avoid double rounding; tdf#124286 follow-up
    
    Change-Id: Ie4028b20f2d3087a54bbfafd35d59fa06ec7a061
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/150439
    Reviewed-by: Eike Rathke <er...@redhat.com>
    Tested-by: Jenkins
    (cherry picked from commit 8186a01f2a26f05645a2a3c9c93b453bd35b796f)
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/150505
    Reviewed-by: Mike Kaganski <mike.kagan...@collabora.com>
    Reviewed-by: Adolfo Jayme Barrientos <fit...@ubuntu.com>

diff --git a/sc/source/core/tool/interpr2.cxx b/sc/source/core/tool/interpr2.cxx
index 7826522a89bf..a0b857defc76 100644
--- a/sc/source/core/tool/interpr2.cxx
+++ b/sc/source/core/tool/interpr2.cxx
@@ -962,20 +962,55 @@ void ScInterpreter::RoundNumber( rtl_math_RoundingMode 
eMode )
         fVal = ::rtl::math::round( GetDouble(), 0, eMode );
     else
     {
-        sal_Int16 nDec = GetInt16();
-        double fX = GetDouble();
+        const sal_Int16 nDec = GetInt16();
+        const double fX = GetDouble();
         if (nGlobalError == FormulaError::NONE)
         {
+            // A quite aggressive approach with 12 significant digits.
+            // However, using 14 or some other doesn't work because other
+            // values may fail, like =ROUNDDOWN(2-5E-015;13) would produce
+            // 2 (another example in tdf#124286).
+            constexpr sal_Int16 kSigDig = 12;
+
             if ( ( eMode == rtl_math_RoundingMode_Down ||
                    eMode == rtl_math_RoundingMode_Up ) &&
-                 nDec < 12 && fmod( fX, 1.0 ) != 0.0 )
+                 nDec < kSigDig && fmod( fX, 1.0 ) != 0.0 )
+
             {
-                // tdf124286 : round to 12 significant digits before rounding
+                // tdf124286 : round to significant digits before rounding
                 //             down or up to avoid unexpected rounding errors
                 //             caused by decimal -> binary -> decimal 
conversion
-                double fRes;
-                RoundSignificant( fX, 12, fRes );
-                fVal = ::rtl::math::round( fRes, nDec, eMode );
+
+                double fRes = fX;
+                // Similar to RoundSignificant() but omitting the back-scaling
+                // and interim integer rounding before the final rounding,
+                // which would result in double rounding. Instead, adjust the
+                // decimals and round into integer part before scaling back.
+                const double fTemp = floor( log10( std::abs(fRes))) + 1.0 - 
kSigDig;
+                // Avoid inaccuracy of negative powers of 10.
+                if (fTemp < 0.0)
+                    fRes *= pow(10.0, -fTemp);
+                else
+                    fRes /= pow(10.0, fTemp);
+                if (std::isfinite(fRes))
+                {
+                    // fRes is now at a decimal normalized scale.
+                    // Truncate up-rounding to opposite direction for values
+                    // like 0.0600000000000005 =ROUNDUP(8.06-8;2) that here now
+                    // is 600000000000.005 and otherwise would yield 0.07
+                    if (eMode == rtl_math_RoundingMode_Up)
+                        fRes = ::rtl::math::approxFloor(fRes);
+                    fVal = ::rtl::math::round( fRes, nDec + fTemp, eMode );
+                    if (fTemp < 0.0)
+                        fVal /= pow(10.0, -fTemp);
+                    else
+                        fVal *= pow(10.0, fTemp);
+                }
+                else
+                {
+                    // Overflow. Let our round() decide if and how to round.
+                    fVal = ::rtl::math::round( fX, nDec, eMode );
+                }
             }
             else
                 fVal = ::rtl::math::round( fX, nDec, eMode );

Reply via email to