Bug 152739 - Round ROUND_HALF_UP is OK in Python, but NO in Basic when call the same Python def.
Summary: Round ROUND_HALF_UP is OK in Python, but NO in Basic when call the same Pytho...
Status: RESOLVED NOTABUG
Alias: None
Product: LibreOffice
Classification: Unclassified
Component: BASIC (show other bugs)
Version:
(earliest affected)
7.1.8.1 release
Hardware: All Windows (All)
: medium normal
Assignee: Not Assigned
URL:
Whiteboard:
Keywords:
Depends on:
Blocks:
 
Reported: 2022-12-30 18:47 UTC by edil
Modified: 2024-04-12 12:36 UTC (History)
2 users (show)

See Also:
Crash report or crash signature:


Attachments
macro (1.96 KB, text/plain)
2022-12-30 20:20 UTC, edil
Details

Note You need to log in before you can comment on or make changes to this bug.
Description edil 2022-12-30 18:47:58 UTC
Description:
I have a Python function for round up numbers, that works fine:

' # RoundUP.py  file in Script 
' from decimal import Decimal, getcontext, ROUND_HALF_UP  # # Round UP
' round_context = getcontext()
' round_context.rounding = ROUND_HALF_UP
' # -------------------------------------
' def roundUp(x, digits, precision=5):
    ' tmp = round(Decimal(x), precision)
    ' fRet = float(tmp.__round__(digits))
    ' return fRet

' # ---------------------------------------------------------------
' if __name__ == '__main__':
    ' fRU = roundUp(2.125, 2)  # -> 2.13
    ' fRU = roundUp(3.125, 2)  # -> 3.13
    ' fRU = roundUp(-2.125, 2)  # -> -2.13
    ' fRU = roundUp(4.125, 2)  # -> 4.13
    ' fRU = roundUp(2.225, 2)  # -> 2.23
    ' pass

I call that function from Basic, and the result change:
fRU = RoundUp(2.125, 2) ' 2.12

Now i have 2.12, not 2.13. Why ???

'---BASIC------------------------
Function RoundUp(num, dec) ' Number to Double Up
	Dim vRoundUp
	
	sFunctionPy = "vnd.sun.star.script:RoundUP.py$roundUp"& _
			"?language=Python&location=user"		
	oMSPF = createUnoService( _
	 "com.sun.star.script.provider.MasterScriptProviderFactory")
	gScriptProvider = oMSPF.createScriptProvider("")	
	oScript = gScriptProvider.getScript(sFunctionPy)
	vRoundUp = oScript.invoke(Array(num, dec), Array(), Array())
	RoundUp = vRoundUp 
End Function

Steps to Reproduce:
1.Copy a  RoundUP.py  file in Script (the code is in Description above)
2.Run Main in Calc RounUP.ods
3.Examine the different results

Actual Results:
2.12

Expected Results:
2.13


Reproducible: Always


User Profile Reset: No

Additional Info:
Version: 7.1.8.1 (x64) / LibreOffice Community
Build ID: e1f30c802c3269a1d052614453f260e49458c82c
CPU threads: 16; OS: Windows 10.0 Build 22621; UI render: Skia/Raster; VCL: win
Locale: it-IT (it_IT); UI: it-IT
Calc: CL
Comment 1 edil 2022-12-30 20:20:11 UTC
Created attachment 184399 [details]
macro
Comment 2 Buovjaga 2023-03-17 12:21:13 UTC
I copied it to instdir/share/RoundUP.py as:

from decimal import Decimal, getcontext, ROUND_HALF_UP  # # Round UP

round_context = getcontext()
round_context.rounding = ROUND_HALF_UP


# -------------------------------------
def roundUp(x, digits, precision=5):
    tmp = round(Decimal(x), precision)
    fRet = float(tmp.__round__(digits))
    return fRet


# ---------------------------------------------------------------
if __name__ == '__main__':
    fRU = roundUp(2.125, 2)  # -> 2.13
    fRU = roundUp(3.125, 2)  # -> 3.13
    fRU = roundUp(-2.125, 2)  # -> -2.13
    fRU = roundUp(4.125, 2)  # -> 4.13
    fRU = roundUp(2.225, 2)  # -> 2.23
    pass

Accordingly, in the Basic script I changed the location to share:
sFunctionPy = "vnd.sun.star.script:RoundUP.py$roundUp"& _
    "?language=Python&location=share"

When I run it, I get

BASIC runtime error.
Argument is not optional

pointing to line

vRoundUp = oScript.invoke(Array(num, dec), Array(), Array())

Please advise.

Arch Linux 64-bit, X11
Version: 7.6.0.0.alpha0+ (X86_64) / LibreOffice Community
Build ID: f0ff4243d45b11f372a2ed824fbb8806de9cb595
CPU threads: 8; OS: Linux 6.2; UI render: default; VCL: kf5 (cairo+xcb)
Locale: fi-FI (fi_FI.UTF-8); UI: en-US
Calc: threaded
Built on 17 March 2023
Comment 3 QA Administrators 2023-09-14 03:06:02 UTC Comment hidden (obsolete)
Comment 4 edil 2023-09-24 08:18:00 UTC
Dear Buovjaga, why don't you test in the same my environment (windows) and in the same location (user) ?

