Jump to content

Is working with decimals buggy, or is there a better way?


 Share

Recommended Posts

Recently I was trying to build a numbered tier set with decimals when I encountered this consistent anomaly. This small reproducer that is supposed to enumerate from 0.01 to 1 incrementing by 0.01 consistently adds a the decimal value of 0.0000000000001 every time it hits 0.81. Has anyone else encountered this, and should it be reported as a bug, or is there a better way to enumerate this type of project?

I am using AutoIt 3.3.8.0, and this same anomaly occurs in both of these examples.

;Example 1
For $x = Number(0.01) To 1 Step Number(0.01)
    ConsoleWrite('- ' & $x & @CRLF)
Next

;Example 2
$r = 0.01
For $x = 1 To 99
    ConsoleWrite('- ' & $r & @CRLF)
    $r += 0.01
Next

Realm

My Contributions: Unix Timestamp: Calculate Unix time, or seconds since Epoch, accounting for your local timezone and daylight savings time. RegEdit Jumper: A Small & Simple interface based on Yashied's Reg Jumper Function, for searching Hives in your registry. 

Link to comment
Share on other sites

Welcome to the strange world of IEEE754.

In this case the problem is that you can't actually represent 0.01 accurately in binary. It's a bit like trying to write 2/3 in decimal, it goes on forever (don't believe me? Wolfram). Imagine if I wanted to keep adding 2/3 in decimal, the computer would add: 0.66...67 + 0.66...67 + 0.66...67 + ... And eventually those 7s are going to make a difference. That is what you are seeing here.

Link to comment
Share on other sites

@JohnOne

Thanks for the tip, I added rounding to my script shortly after posting.

@Mat

I am glad to hear it is not an Au3 bug, rather a common machine/binary anomaly. I had even tried the fractional approach after posting, and found the same anomaly occurring and began to think it must have been something more machine side that was causing it.

My Contributions: Unix Timestamp: Calculate Unix time, or seconds since Epoch, accounting for your local timezone and daylight savings time. RegEdit Jumper: A Small & Simple interface based on Yashied's Reg Jumper Function, for searching Hives in your registry. 

Link to comment
Share on other sites

My actual script where I discovered this anomaly was not in monetary, rather my decimals were as deep as ten thousandths. I found that the anomalies were deviating my calculations by more than 1% in some cases, which is not acceptable for my needs.

I also found another that explains how the function Round() can produce unexpected and innacurate results as well. Has anyone developed a way with "AutoIt" to perform decimal calculations that will provide more accurate results?

Thanks for everyone's 0.02 ; Yes, Pun intended!

Edit: I understand there is a better way with Java, as GPinzone pointed out. However, I would prefer to keep my script uniform in one language, if feasible, and with AutoIt since it's capabilities fits my projects needs more entirely.

Edited by Realm

My Contributions: Unix Timestamp: Calculate Unix time, or seconds since Epoch, accounting for your local timezone and daylight savings time. RegEdit Jumper: A Small & Simple interface based on Yashied's Reg Jumper Function, for searching Hives in your registry. 

Link to comment
Share on other sites

Various Statistics that play a role in another application's calculations. Some of the stats that are important, and present the chances of these anomalies are in example:

$num = $id*$mod ;Where I have detected the anomalies occurring.
$tally += $num

$ave = $num/$occurrence ;This may have produce a couple of anomalies as well

$Mod ranges are almost, if not, always an integer, while $id can range from an integer to a number as deep as ten thousandths.

The main part of the script is an algorithm that will find a low or high median value, from a collection of values of the same object, that has a certain range of occurrences, and return its value modified by another value. When I say modified, the value will be have a certain percentage added or subtracted based on occurrences , and in a few instances based on the amount of occurrences it may return itself, or itself +/- another value. However, the example I provided above is the section that produces the anomalies, and the area where I need the most accuracy performed.

Edit: Typos. I have a bad habit of posting before proofreading ;)

Edited by Realm

My Contributions: Unix Timestamp: Calculate Unix time, or seconds since Epoch, accounting for your local timezone and daylight savings time. RegEdit Jumper: A Small & Simple interface based on Yashied's Reg Jumper Function, for searching Hives in your registry. 

