Floating-point issue [being solved?]

Discuss the future of the AutoHotkey language
just me
Posts: 9425
Joined: 02 Oct 2013, 08:51
Location: Germany

Floating-point issue [being solved?]

21 Feb 2014, 01:05

[Update by Lexikos: v2.0-a046 changed the floating-point string format to avoid truncation.]

Source: AHK v2 --- Bugreport

Code: Select all

F := 0.0000000001
MsgBox, 0, AHK, % "F = " . F . " - StrLen = " . StrLen(F)
AHK 1.1 output:

Code: Select all

---------------------------
AHK
---------------------------
F = 0.0000000001 - StrLen = 12
---------------------------
OK   
---------------------------
AHK v2 output:

Code: Select all

---------------------------
AHK
---------------------------
F = 0.000000 - StrLen = 8
---------------------------
OK   
---------------------------
Is this intended behaviour?
Last edited by lexikos on 19 Jun 2020, 18:40, edited 3 times in total.
Reason: update
lexikos
Posts: 9560
Joined: 30 Sep 2013, 04:07
Contact:

Re: Floating-point issue

21 Feb 2014, 02:22

Yes. In v1, floating-point literals are actually just strings. In v2, they are not strings. This produces the same behaviour, but on both versions:

Code: Select all

F := 0.0000000001 + 0
MsgBox, 0, AHK, % "F = " . F . " - StrLen = " . StrLen(F)
To show the number with more precision, you can use format("{1:0.15f}", F).
Types
All literal numbers are pure; format is discarded. For example, MsgBox % 0x1 and MsgBox % (0+1) are both equivalent to MsgBox 1, while MsgBox % 1.0 is equivalent to MsgBox 1.000000.
Source: v2-changes
just me
Posts: 9425
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: Floating-point issue

21 Feb 2014, 04:34

Well,
  • V1.1 has a default precision of 6 decimals for floating-point output, which can be changed easily by SetFormat.
  • V2 doesn't have a SetFormat command any more, but is keeping the v1.1 default precision for floating-point output instead of working with full precision.
  • This is the intended behaviour and you can use the lib functionFormat() to show the number with more (e.g. Format("{1:0.15f}", F)) or less (e.g. Format("{1:0.2f}", F)) precision every time you need it.

Code: Select all

; AutoHotkey v2 alpha

; format(format_string, ...)
;   Equivalent to format_v(format_string, Array(...))
format(f, v*) {
    return format_v(f, v)
}

; format_v(format_string, values)
;   - format_string:
;       String of literal text and placeholders, as described below.
;   - values:
;       Array or map of values to insert into the format string.
;
; Placeholder format: "{" id ":" format "}"
;   - id:
;       Numeric or string literal identifying the value in the parameter
;       list.  For example, {1} is values[1] and {foo} is values["foo"].
;   - format:
;       A format specifier as accepted by printf but excluding the
;       leading "%".  See "Format Specification Fields" at MSDN:
;           http://msdn.microsoft.com/en-us/library/56e442dc.aspx
;       The "*" width specifier is not supported, and there may be other
;       limitations.
;
; Examples:
;   MsgBox % format("0x{1:X}", 4919)
;   MsgBox % format("Computation took {2:.9f} {1}", "seconds", 3.2001e-5)
;   MsgBox % format_v("chmod {mode:o} {file}", {mode: 511, file: "myfile"})
;
format_v(f, v)
{
    local out, arg, i, j, s, m, key, buf, c, type, p, O_
    out := "" ; To make #Warn happy.
    VarSetCapacity(arg, 8), j := 1, VarSetCapacity(s, StrLen(f)*2.4)  ; Arbitrary estimate (120% * size of Unicode char).
    O_ := A_AhkVersion >= "2" ? "" : "O)"  ; Seems useful enough to support v1.
    while i := RegExMatch(f, O_ "\{((\w+)(?::([^*`%{}]*([scCdiouxXeEfgGaAp])))?|[{}])\}", m, j)  ; For each {placeholder}.
    {
        out .= SubStr(f, j, i-j)  ; Append the delimiting literal text.
        j := i + m.Len[0]  ; Calculate next search pos.
        if (m.1 = "{" || m.1 = "}") {  ; {{} or {}}.
            out .= m.2
            continue
        }
        key := m.2+0="" ? m.2 : m.2+0  ; +0 to convert to pure number.
        if !v.HasKey(key) {
            out .= m.0  ; Append original {} string to show the error.
            continue
        }
        if m.3 = "" {
            out .= v[key]  ; No format specifier, so just output the value.
            continue
        }
        if (type := m.4) = "s"
            NumPut((p := v.GetAddress(key)) ? p : &(s := v[key] ""), arg)
        else if InStr("cdioux", type)  ; Integer types.
            NumPut(v[key], arg, "int64") ; 64-bit in case of something like {1:I64i}.
        else if InStr("efga", type)  ; Floating-point types.
            NumPut(v[key], arg, "double")
        else if (type = "p")  ; Pointer type.
            NumPut(v[key], arg)
        else {  ; Note that this doesn't catch errors like "{1:si}".
            out .= m.0  ; Output m unaltered to show the error.
            continue
        }
        ; MsgBox % "key=" key ",fmt=" m.3 ",typ=" m.4 . (m.4="s" ? ",str=" NumGet(arg) ";" (&s) : "")
        if (c := DllCall("msvcrt\_vscwprintf", "wstr", "`%" m.3, "ptr", &arg, "cdecl")) >= 0  ; Determine required buffer size.
          && DllCall("msvcrt\_vsnwprintf", "wstr", buf, "ptr", VarSetCapacity(buf, ++c*2)//2, "wstr", "`%" m.3, "ptr", &arg, "cdecl") >= 0 {  ; Format string into buf.
            out .= buf  ; Append formatted string.
            continue
        }
    }
    out .= SubStr(f, j)  ; Append remainder of format string.
    return out
}
Zelio
Posts: 278
Joined: 30 Sep 2013, 00:45
Location: France

