Forum: Poser Python Scripting


Subject: Floating point decimals and binary representation.

structure opened this issue on Aug 24, 2021 ยท 17 posts


structure posted Tue, 24 August 2021 at 2:40 AM Forum Coordinator

so using the following code I came across the "can't represent this decimal properly" feature.

I understand the reasoning for this, it has to do with the fact that decimals cannot be accurately represented by binary.

But lets say I need real precision ( for some engineering venture perhaps ). The results in blue are what I want, the results in red are what I am getting.

Is there a workaround in python to actually return the decimals I want?

from os.path import basename, dirname, exists, isfile, join, splitext
...
...
# make a new ( numbered ) copy of the original file
def uniquify( path, ext ):
    file_name = path
    if isfile(file_name):
        expand = 0
        while True:
            expand += round( .1, 2 ) 
            exp = "_" + str(expand)
            new_file_name = file_name.split(ext)[0] + str(exp) + ext
            if isfile(new_file_name):
                continue
            else:
                return new_file_name

image.png


changing the .1 to 1/10 yields the same result

image.png

similarly the following code returns odd numbers also.

import numpy
for i in numpy.arange( 0.1, 0.9, 0.1 ):
    print( i )

which returns

0.1
0.2
0.30000000000000004
0.4
0.5
0.6
0.7000000000000001
0.8

if I round the result:

for i in numpy.arange( 0.1, 0.9, 0.1 ):
    print( round( i, 2 ) )

python returns the following ...

0.1
0.2
0.3
0.4
0.5
0.6
0.7
0.8

EDIT : a temporary workaround seems to be slicing the returned number like this

new_file_name = file_name.split(ext)[0] + str(exp[0:4]) + ext

Locked Out


odf posted Tue, 24 August 2021 at 4:17 AM

I'm not sure I understand the problem. If your application is to print version numbers, why represent them as floats, at all?

In general, to represent a number as a string rounded to a certain number of decimal places, say 3, you can use ("%.3f" % x).

for i in numpy.arange( 0.1, 0.9, 0.1 ):
  print("%.1f" % i)

-- I'm not mad at you, just Westphalian.


bagginsbill posted Tue, 24 August 2021 at 7:32 AM

We could literally answer your questions about goofy floats but first let's decide if that rabbit hole is worth ANY of the grief. I suggest instead that you deal with version strings using the dedicated, properly designed features of Python for this. Have a look.

https://packaging.pypa.io/en/latest/version.html


Renderosity forum reply notifications are wonky. If I read a follow-up in a thread, but I don't myself reply, then notifications no longer happen AT ALL on that thread. So if I seem to be ignoring a question, that's why. (Updated September 23, 2019)


bagginsbill posted Tue, 24 August 2021 at 7:34 AM

Oh crap. Right after I wrote that I discovered you have to install that "packaging" package and you might not be able to in Poser Python.


Renderosity forum reply notifications are wonky. If I read a follow-up in a thread, but I don't myself reply, then notifications no longer happen AT ALL on that thread. So if I seem to be ignoring a question, that's why. (Updated September 23, 2019)


bagginsbill posted Tue, 24 August 2021 at 7:46 AM

OK so after a quick experiment I conclude your problem is simply that adding .1 each time is a bad way to do as the slight inaccuracy of representing .1 accumulates. Instead, just iterate on an integer and divide by 10.

Example:

for i in range(100):
    name = 'foo_copy_' + str(i/10) + '.bar'
    print(name)

Works just fine


Renderosity forum reply notifications are wonky. If I read a follow-up in a thread, but I don't myself reply, then notifications no longer happen AT ALL on that thread. So if I seem to be ignoring a question, that's why. (Updated September 23, 2019)


bagginsbill posted Tue, 24 August 2021 at 7:48 AM

Honestly, though, I don't know why you need 0.1, 0.2, 0.3 ... instead of just 1, 2, 3 ...


Renderosity forum reply notifications are wonky. If I read a follow-up in a thread, but I don't myself reply, then notifications no longer happen AT ALL on that thread. So if I seem to be ignoring a question, that's why. (Updated September 23, 2019)


bagginsbill posted Tue, 24 August 2021 at 7:53 AM

I cleaned up your function - it can be much simpler.

# make a new ( numbered ) copy of the original file
def uniquify( path, ext ):
    file_name = path + ext
    i = 0
    while isfile(file_name):
        i += 1
        file_name = path + "_copy_" + str(i/10) + ext
    return file_name


Renderosity forum reply notifications are wonky. If I read a follow-up in a thread, but I don't myself reply, then notifications no longer happen AT ALL on that thread. So if I seem to be ignoring a question, that's why. (Updated September 23, 2019)