Link to comment
Share on other sites

Edit: I understand there is a better way with Java, as GPinzone pointed out. However, I would prefer to keep my script uniform in one language, if feasible, and with AutoIt since it's capabilities fits my projects needs more entirely.

Oh, I wasn't suggesting you change to Java. This is an issue that occurs across languages. If you were writing software to do stuff with money, you'd be better off using int or long, but you're not so, nevermind. ;)

Gerard J. Pinzonegpinzone AT yahoo.com
Link to comment
Share on other sites

You need to be extra cautious with loops involving fractional (real) start, increment, bound or step.

When you write

For $x = 0.01 To 1 Step 0.01

you should realize that 0.01 does not have an exact representation in binary floating point (as many other previous posts in similar threads point out). So by the For semantics, you loop exit test is for $x >= 1, which doesn't occur at the 100th loop (because your actual step value isn't exactly 0.01, but a slightly lower value). Remember that binary FP are almost always undervalued. Hence you're likely to perform one more loop that you expect.

OTOH, double FP representation offers a range large enough to achieve most everyday's computations, given sufficient caution to implementation. Unless you're dealing with data sets containing both very small and very large values, or you accumulate hundreds millions of terms in a single loop, your results will have 15 to 16 digits correct, which is by far enough for "most purposes".

Edited by jchd

This wonderful site allows debugging and testing regular expressions (many flavors available). An absolute must have in your bookmarks.
Another excellent RegExp tutorial. Don't forget downloading your copy of up-to-date pcretest.exe and pcregrep.exe here
RegExp tutorial: enough to get started
PCRE v8.33 regexp documentation latest available release and currently implemented in AutoIt beta.

SQLitespeed is another feature-rich premier SQLite manager (includes import/export). Well worth a try.
SQLite Expert (freeware Personal Edition or payware Pro version) is a very useful SQLite database manager.
An excellent eBook covering almost every aspect of SQLite3: a must-read for anyone doing serious work.
SQL tutorial (covers "generic" SQL, but most of it applies to SQLite as well)
A work-in-progress SQLite3 tutorial. Don't miss other LxyzTHW pages!
SQLite official website with full documentation (may be newer than the SQLite library that comes standard with AutoIt)

Link to comment
Share on other sites

Those examples were merely testing examples, and not part of my actual script. When I first discovered the anomaly, I wrote those examples quickly in an attempt to identify the problem. Originally my loop exit test was $x = 1, which I am sure you can imagine as I didn't take the time to think out, it didn't stop <Insert embarrassed laugh here>. My actual loop is enumerating the first dimension of an array, and extracting the data from different elements in the 2nd dimension.

My actual algorithm is yet finished. but to give a general idea:

For $i = 0 To UBound($myArray)-1
    $id = $myArray[$i][5] ;These numbers can be integers or numbers upto ten thousandths place.(0.0001)
    $occurence = $myArray[$i][6]

    $num = $id*_ReturnModTier($id, $occurence) ;This is a custom function that searches another array for the mod value.
    ;The returning mod number is always an Integer. However, I may have to use decimals in the future as well.
    $tally += $num
    $totalOccurences += $occurence
Next
$ave = $tally/$occurence

Edit: Sorry, My internet crashed while it was uploading this post. This is the rest of my post.

Edited by Realm

My Contributions: Unix Timestamp: Calculate Unix time, or seconds since Epoch, accounting for your local timezone and daylight savings time. RegEdit Jumper: A Small & Simple interface based on Yashied's Reg Jumper Function, for searching Hives in your registry. 

Link to comment
Share on other sites

I get that. I was merely giving substance to the example posted, mostly for future readers.

You'll get similar (but not always same!) behavior with any language/program whose low-level code uses hardware FP compatible with the Intel base and doesn't provide specialized code/library for FP(*).

Also note that FP computations are _not_ portable in general, in that different platforms (hard + soft) may give diverging results, while still being IEEE-compliant, whatever that means. The issue there is that there are too many possible readings of "the" standard, and there are too many versions/revisions of "the" standard.

