Rounding Bug

Report problems with documented functionality
lexikos
Posts: 9494
Joined: 30 Sep 2013, 04:07
Contact:

Re: Rounding Bug

Post by lexikos » 04 Nov 2022, 03:12

@malcev
So you've found a way to round one value in a way that meets your current expectation, which I'll point out is different than the OP's expectation. Now can you generalize it to work with any value, and prove that it has better or more correct results for every possible input, or even a majority?

Once you've done that, then you can submit a pull request.

In the meantime, I found an answer to your previous comment:
By the way in python it returns correct - 1.0.
But is it correct? Python shows that 1.0005 evaluates to 1.0005, which should round to 1.001. Yet it rounds to 1.0, as you said. 1.00049999999999984 also evaluates to 1.0005.

The Python documentation, however, has a good explanation of floating-point issues and limitations and explicitly tells you to keep in mind that it is imprecise.
Interestingly, there are many different decimal numbers that share the same nearest approximate binary fraction. For example, the numbers 0.1 and 0.10000000000000001 and 0.1000000000000000055511151231257827021181583404541015625 are all approximated by 3602879701896397 / 2 ** 55. Since all of these decimal values share the same approximation, any one of them could be displayed while still preserving the invariant eval(repr(x)) == x.

Historically, the Python prompt and built-in repr() function would choose the one with 17 significant digits, 0.10000000000000001. Starting with Python 3.1, Python (on most systems) is now able to choose the shortest of these and simply display 0.1.
Source: 15. Floating Point Arithmetic: Issues and Limitations — Python 3.11.0 documentation
So for the value 1.0005, it is showing you just one of multiple decimal numbers that share the same nearest approximate binary fraction, and yet the result of rounding that number doesn't match up with what value it displays. AutoHotkey v2 shows a different approximation, not necessarily more or less correct, just trying to make the imprecision more obvious to reduce surprises.

User avatar
oldbrother
Posts: 265
Joined: 23 Oct 2013, 05:08

Re: Rounding Bug

Post by oldbrother » 05 Nov 2022, 08:21

Dear all,

My work is related to pricing and I'm using AHK heavily for the price calculations. It bothers me that AHK rounding results are different from the ones calculated in Excel in some cases. Thank you all for helping me to understand the issue. I wrote a work-around function below. It works for me. I didn't see any issues so far.

Code: Select all

SRound(Num,P=0)
{
  If (Num>=0)
    Return Round(Num + 0.000001,P)
  Else
    Return Round(Num - 0.000001,P)
}

malcev
Posts: 1769
Joined: 12 Aug 2014, 12:37

Re: Rounding Bug

Post by malcev » 05 Nov 2022, 08:30

I didn't see any issues so far
But I see. ;)

Code: Select all

msgbox % SRound(2.000004,5)

User avatar
oldbrother
Posts: 265
Joined: 23 Oct 2013, 05:08

Re: Rounding Bug

Post by oldbrother » 05 Nov 2022, 08:41

@malcev I use it for pricing only. 2-3 digits is enough for me.
Have a good day!

User avatar
oldbrother
Posts: 265
Joined: 23 Oct 2013, 05:08

Re: Rounding Bug

Post by oldbrother » 04 Feb 2023, 09:54

I'm using this walk-around function now. It works fine to me:

Code: Select all

SRound(num, N = 0)
{
	Num := Trim(Num)
	Sign := SubStr(Num,1,1)
    if(Sign="+" or Sign ="-")
		Num:= SubStr(Num,2)
	Else
		Sign :=""

	num := num * 10 ** N
	RegExMatch(num, "\d*.\K\d", decimal)
	if(decimal >= 5)
		Result := Ceil(num) / (10 ** N)
	else
		Result := Floor(num) / (10 ** N)
	Return % Sign round(Result,N)
}

guest3456
Posts: 3453
Joined: 09 Oct 2013, 10:31

Re: Rounding Bug

Post by guest3456 » 05 Feb 2023, 22:42