adp001 posted Tue, 24 August 2021 at 8:33 AM

bagginsbill posted at 8:28AM Tue, 24 August 2021 - #4425960

I cleaned up your function - it can be much simpler.

# make a new ( numbered ) copy of the original file
def uniquify( path, ext ):
    file_name = path + ext
    i = 0
    while isfile(file_name):
        i += 1
        file_name = path + "_copy_" + str(i/10) + ext
    return file_name

To make that work with Poser Versions != 12, you have to execute this first:

from __future__ import  division

If you don't you will get garbage with this function.

But, as @odf said: The better way is to use Pythons format functions to convert numbers to strings.




adp001 posted Tue, 24 August 2021 at 8:43 AM

Python 3 (Poser 12) understands the old way of formatting strings with "%", and Python 2 (P11) can handle ".format(...)" strings preferred with Python 3.




bagginsbill posted Tue, 24 August 2021 at 11:26 AM

I'm not using Python 12 - but I am using Python 3 because 2 is dead.

Anyway - yes if you're in Python 2 (Poser 11 or below) then division by 10 needs to be i / 10.0.

And yes you can format umpteen ways

f'{i / 10:.1f}'
'%.1f' % (i / 10.0)
str(i / 10.0)

You pick which way you're comfortable with. I usually pick based on the simplest syntax, or the shortest amount of coding, which generally means fewer mistakes.


Renderosity forum reply notifications are wonky. If I read a follow-up in a thread, but I don't myself reply, then notifications no longer happen AT ALL on that thread. So if I seem to be ignoring a question, that's why. (Updated September 23, 2019)


adp001 posted Tue, 24 August 2021 at 2:35 PM

As far as I know, the forum is called "Python" and not "Python 3".

By the way, in the Poser universe there are significantly more Poser 11 users who need Python 2 than those who use Poser 12 with Python 3. And that's likely to continue for a while. As I see it, P12 will probably need another 3-5 years to become reasonably stable and usable. Which means Python 2 will continue to exist in the Poser universe....

Using Python standards (like format for displaying numbers) also has the advantage that there are less problems with conversions/changes. The current disaster regarding Python 3 incompatibility of most scripts should be incentive enough to do things differently in the future and stick to established standards.




odf posted Tue, 24 August 2021 at 6:35 PM

Thanks to this thread I learned that Python 3 has a decimal module.

-- I'm not mad at you, just Westphalian.


adp001 posted Tue, 24 August 2021 at 6:40 PM

https://docs.python.org/2.7/library/decimal.html

:) :) :)




odf posted Tue, 24 August 2021 at 6:41 PM

adp001 posted at 6:36PM Tue, 24 August 2021 - #4425963

bagginsbill posted at 8:28AM Tue, 24 August 2021 - #4425960

I cleaned up your function - it can be much simpler.

# make a new ( numbered ) copy of the original file
def uniquify( path, ext ):
    file_name = path + ext
    i = 0
    while isfile(file_name):
        i += 1
        file_name = path + "_copy_" + str(i/10) + ext
    return file_name

To make that work with Poser Versions != 12, you have to execute this first:

from __future__ import  division

If you don't you will get garbage with this function.

My preference is to use str(i / 10.0) which will work correctly in both Python 2 and 3. Similarly, I like to use the // in Python 2 because it won't break when one upgrades to 3.

-- I'm not mad at you, just Westphalian.


odf posted Tue, 24 August 2021 at 6:43 PM

adp001 posted at 6:42PM Tue, 24 August 2021 - #4426000

https://docs.python.org/2.7/library/decimal.html

:) :) :)

I didn't say that Python 2 didn't have it. It's just that the Python 3 manual was the first hit I found and I was too lazy to check if it was also available for Python 2.

-- I'm not mad at you, just Westphalian.


adp001 posted Tue, 24 August 2021 at 6:52 PM

odf posted at 6:43PM Tue, 24 August 2021 - #4426001

My preference is to use str(i / 10.0) which will work correctly in both Python 2 and 3. Similarly, I like to use the // in Python 2 because it won't break when one upgrades to 3.

Yup, mine too. But it still makes more sense to write a formatted string like ... + "%1.1f" % [value] + ...




structure posted Tue, 24 August 2021 at 8:17 PM Forum Coordinator

Thanks for your feedback guys.

I have no intention of using flouting points in the example cited. I am actually using integers for that. However while writing the code for this I became curious as to how best to work with floating points. (I also tried the decimal module.)

I appreciate your responses.

p.s. @BagginsBill... Thanks for the tidy up

Locked Out