Every time you perform seamingly "simple" FP computation, you in fact need to ask yourself where you're going. I agree this is sad state of the art.

EDIT: (*) doesn't mean that Intel hardware is broken (at least currently!), since no hardware platform can be anywhere close to perfect in this domain. Intel is just a common example.

Edited by jchd

This wonderful site allows debugging and testing regular expressions (many flavors available). An absolute must have in your bookmarks.
Another excellent RegExp tutorial. Don't forget downloading your copy of up-to-date pcretest.exe and pcregrep.exe here
RegExp tutorial: enough to get started
PCRE v8.33 regexp documentation latest available release and currently implemented in AutoIt beta.

SQLitespeed is another feature-rich premier SQLite manager (includes import/export). Well worth a try.
SQLite Expert (freeware Personal Edition or payware Pro version) is a very useful SQLite database manager.
An excellent eBook covering almost every aspect of SQLite3: a must-read for anyone doing serious work.
SQL tutorial (covers "generic" SQL, but most of it applies to SQLite as well)
A work-in-progress SQLite3 tutorial. Don't miss other LxyzTHW pages!
SQLite official website with full documentation (may be newer than the SQLite library that comes standard with AutoIt)

Link to comment
Share on other sites

If you can assume that the precision of all the numbers are to the "ten thousandths place (0.0001)," you could theoretically move the decimal point over 5 places and use integer math.

Gerard J. Pinzonegpinzone AT yahoo.com
Link to comment
Share on other sites

Unfortunately, they are numerals, and not strings. Thus 1.1 = 1.1 rather than 1.1000, if only it were that easy. I ended up writing another function to handle my mathematical equations, by breaking each number down into seperate arrays placing the always formatted to place the decimal at a given point in the array, and then all integers that make up the number in their corresponding elements to the right and left of the decimal element. Then it adds the matching elements of each array, before a small algorithm will turn the new array back into a whole number. It was a fun little mathematical jog that tested my wits and my patience for a few moments. However, I ran it through 300+ equations and have yet to discover an error. My old neighbor from Michigan, whom i consider a mathematical genius, has agreed to make up a stress test of equations for it this week, and if all goes well, I may have a tidy little mathematical example to share with the community ;)

Edit: If only I had seen your advice before writing the function, I may taken the easy route and passed each numeral through StringFormat() to add the extra zeros. However, I am glad I have taken this other approach since it may lead to more accurate mathematical work for future projects as well.

Edited by Realm

My Contributions: Unix Timestamp: Calculate Unix time, or seconds since Epoch, accounting for your local timezone and daylight savings time. RegEdit Jumper: A Small & Simple interface based on Yashied's Reg Jumper Function, for searching Hives in your registry. 

Link to comment
Share on other sites

That's the standard way to combat FP inaccuracies: to scale up all values to integers, perform whatever computation is needed and scale the result(s) down accordingly. Due to limited size of native integers (63 bits + sign) it places a constraint on the range you can apply this to, but it still leaves plenty of possibilities for most common jobs.

OTOH you can find a stringized version for arbitrary size basic arithmetic UDF in the example scripts.

This wonderful site allows debugging and testing regular expressions (many flavors available). An absolute must have in your bookmarks.
Another excellent RegExp tutorial. Don't forget downloading your copy of up-to-date pcretest.exe and pcregrep.exe here
RegExp tutorial: enough to get started
PCRE v8.33 regexp documentation latest available release and currently implemented in AutoIt beta.

SQLitespeed is another feature-rich premier SQLite manager (includes import/export). Well worth a try.
SQLite Expert (freeware Personal Edition or payware Pro version) is a very useful SQLite database manager.
An excellent eBook covering almost every aspect of SQLite3: a must-read for anyone doing serious work.
SQL tutorial (covers "generic" SQL, but most of it applies to SQLite as well)
A work-in-progress SQLite3 tutorial. Don't miss other LxyzTHW pages!
SQLite official website with full documentation (may be newer than the SQLite library that comes standard with AutoIt)

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...