0

I'm working with some in-place code dealing with formatting user-stored floating point numbers for human display.

The current implementation does this:

"{0:.24f}".format(some_floating_point).rstrip('0')

which makes sense and works just fine for the most part. But when faced with a value of such as 0.0003 things don't go as well.

>>> "{0:.24f}".format(0.0003).rstrip('0')
'0.000299999999999999973719'

Some further investigation indicates that Python seems to change the underlying representation based on the number of digits requested?

>>> "{0:.15f}".format(0.0003)
'0.000300000000000'
>>> "{0:.20f}".format(0.0003)
'0.00029999999999999997'

My assumption is single precision vs double.

The user enters these values where they are stored in the database as a double, and when the form is rendered again later the same value is prepopulated in the field. Therefore I need a 1:1 mapping of these representations.

My question is therefore: What is an elegant, and more importantly safe way to deal with this behavior? My best efforts so far have involved log10 and are less than ideal to put it nicely.

EDIT: As Prune points out the value is not actually changing, but rather the rounding done by format will carry over causing a set of 9s to become 0s (d'oh). The behavior makes sense then, but the solution is still escaping me.

1
  • There isn't an elegant solution to this, it's inherent in the difference between base 2 and base 10. I could provide an answer, but it too would require log10 - you need to limit the number of digits in the format to one less than the number of significant digits stored in the double. Commented Nov 10, 2016 at 4:46

1 Answer 1

1

You are receiving the number as stored. 0.0003 cannot be stored exactly as a binary fraction. To illustrate:

>>> 0.00029999999999999997 == 0.0003
True

Print formatting rounds the number at the least significant digit. Double precision merely pushes the problem farther to the right. To fully "solve" the problem to base-10 eyes, you need to switch to decimal arithmetic, or perhaps build your own string handler for numbers that are sufficiently close to a simpler value (a suspicious string of 9's or 0's in the fractional part).


Here's the start of a function for you. I tested it with 0.0004, which stores as a hair more than 0.0004; the 9's case is left as an exercise :-) .

def str_round(x):
    size = 6
    nines = '9'*size
    zeros = '0'*size

    str = "{0:.24f}".format(x).rstrip('0')
    str_len = len(str)
    print str, str_len

    if nines in str:
        # replace leading digit with one more
        pos = str.index(nines)
        # ADD CODE HERE
        # Turn the leading portion into an integer;
        #   increment and convert back to zero-leading string.
        # Fill out the rest with zeros.

    elif zeros in str:
        # Change all trailing digits to 0
        pos = str.index(zeros)
        str = str[:pos] + '0'*(str_len - pos)

    return str

print str_round(0.0004)
Sign up to request clarification or add additional context in comments.

1 Comment

Aha, I somehow failed to realize that e.g. 29999 rounds up to 30000. So it "breaks" when asking to format with a digit that's not 9 in this case. For some reason I thought it was doing something more complex. Still doesn't provide much of a solution however.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.