oldbrother wrote:
05 Nov 2022, 08:41
@malcev I use it for pricing only. 2-3 digits is enough for me.
Have a good day!
could you multiply by 1000, and then round, and then divide back down to display?


User avatar
oldbrother
Posts: 265
Joined: 23 Oct 2013, 05:08

Re: Rounding Bug

Post by oldbrother » 23 Feb 2023, 23:05

Just for Fun. :lol:

See how Chat-GPT answered my question:
1.png
1.png (82.49 KiB) Viewed 1840 times
2.png
2.png (127.87 KiB) Viewed 1840 times

User avatar
oldbrother
Posts: 265
Joined: 23 Oct 2013, 05:08

Re: Rounding Bug

Post by oldbrother » 23 Feb 2023, 23:23

guest3456 wrote:
05 Feb 2023, 22:42
could you multiply by 1000, and then round, and then divide back down to display?
It doesn't work.

Code: Select all

Num := 1.035
msgbox % SRound(Num) ; Returns 1.03

SRound(Num)
{
 Num := Round(Num*1000)/1000
 Return Round(Num,2)
}

malcev
Posts: 1769
Joined: 12 Aug 2014, 12:37

Re: Rounding Bug

Post by malcev » 24 Feb 2023, 06:16

Also

Code: Select all

Num := 1.0005
msgbox % SRound(Num, 3)

SRound(Num, n=0)
{
   static doc
   if !doc
   {
      doc := ComObjCreate("htmlfile")
      doc.write("<meta http-equiv=""X-UA-Compatible"" content=""IE=9"">")
   }
   return doc.parentWindow.eval(num ".toFixed(" n ");")
}
Or

Code: Select all

Num := 1.0005
msgbox % SRound(Num, 3)

SRound(Num, n=0)
{
   static js
   if !js
      js := new JsRT.Edge
   return (num<0 ? "-" : "") js.Eval("Number(Math.round(" abs(num) "+'e" n "')+'e-" n "');")
}


/*
 *  ActiveScript for AutoHotkey v1.1
 *
 *  Provides an interface to Active Scripting languages like VBScript and JScript,
 *  without relying on Microsoft's ScriptControl, which is not available to 64-bit
 *  programs.
 *
 *  License: Use, modify and redistribute without limitation, but at your own risk.
 */
class ActiveScript extends ActiveScript._base
{
    __New(Language)
    {
        if this._script := ComObjCreate(Language, ActiveScript.IID)
            this._scriptParse := ComObjQuery(this._script, ActiveScript.IID_Parse)
        if !this._scriptParse
            throw Exception("Invalid language", -1, Language)
        this._site := new ActiveScriptSite(this)
        this._SetScriptSite(this._site.ptr)
        this._InitNew()
        this._objects := {}
        this.Error := ""
        this._dsp := this._GetScriptDispatch()  ; Must be done last.
        try
            if this.ScriptEngine() = "JScript"
                this.SetJScript58()
    }

    SetJScript58()
    {
        static IID_IActiveScriptProperty := "{4954E0D0-FBC7-11D1-8410-006008C3FBFC}"
        if !prop := ComObjQuery(this._script, IID_IActiveScriptProperty)
            return false
        VarSetCapacity(var, 24, 0), NumPut(2, NumPut(3, var, "short") + 6)
        hr := DllCall(NumGet(NumGet(prop+0)+4*A_PtrSize), "ptr", prop, "uint", 0x4000
            , "ptr", 0, "ptr", &var), ObjRelease(prop)
        return hr >= 0
    }
    
    Eval(Code)
    {
        pvar := NumGet(ComObjValue(arr:=ComObjArray(0xC,1)) + 8+A_PtrSize)
        this._ParseScriptText(Code, 0x20, pvar)  ; SCRIPTTEXT_ISEXPRESSION := 0x20
        return arr[0]
    }
    
    Exec(Code)
    {
        this._ParseScriptText(Code, 0x42, 0)  ; SCRIPTTEXT_ISVISIBLE := 2, SCRIPTTEXT_ISPERSISTENT := 0x40
        this._SetScriptState(2)  ; SCRIPTSTATE_CONNECTED := 2
    }
    