Re: Floating-point issue [solved]

21 Feb 2014, 09:14

By default, it is not better to have always a full precision or full precision with a zero auto-trim at right ? I am not sure that everybody will understand that a script like this will works, not intuitive... but noobs can easly understand how to do a SubStr(), a Round() or a Floor() without use a complicated non-built-in Format() who merge string/number/type, and barbarian like RegEx.

Code: Select all

F := 0.0000000001
MsgBox % F
F *= 10000
MsgBox % F
just me
Posts: 9425
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: Floating-point issue [solved]

21 Feb 2014, 10:09

Brilliant, Zelio. I never thought about to use Round() to achieve this. :D

Code: Select all

F := 0.0000000001
MsgBox, 0, AHK, % "F = " . F . " - StrLen = " . StrLen(F) . " - Type = " . Type(F)
F := Round(F, 15)
MsgBox, 0, AHK, % "F = " . F . " - StrLen = " . StrLen(F) . " - Type = " . Type(F)
Zelio
Posts: 278
Joined: 30 Sep 2013, 00:45
Location: France

Re: Floating-point issue [solved]

21 Feb 2014, 11:27

Also SubStr(F, 1, pos) can works but only if we have full precision by default and if we know the natural number.

Anyway, my doubt about half visible precision (or no zero autotrim) is to create confusion for noobs or during the switch... Can we replace the "display integer or float 6" by "display integer or float max" or "display auto-trimmed full precision" ? (123.0000000000000000 = 123 , 1.0000000001230000 = 1.0000000000123, or whatever to show the hidden information, to display natural and accurate/complete decimale)

Maybe it is only for my personal experience, the only time we need decimal number with same lenght it isn't for compare them in logic or use mathematic, but for store them to a string for a special thing and 6 digit is rarely the good choice, or we use monospaced font for compare them line by line...

For me this feature is like a bad "preview", a kind of msgbox % mystr who become msgbox % substr(mystr, 1, writtenintherock:=6) is not usefull, when we need a customized lenght we say it to the program, else we need the complete information...
lexikos
Posts: 9560
Joined: 30 Sep 2013, 04:07
Contact:

Re: Floating-point issue [solved]

21 Feb 2014, 22:46

v1 by default truncated floating-point values when converted to a string, and that doesn't seem to have caused much confusion. Basically the only difference now is that format() will be used to override the default instead of SetFormat. 6 digits is the same precision that printf uses by default.

How will leaving the default behaviour the same as v1 cause confusion "during the switch"? I think the opposite would be true.
Zelio wrote:By default, it is not better to have always a full precision or full precision with a zero auto-trim at right ?
The thing is: floating-point numbers aren't precise. For instance:

Code: Select all

