-1

Edit: this is in python 2.7.7

Has anyone come across this? I need to serialise an key-value pair object into a string, in which each key contains a float.

There can be any number of entries (I demonstrate just one, "Test", in this example).

It seems that when evaluating the whole object, Python can add decimals to the value, whereas when we look at those exact same values independantly (outside of the object), they appear "normal".

I need a way to make the final, serialised object contain the original values (0.08 in this case) ; or a means to force them to 2DPs.

userConstraints = {
        "test": 0.08
    }

print userConstraints["test"] # Appears ok here
print str(userConstraints) # Has extra DPs here. Why? How to fix?

Output:

0.8
{'test': 0.080000000000000002}

Serialisation logic (on the other side)

obj=ast.literal_eval(theSerialisedString)

Output of serialised obj

{'test': 0.080000000000000002}
20
  • That code doesn't produce that output. Commented Feb 3, 2022 at 10:32
  • Different code, where 0.08 is a computed value, might produce that output. Python 2's float.__str__ truncates values more aggressively that float.__repr__, and printing a container uses the repr of the container's contents. Commented Feb 3, 2022 at 10:34
  • 1
    "once deserialised back to an object it (naturally) contains the very long float with all the decimals" - however you resolve this, the deserialized output will probably be exactly the same output you're getting right now. Even if that output doesn't look like what you expected, it's probably correct. Commented Feb 3, 2022 at 11:02
  • 1
    The IronPython thing is an important detail that's probably the source of the repr discrepancies - IronPython has a completely different implementation of most of the language core. (The standard implementation should show the same float.__repr__ output in all 2.7 releases. The Python 3.1 float.__repr__ behavior was backported in 2.7.0.) Commented Feb 3, 2022 at 11:17
  • 3
    Now that we have more information, we can say for sure that the deserialized data structure is fine. These floats have exactly the values they should have. You're just seeing more of their digits than you're used to. If you want to see less digits, you can change your display handling, but the stored values are fine. Commented Feb 3, 2022 at 11:21

1 Answer 1

2

So, all in all, your version and implementation of Python has a very precise repr() for floating-point numbers. (We can tell it's not a vanilla CPython 2.7.7, since the shorter floating-point repr() from Python 3.1 has been backported into CPython 2.7.0, and that version doesn't do reprs like you say it does.)

Your wire format for serialization is Python repr(), and you want to round off some of those lengthy floats, because reasons.

That's no problem, since Python comes with reprlib - a module for customizing repr (confusingly repr in Python 2).

Et voilà:

import repr as reprlib  # Python 2
import reprlib          # Python 3


class TruncatedFloatRepr(reprlib.Repr):
    def repr_float(self, x, level):
        v = repr(x)
        if "." in v:
            decimals_after_dot = len(v.partition(".")[-1])
            if decimals_after_dot > 5:  # Too many decimals? Truncate!
                return "%.2f" % x
        # Otherwise, use the vanilla `repr`.
        return v


userConstraints = {
    "test": 0.08000000004,
    "hello": ["world", "beep", 3.0, "boop"],
    "one": ("two", 1.1),
}

print(TruncatedFloatRepr().repr(userConstraints))

The output is

{'hello': ['world', 'beep', 3.0, 'boop'], 'one': ('two', 1.1), 'test': 0.08}

and of course you might want to consider changing that 5 to something else, or maybe count the number of zeroes in the decimal part, or whatever suits your business logic fancy. As it is, this implementation will just always truncate numbers with too much decimal precision, and that's likely not what you really want.

5
  • Seems the version of ironpython I'm stuck with doesn't have this module. I think at this point I should just push back on the rounding request since the solution works fine as is anyway. Commented Feb 3, 2022 at 11:55
  • 1
    Well, reprlib itself is a pure-Python module, so if you need it, you can just vendor it from e.g. github.com/python/cpython/blob/2.7/Lib/repr.py
    – AKX
    Commented Feb 3, 2022 at 11:58
  • 2
    It's a naming issue. reprlib was called repr in Python 2, but that was a terrible name that clashed with the built-in repr function, so they renamed it in Python 3. Commented Feb 3, 2022 at 12:10
  • 1
    Just import repr as reprlib. Commented Feb 3, 2022 at 12:12
  • 1
    @user2357112supportsMonica Ah, yeah, you're correct. docs.python.org/2/library/repr.html Edited into the answer.
    – AKX
    Commented Feb 3, 2022 at 12:30

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.