    AddObject(Name, DispObj, AddMembers := false)
    {
        static a, supports_dispatch ; Test for built-in IDispatch support.
            := a := ((a:=ComObjArray(0xC,1))[0]:=[42]) && a[0][1]=42
        if IsObject(DispObj) && !(supports_dispatch || ComObjType(DispObj))
            throw Exception("Adding a non-COM object requires AutoHotkey v1.1.17+", -1)
        this._objects[Name] := DispObj
        this._AddNamedItem(Name, AddMembers ? 8 : 2)  ; SCRIPTITEM_ISVISIBLE := 2, SCRIPTITEM_GLOBALMEMBERS := 8
    }
    
    _GetObjectUnk(Name)
    {
        return !IsObject(dsp := this._objects[Name]) ? dsp  ; Pointer
            : ComObjValue(dsp) ? ComObjValue(dsp)  ; ComObject
            : &dsp  ; AutoHotkey object
    }
    
    class _base
    {
        __Call(Method, Params*)
        {
            if ObjHasKey(this, "_dsp")
                try
                    return (this._dsp)[Method](Params*)
                catch e
                    throw Exception(e.Message, -1, e.Extra)
        }
        
        __Get(Property, Params*)
        {
            if ObjHasKey(this, "_dsp")
                try
                    return (this._dsp)[Property, Params*]
                catch e
                    throw Exception(e.Message, -1, e.Extra)
        }
        
        __Set(Property, Params*)
        {
            if ObjHasKey(this, "_dsp")
            {
                Value := Params.Pop()
                try
                    return (this._dsp)[Property, Params*] := Value
                catch e
                    throw Exception(e.Message, -1, e.Extra)
            }
        }
    }
    
    _SetScriptSite(Site)
    {
        hr := DllCall(NumGet(NumGet((p:=this._script)+0)+3*A_PtrSize), "ptr", p, "ptr", Site)
        if (hr < 0)
            this._HRFail(hr, "IActiveScript::SetScriptSite")
    }
    
    _SetScriptState(State)
    {
        hr := DllCall(NumGet(NumGet((p:=this._script)+0)+5*A_PtrSize), "ptr", p, "int", State)
        if (hr < 0)
            this._HRFail(hr, "IActiveScript::SetScriptState")
    }
    
    _AddNamedItem(Name, Flags)
    {
        hr := DllCall(NumGet(NumGet((p:=this._script)+0)+8*A_PtrSize), "ptr", p, "wstr", Name, "uint", Flags)
        if (hr < 0)
            this._HRFail(hr, "IActiveScript::AddNamedItem")
    }
    
    _GetScriptDispatch()
    {
        hr := DllCall(NumGet(NumGet((p:=this._script)+0)+10*A_PtrSize), "ptr", p, "ptr", 0, "ptr*", pdsp)
        if (hr < 0)
            this._HRFail(hr, "IActiveScript::GetScriptDispatch")
        return ComObject(9, pdsp, 1)
    }
    
    _InitNew()
    {
        hr := DllCall(NumGet(NumGet((p:=this._scriptParse)+0)+3*A_PtrSize), "ptr", p)
        if (hr < 0)
            this._HRFail(hr, "IActiveScriptParse::InitNew")
    }
    
    _ParseScriptText(Code, Flags, pvarResult)
    {
        VarSetCapacity(excp, 8 * A_PtrSize, 0)
        hr := DllCall(NumGet(NumGet((p:=this._scriptParse)+0)+5*A_PtrSize), "ptr", p
            , "wstr", Code, "ptr", 0, "ptr", 0, "ptr", 0, "uptr", 0, "uint", 1
            , "uint", Flags, "ptr", pvarResult, "ptr", 0)
        if (hr < 0)
            this._HRFail(hr, "IActiveScriptParse::ParseScriptText")
    }
    