f1 := 0.10 + 0.20
f2 := 0.30
MsgBox % f1=f2 ? "equal" : "not equal"  ; displays "not equal"
MsgBox % format("{1:0.17g}", f1)  ; displays 0.30000000000000004
Notice that 'g' truncates trailing 0s, but it also produces scientific notation in some cases. For example, 0.00001 with 0.15g produces 1e-005, but apparently that's not really full precision; with 0.17g it produces 1.0000000000000001e-005.
Zelio wrote:By default, it is not better to have always a full precision or full precision with a zero auto-trim at right ?
Just because I like to twist words: AutoHotkey uses double-precision floating point numbers, so by displaying half you actually get full precision. :P
a complicated non-built-in Format() who merge string/number/type
Who's going to be using floating-point numbers with that precision that would be confused by format()? (I don't know the answer; I've never needed such precision in AutoHotkey.) That it's not built-in is irrelevant for two reasons: 1) usage is the same regardless; 2) it'll be built in. Furthermore, the main complexity is in understanding the format specifiers; but SetFormat uses those same "complicated" specifiers.

Anyway, good point about Round().
just me wrote:V2 doesn't have a SetFormat command any more, but is keeping the v1.1 default precision for floating-point output instead of working with full precision.
Not for floating-point output, but for string output of floating-point numbers. The difference is important.


One other important thing: Prior to v1.0.48+ and in any v1.x version with SetFormat Float (not FloatFast), simply storing a floating-point number in a variable caused it to lose precision.
Zelio
Posts: 278
Joined: 30 Sep 2013, 00:45
Location: France

Re: Floating-point issue [solved]

22 Feb 2014, 13:24

"during the switch", I am agree with you but I wanted to point small fraction, for example 0.0000003333333333 display "0.000000", that look likes a "null" value, or 123.0000004949494949 display "123.000000", without say something interesting at the end (expanded and auto-trimmed of V1 if information inside "float > 6").

Also Format() is a good fonction, I want it of course, it was just for compare ease of use but I'm clumsy...
I thought 16 digits was the max, 17 works fasly but it is more an "artefact", no ?
Anyway, nevermind, I am alone to cry, and as you planned V2 promises to be fast and powerfull without all thoses layers of reformat.

I think I found a tiny bug about rounded value... when we created a very long number...

Code: Select all

v := 1.00000049999999990
msgbox % v
v := 1.00000049999999999
msgbox % v ; rounded to 1.000001, more digits
v := 123456.00000049999999999
msgbox % v ; rounded to 123456.000001
v := 123456.00000049999000000
msgbox % v ; so it seems depend of the lenght
lexikos
Posts: 9560
Joined: 30 Sep 2013, 04:07
Contact:

Re: Floating-point issue [solved]

22 Feb 2014, 16:36

What makes you think it's performing rounding? Like I said and demonstrated, floating-point numbers aren't that precise.

Perhaps it's best to show this imprecision by default, as you've been saying.

Edit: To be clear, the default float formatting does perform rounding, and that rounding is done correctly. The input numbers just aren't what you expect, since the numbers you wrote can't be accurately represented in the 64-bit floating-point format. The actual numbers are:

Code: Select all

1.0000004999999998
1.0000005000000001
123456.0000005
123456.00000049999
just me
Posts: 9425
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: Floating-point issue [solved]

23 Feb 2014, 01:40

lexikos wrote:6 digits is the same precision that printf uses by default.
THX, I always wondered what might be the reason for the 6 digits.
Perhaps it's best to show this imprecision by default, as you've been saying.
+1 :mrgreen:
Zelio
Posts: 278
Joined: 30 Sep 2013, 00:45
Location: France

Re: Floating-point issue [solved]

24 Feb 2014, 00:59