"The statement BASIC runtime error.
Argument is not optional
pointing to line 
vRoundUp = oScript.invoke(Array(num, dec), Array(), Array()) "

i think simplicity to try :
oScript.invoke(Array(num, dec, 5), Array(), Array())

If you know a better code or advice to obtain the round up, please notice me.
Thank you
Comment 5 Buovjaga 2023-11-01 16:15:44 UTC
Alain: any idea about this?
Comment 6 Alain Romedenne 2023-11-02 18:10:54 UTC
Edil,

You're obviously tackling a complex topic here!

The difference LIES in the way Python and Basic store FP numbers, as well as in the way libO Scripting Framework converts FP numbers from a language to another.

One needs such information to fully diagnose your bug.

I would strongly advise to use Calc cells content - for Python & Basic tests - to guarantee that input numbers share identical FP formats. As well as I would resort to Calc rounding functions for your very case.

more in:
https://en.wikipedia.org/wiki/Floating-point_arithmetic



PS: Observe the steps to reproduce such anomaly aren't precise enough, please amend them instead of pointing fingers the person assisting you..
Comment 7 edil 2023-11-03 14:46:36 UTC
Thank you  Buovjaga and Alain Romedenne for help me, I apologize if you think I wasn't polite.
I thought LO cell.Value as Double and Python floating point number, both 8 bytes was from IEEE 754.
My app use for big ranges and complex calculation Range.DataArray to get an Array(Array()) of values from Calc Sheet, call python function for process them as argument and round results, and return an array(array()) to set values in the same sheet range. This make my routine very fast.
The problem is that python must round some numbers ad then multiply them to other number, so the round difference is important.
Do you have any idea to solve this behavior, or work around ?
Is this a bug o simplicity
Comment 8 Alain Romedenne 2023-11-04 11:41:38 UTC
Hi Edil,

I suggest you use Calc rounding - Round, RoundDown, RoundUp - functions instead of user defined functions:
https://help.libreoffice.org/latest/en-US/text/scalc/01/04060106.html?&DbPAR=CALC

ScriptForge Session service ExecuteCalcFunction() method provides such programming facility..
https://help.libreoffice.org/latest/en-US/text/sbasic/shared/03/sf_session.html?DbPAR=BASIC
.. and is based on "com.sun.star.sheet.FunctionAccess" UNO service. 

Refer to https://help.libreoffice.org/latest/en-US/text/sbasic/shared/calc_functions.html?DbPAR=BASIC#bm_id291592361063458 if you still require to resort to BASIC.

Hope that helps
Comment 9 edil 2023-11-04 21:14:16 UTC
Thank you Alain for your suggests.
In my 30 thousand code rows app, the general function structure is Basic --> Array --> python --> Array --> Basic for improve the faster execution.
For me it is not good idea for each array value in Python call an LO API service, maybe I will add a little number like : 
round(x + 10**-(digits+3), digits), or the classic 
Round = Int(x * 10 ^ digits + 0.5) / 10 ^ digits
What do you think? And about the bug (or incompatibility), it is not correctable?
By Nicola
Comment 10 Mike Kaganski 2024-04-12 11:40:28 UTC
(In reply to Buovjaga from comment #2)
> When I run it, I get
> 
> BASIC runtime error.
> Argument is not optional
> 
> pointing to line
> 
> vRoundUp = oScript.invoke(Array(num, dec), Array(), Array())

Because you must run not the RoundUp function directly, but some other code, that calls the function and passes all the values, like

Sub Main
  MsgBox RoundUp(2.125, 2)
End Sub

Additionally, the script must be in 'instdir/share/Scripts/python/RoundUP.py'.

(In reply to edil from comment #4)
> Dear Buovjaga, why don't you test in the same my environment (windows) and
> in the same location (user) ?

Because not everyone can have the same environment. Reproduction phase involves people trying to repro in different environments.

===

Changing the *Python* code of roundUp to end with something like

    return repr(x)

(so that it simply returns a string representation of the *passed* argument) shows that the value *passed from Basic to Python* is indeed 2.125 (and that is no wonder, since Basic uses IEEE 754, in which, 2.125 is representable exactly). Further changing the code to end with

    return repr(fRet)

shows that it's the *calculation in Python* that has the problem. It happens both in Windows, and in Ubuntu (so not specific to LibreOffice build of Python). And yes, executing this code in Python prompt produces expected '2.13' both in Windows, and in Ubuntu.

Since I'm not an expert in Python, I can only tell that it needs debugging what Python environment details differ in this case.

Setting to NEW.
Comment 11 Mike Kaganski 2024-04-12 12:36:34 UTC
https://stackoverflow.com/questions/33019698/how-to-properly-round-up-half-float-numbers

In fact, it seems that *exactly the behavior of Python in LibreOffice* is correct. Its 'round' does the half-to-even rounding, as the documentation explicitly states [1]. And the StackOverflow article discusses the proper Pythonic way to round up.

Why isn't it correct when trying in Python shell, is unclear, and outside of scope here.

[1] https://docs.python.org/3.8/library/functions.html#round