    _HRFail(hr, what)
    {
        if e := this.Error
        {
            this.Error := ""
            throw Exception("`nError code:`t" this._HRFormat(e.HRESULT)
                . "`nSource:`t`t" e.Source "`nDescription:`t" e.Description
                . "`nLine:`t`t" e.Line "`nColumn:`t`t" e.Column
                . "`nLine text:`t`t" e.LineText, -3)
        }
        throw Exception(what " failed with code " this._HRFormat(hr), -2)
    }
    
    _HRFormat(hr)
    {
        return Format("0x{1:X}", hr & 0xFFFFFFFF)
    }
    
    _OnScriptError(err) ; IActiveScriptError err
    {
        VarSetCapacity(excp, 8 * A_PtrSize, 0)
        DllCall(NumGet(NumGet(err+0)+3*A_PtrSize), "ptr", err, "ptr", &excp) ; GetExceptionInfo
        DllCall(NumGet(NumGet(err+0)+4*A_PtrSize), "ptr", err, "uint*", srcctx, "uint*", srcline, "int*", srccol) ; GetSourcePosition
        DllCall(NumGet(NumGet(err+0)+5*A_PtrSize), "ptr", err, "ptr*", pbstrcode) ; GetSourceLineText
        code := StrGet(pbstrcode, "UTF-16"), DllCall("OleAut32\SysFreeString", "ptr", pbstrcode)
        if fn := NumGet(excp, 6 * A_PtrSize) ; pfnDeferredFillIn
            DllCall(fn, "ptr", &excp)
        wcode := NumGet(excp, 0, "ushort")
        hr := wcode ? 0x80040200 + wcode : NumGet(excp, 7 * A_PtrSize, "uint")
        this.Error := {HRESULT: hr, Line: srcline, Column: srccol, LineText: code}
        static Infos := "Source,Description,HelpFile"
        Loop Parse, % Infos, `,
            if pbstr := NumGet(excp, A_Index * A_PtrSize)
                this.Error[A_LoopField] := StrGet(pbstr, "UTF-16"), DllCall("OleAut32\SysFreeString", "ptr", pbstr)
        return 0x80004001 ; E_NOTIMPL (let Exec/Eval get a fail result)
    }
    
    __Delete()
    {
        if this._script
        {
            DllCall(NumGet(NumGet((p:=this._script)+0)+7*A_PtrSize), "ptr", p)  ; Close
            ObjRelease(this._script)
        }
        if this._scriptParse
            ObjRelease(this._scriptParse)
    }
    
    static IID := "{BB1A2AE1-A4F9-11cf-8F20-00805F2CD064}"
    static IID_Parse := A_PtrSize=8 ? "{C7EF7658-E1EE-480E-97EA-D52CB4D76D17}" : "{BB1A2AE2-A4F9-11cf-8F20-00805F2CD064}"
}

class ActiveScriptSite
{
    __New(Script)
    {
        ObjSetCapacity(this, "_site", 3 * A_PtrSize)
        NumPut(&Script
        , NumPut(ActiveScriptSite._vftable("_vft_w", "31122", 0x100)
        , NumPut(ActiveScriptSite._vftable("_vft", "31125232211", 0)
            , this.ptr := ObjGetAddress(this, "_site"))))
    }
    
    _vftable(Name, PrmCounts, EIBase)
    {
        if p := ObjGetAddress(this, Name)
            return p
        ObjSetCapacity(this, Name, StrLen(PrmCounts) * A_PtrSize)
        p := ObjGetAddress(this, Name)
        Loop Parse, % PrmCounts
        {
            cb := RegisterCallback("_ActiveScriptSite", "F", A_LoopField, A_Index + EIBase)
            NumPut(cb, p + (A_Index-1) * A_PtrSize)
        }
        return p
    }
}

_ActiveScriptSite(this, a1:=0, a2:=0, a3:=0, a4:=0, a5:=0)
{
    Method := A_EventInfo & 0xFF
    if A_EventInfo >= 0x100  ; IActiveScriptSiteWindow
    {
        if Method = 4  ; GetWindow
        {
            NumPut(0, a1+0) ; *phwnd := 0
            return 0 ; S_OK
        }
        if Method = 5  ; EnableModeless
        {
            return 0 ; S_OK
        }
        this -= A_PtrSize     ; Cast to IActiveScriptSite
    }
    ;else: IActiveScriptSite
    if Method = 1  ; QueryInterface
    {
        iid := _AS_GUIDToString(a1)
        if (iid = "{00000000-0000-0000-C000-000000000046}"  ; IUnknown
         || iid = "{DB01A1E3-A42B-11cf-8F20-00805F2CD064}") ; IActiveScriptSite
        {
            NumPut(this, a2+0)
            return 0 ; S_OK
        }
        if (iid = "{D10F6761-83E9-11cf-8F20-00805F2CD064}") ; IActiveScriptSiteWindow
        {
            NumPut(this + A_PtrSize, a2+0)
            return 0 ; S_OK
        }
        NumPut(0, a2+0)
        return 0x80004002 ; E_NOINTERFACE
    }
    if Method = 5  ; GetItemInfo
    {
        a1 := StrGet(a1, "UTF-16")
        , (a3 && NumPut(0, a3+0))  ; *ppiunkItem := NULL
        , (a4 && NumPut(0, a4+0))  ; *ppti := NULL
        if (a2 & 1) ; SCRIPTINFO_IUNKNOWN
        {
            if !(unk := Object(NumGet(this + A_PtrSize*2))._GetObjectUnk(a1))
                return 0x8002802B ; TYPE_E_ELEMENTNOTFOUND
            ObjAddRef(unk), NumPut(unk, a3+0)
        }
        return 0 ; S_OK
    }
    if Method = 9  ; OnScriptError
        return Object(NumGet(this + A_PtrSize*2))._OnScriptError(a1)
    
    ; AddRef and Release don't do anything because we want to avoid circular references.
    ; The site and IActiveScript are both released when the AHK script releases its last
    ; reference to the ActiveScript object.
    
    ; All of the other methods don't require implementations.
    return 0x80004001 ; E_NOTIMPL
}

_AS_GUIDToString(pGUID)
{
    VarSetCapacity(String, 38*2)
    DllCall("ole32\StringFromGUID2", "ptr", pGUID, "str", String, "int", 39)
    return String
}


/*
 *  JsRT for AutoHotkey v1.1
 *
 *  Utilizes the JavaScript engine that comes with IE11.
 *
 *  License: Use, modify and redistribute without limitation, but at your own risk.
 */
class JsRT extends ActiveScript._base
{
    __New()
    {
        throw Exception("This class is abstract. Use JsRT.IE or JSRT.Edge instead.", -1)
    }
    
    class IE extends JsRT
    {
        __New()
        {
            if !this._hmod := DllCall("LoadLibrary", "str", "jscript9", "ptr")
                throw Exception("Failed to load jscript9.dll", -1)
            if DllCall("jscript9\JsCreateRuntime", "int", 0, "int", -1
                , "ptr", 0, "ptr*", runtime) != 0
                throw Exception("Failed to initialize JsRT", -1)
            DllCall("jscript9\JsCreateContext", "ptr", runtime, "ptr", 0, "ptr*", context)
            this._Initialize("jscript9", runtime, context)
        }
    }
    
    class Edge extends JsRT
    {
        __New()
        {
            if !this._hmod := DllCall("LoadLibrary", "str", "chakra", "ptr")
                throw Exception("Failed to load chakra.dll", -1)
            if DllCall("chakra\JsCreateRuntime", "int", 0
                , "ptr", 0, "ptr*", runtime) != 0
                throw Exception("Failed to initialize JsRT", -1)
            DllCall("chakra\JsCreateContext", "ptr", runtime, "ptr*", context)
            this._Initialize("chakra", runtime, context)
        }
        
        ProjectWinRTNamespace(namespace)
        {
            return DllCall("chakra\JsProjectWinRTNamespace", "wstr", namespace)
        }
    }
    
    _Initialize(dll, runtime, context)
    {
        this._dll := dll
        this._runtime := runtime
        this._context := context
        DllCall(dll "\JsSetCurrentContext", "ptr", context)
        DllCall(dll "\JsGetGlobalObject", "ptr*", globalObject)
        this._dsp := this._JsToVt(globalObject)
    }
    
    __Delete()
    {
        this._dsp := ""
        if dll := this._dll
        {
            DllCall(dll "\JsSetCurrentContext", "ptr", 0)
            DllCall(dll "\JsDisposeRuntime", "ptr", this._runtime)
        }
        DllCall("FreeLibrary", "ptr", this._hmod)
    }
    
    _JsToVt(valref)
    {
        VarSetCapacity(variant, 24, 0)
        DllCall(this._dll "\JsValueToVariant", "ptr", valref, "ptr", &variant)
        ref := ComObject(0x400C, &variant), val := ref[], ref[] := 0
        return val
    }
    
    _ToJs(val)
    {
        VarSetCapacity(variant, 24, 0)
        ref := ComObject(0x400C, &variant) ; VT_BYREF|VT_VARIANT
        ref[] := val
        DllCall(this._dll "\JsVariantToValue", "ptr", &variant, "ptr*", valref)
        ref[] := 0
        return valref
    }
    
    _JsEval(code)
    {
        e := DllCall(this._dll "\JsRunScript", "wstr", code, "uptr", 0, "wstr", "source.js"
            , "ptr*", result)
        if e
        {
            if DllCall(this._dll "\JsGetAndClearException", "ptr*", excp) = 0
                throw this._JsToVt(excp)
            throw Exception("JsRT error", -2, format("0x{:X}", e))
        }
        return result
    }
    
    Exec(code)
    {
        this._JsEval(code)
    }
    
    Eval(code)
    {
        return this._JsToVt(this._JsEval(code))
    }
    
    AddObject(name, obj, addMembers := false)
    {
        if addMembers
            throw Exception("AddMembers=true is not supported", -1)
        this._dsp[name] := obj
    }
}

malcev
Posts: 1769
Joined: 12 Aug 2014, 12:37

Re: Rounding Bug

Post by malcev » 24 Feb 2023, 06:45

BTW Your code has bugs:

Code: Select all

Num := 555555555.999999
msgbox % SRound(Num, 7)

SRound(num, N = 0)
{
	Num := Trim(Num)
	Sign := SubStr(Num,1,1)
    if(Sign="+" or Sign ="-")
		Num:= SubStr(Num,2)
	Else
		Sign :=""

	num := num * 10 ** N
	RegExMatch(num, "\d*.\K\d", decimal)
	if(decimal >= 5)
		Result := Ceil(num) / (10 ** N)
	else
		Result := Floor(num) / (10 ** N)
	Return % Sign round(Result,N)
}

just me
Posts: 9407
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: Rounding Bug

Post by just me » 25 Feb 2023, 05:36

malcev wrote:BTW Your code has bugs:

Code: Select all

Num := 555555555.999999
MsgBox, % Round(Num, 7) ; -> 555555555.9999991
?

malcev
Posts: 1769
Joined: 12 Aug 2014, 12:37

Re: Rounding Bug

Post by malcev » 25 Feb 2023, 05:43

Do You think is it right answer?

just me
Posts: 9407
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: Rounding Bug

Post by just me » 25 Feb 2023, 05:47

Code: Select all

Num := 555555555.999999
MsgBox, % Format("{:0.15f}", Num) ; -> 555555555.999999050000000
Yes.

malcev
Posts: 1769
Joined: 12 Aug 2014, 12:37

Re: Rounding Bug

Post by malcev » 25 Feb 2023, 05:50

Read topic from start.
We try to get round this situation.

just me
Posts: 9407
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: Rounding Bug

Post by just me » 25 Feb 2023, 05:53

We try to get round the situation when 5 is not rounded up!

malcev
Posts: 1769
Joined: 12 Aug 2014, 12:37

Re: Rounding Bug

Post by malcev » 25 Feb 2023, 05:56

No. If I see that number is 555555555.999999, then I want that it will be exactly "555555555.999999".

just me
Posts: 9407
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: Rounding Bug

Post by just me » 25 Feb 2023, 06:21

So you want to restrict the rounding to use only the currently visible string representation of the floating-point value?

malcev
Posts: 1769
Joined: 12 Aug 2014, 12:37

Re: Rounding Bug

Post by malcev » 25 Feb 2023, 06:29

Not me, but topic starter.
I just posted 2 variants to get it without parsing string of digits manually.

neogna2
Posts: 586
Joined: 15 Sep 2016, 15:44

Re: Rounding Bug

Post by neogna2 » 25 Feb 2023, 16:24

oldbrother wrote:
23 Feb 2023, 23:23
guest3456 wrote:
05 Feb 2023, 22:42
could you multiply by 1000, and then round, and then divide back down to display?
It doesn't work.

Code: Select all

Num := 1.035
msgbox % SRound(Num) ; Returns 1.03

SRound(Num)
{
 Num := Round(Num*1000)/1000
 Return Round(Num,2)
}
Your first Round has no effect here since by default it acts on decimals but after multiplying 1000 there are no decimals left in this case.
But try this (v2 code)

Code: Select all

Num := 1.035
MsgBox SRound(Num) ; Returns 1.04
SRound(Num) {
    return Round(Num * 1000, -1) / 1000  ; 1.035 --> 1035 --> 1040 --> 1.04
}
https://www.autohotkey.com/docs/v2/lib/Math.htm#Round
If N is negative, Number is rounded by N digits to the left of the decimal point

guest3456
Posts: 3453
Joined: 09 Oct 2013, 10:31

Re: Rounding Bug

Post by guest3456 » 25 Feb 2023, 22:05

neogna2 wrote:
25 Feb 2023, 16:24
oldbrother wrote:
23 Feb 2023, 23:23
guest3456 wrote:
05 Feb 2023, 22:42
could you multiply by 1000, and then round, and then divide back down to display?
It doesn't work.

Code: Select all

Num := 1.035
msgbox % SRound(Num) ; Returns 1.03

SRound(Num)
{
 Num := Round(Num*1000)/1000
 Return Round(Num,2)
}
Your first Round has no effect here since by default it acts on decimals but after multiplying 1000 there are no decimals left in this case.
But try this (v2 code)

Code: Select all

Num := 1.035
MsgBox SRound(Num) ; Returns 1.04
SRound(Num) {
    return Round(Num * 1000, -1) / 1000  ; 1.035 --> 1035 --> 1040 --> 1.04
}
https://www.autohotkey.com/docs/v2/lib/Math.htm#Round
If N is negative, Number is rounded by N digits to the left of the decimal point
correct, this is what i meant

since the OP only needs 2 or 3 decimals then i guess it should be fine

i just tried to generalize the code to match ahk's Round() and passed it all of the examples in this thread, but it still failed for one:

Code: Select all

; MsgBox % SRound(1.035, 2)
; MsgBox % SRound(1.045, 2)
; MsgBox % SRound(1.005, 2)
; MsgBox % SRound(1.0005, 3)
; MsgBox % SRound(1.0004999999999999, 3) ;still fails - rounds up
; MsgBox % SRound(2.0004999999999999, 3)
; MsgBox % SRound(2.000004,5)
; MsgBox % SRound(555555555.999999, 7)

SRound(Num, N := "")
{
   if (N = "") || (N = 0)
      return Round(Num)
   else if (N < 0)
      return Round(Num, N)
   else ; N > 0
   {
      ; msgbox Num=%Num%
      ; multiplier := 10**(N+1)
      ; msgbox multiplier=%multiplier%
      ; newnum := Num*multiplier
      ; msgbox newnum=%newnum%
      return Round(Num * (10**(N+1)), -1) / (10**(N+1))
   }
}


Post Reply

Return to “Bug Reports”