I am very curious about "decimal" type (C# http://msdn.microsoft.com/en-us/library/364x0z75.aspx) or equivalent library for C++ (base 10)
Accurate and 28 digits precision, perfect for buisness automation and normal math...
Hypothetically, it is hard to implement to V2? In our case of an interpreted language, if we compare to "double" type then there is no great speed difference in perfomance?
lexikos
Posts: 9560
Joined: 30 Sep 2013, 04:07
Contact:

Re: Floating-point issue [solved]

24 Feb 2014, 02:38

Are you implying that 64-bit numbers are inadequate? I see no reason to make such a change.
Zelio
Posts: 278
Joined: 30 Sep 2013, 00:45
Location: France

Re: Floating-point issue [solved]

24 Feb 2014, 03:23

No, if you want to stay on 64-bit then I mean that "decimal64" is more adequate than "double" for accuracy.
http://en.wikipedia.org/wiki/Decimal64_ ... int_format
The value is stored as the true decimal value with a base 10 exponent and not as the approximative binary value with a base 2 exponent, but I guess you already know (I just learn it, out of my knowledge, I haven't skill but I like theoric)
But decimal floating-point is relativily new and not hardware accelerated, that is why I wonder if it can be good for our interpreted language (relative to parser or loop operation who take a lot of time, else "double" seems to be 100 time faster inside an idiot compiled loop with FPU)
http://en.wikipedia.org/wiki/IEEE_floating_point (IEEE 754-2008)
In other word, more slow and still eternal problem with infinite decimal (1/3*3<>1), but all finite decimal will note create distasters (buisness automation) and can be compare eachother (math logic, more intuitive)...
Just for curiosity, go to http://ta.twi.tudelft.nl/users/vuik/wi2 ... sters.html
Zelio
Posts: 278
Joined: 30 Sep 2013, 00:45
Location: France

Re: Floating-point issue [being solved?]

04 Mar 2014, 00:19

Maybe offtopic and still out of my knowledge but that can be usefull (for curiosity or for an expert who wants to create a lib for community, I will not post a new thread).
I spend a lot of time (I learn) but at the end I can compare performance for an interpreted script like AutoHotkey (If the interpretation take a lot of time then it reduces the elapsed time relatively)...
Without to be built-in it is only two time slower on my old computer (no decimal FPU, no hardware accelerator), so I guess we can have a 2 times better precision and an exact finite decimal without a great performance cost and no error or bad logic... In my eyes, AutoHotkey V2 can be the first strong and easy script language for financial automation or for any accurate basic arithmetic "+-/*"... or a better toy for finite decimal math and logic...
Because of my poor skills (never compiled a dll or used C#) I use a kind of hack for the unmanaged C# dll (I tried a C++ dll who call C# class but without succes...) and I use the simple C# decimal of MSDN (I tried GMP or MPFR without succes...), in other word I haven't found a ready to use dll to download, so I did (x86)...
dfp.zip
(3.04 KiB) Downloaded 315 times

Code: Select all

#SingleInstance Force
Process, Priority, , High
SetBatchlines -1
SetFormat, Float, 0.17
;hModule := DllCall("LoadLibrary", "str", "dfp.dll", "ptr")

decimal_floating_point := DllCall("dfp.dll\add", "astr", "0.1", "astr", "0.2e0", "astr")
maximum_finite_precision := DllCall("dfp.dll\rnd", "astr", decimal_floating_point, "int", 28, "astr") ;AHK still trimed it?
binary_floating_point := decimal_floating_point + 0
msgbox % decimal_floating_point "`n" maximum_finite_precision "`n" binary_floating_point "`n" 0.1 + 0.2 "`n" 0.3 + 0

; not optimized, no handle, it will retransform with decimal.ToString each time (ease of use for testing)...
t0 := a_tickcount , r := "0"
loop 100000
	r := DllCall("dfp.dll\add", "astr", r, "astr", "0.30", "astr")
msgbox % a_tickcount - t0 "ms`n" r

t0 := a_tickcount, r := 0
loop 100000
	r := r + 0.30
msgbox % a_tickcount - t0 "ms`n" r

;DllCall("FreeLibrary", "ptr", hModule)
ExitApp
_3D_
Posts: 277
Joined: 29 Jan 2014, 14:40

Re: Floating-point issue [being solved?]

11 Mar 2014, 10:59

AHK no types (still) and it is normal heavy mathematic to don`t work.
I like next example by lexikos

Code: Select all

f1 := 0.10 + 0.20
f2 := 0.30
MsgBox % f1=f2 ? "equal" : "not equal"  ; displays "not equal"
MsgBox % format("{1:0.17g}", f1)  ; displays 0.30000000000000004
in most cases f1=f2 ? "equal" : "not equal" will return the right decision but in some cases = is treated like assignment [v2.0 solved]

[SOLUTION] some partial solution is (f1 - f2)? "not equal": "equal"
It cost me days to understand why comparison (<= , >=, !=, =) not work in v1.0
AHKv2.0 alpha forever.
guest3456
Posts: 3454
Joined: 09 Oct 2013, 10:31

Re: Floating-point issue

03 Aug 2021, 08:48

just me wrote:
21 Feb 2014, 04:34
This is the intended behaviour and you can use the lib functionFormat() to show the number with more (e.g. Format("{1:0.15f}", F)) or less (e.g. Format("{1:0.2f}", F)) precision every time you need it.

Code: Select all

; AutoHotkey v2 alpha

; format(format_string, ...)
;   Equivalent to format_v(format_string, Array(...))
format(f, v*) {
    return format_v(f, v)
}

; format_v(format_string, values)
;   - format_string:
;       String of literal text and placeholders, as described below.
;   - values:
;       Array or map of values to insert into the format string.
;
; Placeholder format: "{" id ":" format "}"
;   - id:
;       Numeric or string literal identifying the value in the parameter
;       list.  For example, {1} is values[1] and {foo} is values["foo"].
;   - format:
;       A format specifier as accepted by printf but excluding the
;       leading "%".  See "Format Specification Fields" at MSDN:
;           http://msdn.microsoft.com/en-us/library/56e442dc.aspx
;       The "*" width specifier is not supported, and there may be other
;       limitations.
;
; Examples:
;   MsgBox % format("0x{1:X}", 4919)
;   MsgBox % format("Computation took {2:.9f} {1}", "seconds", 3.2001e-5)
;   MsgBox % format_v("chmod {mode:o} {file}", {mode: 511, file: "myfile"})
;
format_v(f, v)
{
    local out, arg, i, j, s, m, key, buf, c, type, p, O_
    out := "" ; To make #Warn happy.
    VarSetCapacity(arg, 8), j := 1, VarSetCapacity(s, StrLen(f)*2.4)  ; Arbitrary estimate (120% * size of Unicode char).
    O_ := A_AhkVersion >= "2" ? "" : "O)"  ; Seems useful enough to support v1.
    while i := RegExMatch(f, O_ "\{((\w+)(?::([^*`%{}]*([scCdiouxXeEfgGaAp])))?|[{}])\}", m, j)  ; For each {placeholder}.
    {
        out .= SubStr(f, j, i-j)  ; Append the delimiting literal text.
        j := i + m.Len[0]  ; Calculate next search pos.
        if (m.1 = "{" || m.1 = "}") {  ; {{} or {}}.
            out .= m.2
            continue
        }
        key := m.2+0="" ? m.2 : m.2+0  ; +0 to convert to pure number.
        if !v.HasKey(key) {
            out .= m.0  ; Append original {} string to show the error.
            continue
        }
        if m.3 = "" {
            out .= v[key]  ; No format specifier, so just output the value.
            continue
        }
        if (type := m.4) = "s"
            NumPut((p := v.GetAddress(key)) ? p : &(s := v[key] ""), arg)
        else if InStr("cdioux", type)  ; Integer types.
            NumPut(v[key], arg, "int64") ; 64-bit in case of something like {1:I64i}.
        else if InStr("efga", type)  ; Floating-point types.
            NumPut(v[key], arg, "double")
        else if (type = "p")  ; Pointer type.
            NumPut(v[key], arg)
        else {  ; Note that this doesn't catch errors like "{1:si}".
            out .= m.0  ; Output m unaltered to show the error.
            continue
        }
        ; MsgBox % "key=" key ",fmt=" m.3 ",typ=" m.4 . (m.4="s" ? ",str=" NumGet(arg) ";" (&s) : "")
        if (c := DllCall("msvcrt\_vscwprintf", "wstr", "`%" m.3, "ptr", &arg, "cdecl")) >= 0  ; Determine required buffer size.
          && DllCall("msvcrt\_vsnwprintf", "wstr", buf, "ptr", VarSetCapacity(buf, ++c*2)//2, "wstr", "`%" m.3, "ptr", &arg, "cdecl") >= 0 {  ; Format string into buf.
            out .= buf  ; Append formatted string.
            continue
        }
    }
    out .= SubStr(f, j)  ; Append remainder of format string.
    return out
}
how does this format_v() func compare to the built-in Format() func? just more precision?

oh i see, you can have string placeholders instead of just ints

lexikos
Posts: 9560
Joined: 30 Sep 2013, 04:07
Contact:

Re: Floating-point issue [being solved?]

06 Aug 2021, 18:04

This topic predates the built-in Format().

Return to “AutoHotkey Development”

Who is online

Users browsing this forum: No registered users and 42 guests