1

I'm having a hard time rounding off values in dicts. What I have is a list of dicts like this:

y = [{'a': 80.0, 'b': 0.0786235, 'c': 10.0, 'd': 10.6742903}, {'a': 80.73246, 'b': 0.0, 'c':   
10.780323, 'd': 10.0}, {'a': 80.7239, 'b': 0.7823640, 'c': 10.0, 'd': 10.0}, {'a': 
80.7802313217234, 'b': 0.0, 'c': 10.0, 'd': 10.9762304}]

I need to round off the values to just 2 decimal places.

When I try the following:

def roundingVals_toTwoDeci(y):

    for d in y:
        for k, v in d.items():
            v = ceil(v*100)/100.0
            print v
            d[k] = v
    return
roundingVals_toTwoDeci(y)
s = json.dumps(y)
print s

I get:

0.0
0.0
18.2
0.0
27.3
54.5
0.0
0.0
0.0
[{"a": 0.0, "b": 0.0, "c": 27.300000000000001, "d": 0.0, "e": 54.5, "f": 0.0, "g": 18.199999999999999, "h": 0.0, "i": 0.0}]

I need to make this work with versions 2.4+ and so am not using dict comprehensions. First, I am having a hard time looping through all the key, values in all the dicts in the original. Second, this result has just 1 decimal point instead of 2 when it prints inside the function? Third, why is the 'json.dumps' and then 'print' not showing the values from inside the function?

EDIT:

Working with @Mark Ransom's answer below, I get the desired o/p. However, I have to urlencode the json.dumps value and send it to a URL. At the URL, it decodes the values into all the decimal places. So, for example, if, josn.dumps gives {"a": 9.1}, the URL shows it (after urlencode) as 9.10034254344365. The modified code is as below:

class LessPrecise(float):
    def __repr__(self):
        return str(self)

def roundingVals_toTwoDeci(y):
    for d in y:
        for k, v in d.items():
            v = LessPrecise(round(v, 2))
            print v
            d[k] = v




roundingVals_toTwoDeci(y)
j = json.dumps(y)
print j

params = urllib.urlencode({'thekey': j}) 

print json.dumps gives {"a": 9.1} At the URL after urlencode, it gives 9.1078667322034 instead of 9.1as in the following:

Output:::

100.0
0.0
0.0
0.0
100.0
0.0
0.0
0.0
81.8
0.0
18.2
0.0
90.0
0.0
0.0
10.0
[{"a": 100.0, "b": 0.0, "c": 0.0, "d": 0.0}, {"a": 100.0, "b": 0.0, "c": 0.0, "d": 0.0}, {"a":
81.8,  "b": 0.0, "c": 18.2, "d": 0.0}, {"a": 90.0, "b": 0.0, "c": 0.0, "d": 10.0}]

At the URL:

9.100000381469727

The JSON string after json.dumps()

[{"a": 80.0, "b": 0.0, "c": 10.0, "d": 10.0}, {"a": 100.0, "b": 0.0, "c": 0.0, "d": 0.0}, {"a":  
80.0, "b": 0.0, "c": 10.0, "d": 10.0}, {"a": 90.0, "b": 0.0, "c": 0.0, "d": 10.0}]

The urlencode string - after decoding at http://meyerweb.com/eric/tools/dencoder/

thekey=[{"a": 80.0, "b": 0.0, "c": 10.0, "d": 10.0}, {"a": 100.0, "b": 0.0, "c": 0.0, "d": 
0.0}, {"a": 80.0, "b": 0.0, "c": 10.0, "d": 10.0}, {"a": 90.0, "b": 0.0, "c": 0.0, "d": 10.0}]

At the URL, I get values like 18.200000762939453(this value is from a later script run)

askance
  • 1,077
  • 4
  • 14
  • 19
  • 1
    `JSONEncoder` uses `repr`, and repr prints floats with all their available precision. The only possible solutions are to inherit from `JSONEncoder` and round while actually converting the values to a string, or else wrapa the floats into your own type `RoundedFloat` and register a serializer for that. Also note that repr's behaviour depends on the Python version used. – jhermann Oct 02 '13 at 19:54
  • When I do a `print y` after calling `roundingVals_toTwoDeci(y)` but before `s = json.dumps(y`, I get the same exact problem. So, could it be an issue with the function/calling the function/return statement instead? (or, in addition to it) – askance Oct 02 '13 at 19:58
  • `print y` does the exact same `repr` to convert the floating point to a string when that number is inside a list or dictionary. – Mark Ransom Oct 02 '13 at 20:02
  • Ok. What I did was add `v = str(v)` after `v = round(v, 2)` inside the function and now it seems to be working fine. But would `str(v)` be an issue when json sends the data to the external URL (if the URL is not conf. to accept strings or is conf. to accept only a certain datatype other than string, viz., 'float'? My other question is why does this print only upto 1 decimal point - my requirement is for 2 decimal points. – askance Oct 02 '13 at 20:11
  • @jhermann that deserves to be an answer, then you can format it properly. – Mark Ransom Oct 02 '13 at 20:13
  • The new information doesn't make any sense. You're passing a string to `urlencode`, it's not going to change the number inside the string. Can you tell us the *exact* string you get from `json.dumps` and the *exact complete* string you get from `urlencode`? – Mark Ransom Oct 03 '13 at 01:47
  • Edited the question to include the two strings from `json.dumps()` and the decoded values from `urlencode`. – askance Oct 03 '13 at 04:18
  • Thanks. As you can see, the encoded URL is correct so there's absolutely nothing you can do to fix anything on that end; the problem is on the page receiving the URL. Before you do anything else read http://stackoverflow.com/questions/2100490/floating-point-inaccuracy-examples – Mark Ransom Oct 03 '13 at 19:41
  • Thanks. Was waiting to hear back from the guys behind the URL. The decoded params are fine as you point above and it's their app (URL) that needs to be dealt with. – askance Oct 08 '13 at 16:36

8 Answers8

4

Taking the best bits from a couple of other answers:

class LessPrecise(float):
    def __repr__(self):
        return str(self)

def roundingVals_toTwoDeci(y):
    for d in y:
        for k, v in d.items():
            v = LessPrecise(round(v, 2))
            print v
            d[k] = v

>>> roundingVals_toTwoDeci(y)
80.0
10.0
0.08
10.67
80.73
10.78
0.0
10.0
80.72
10.0
0.78
10.0
80.78
10.0
0.0
10.98
>>> s=json.dumps(y)
>>> s
'[{"a": 80.0, "c": 10.0, "b": 0.08, "d": 10.67}, {"a": 80.73, "c": 10.78, "b": 0.0, "d": 10.0}, {"a": 80.72, "c": 10.0, "b": 0.78, "d": 10.0}, {"a": 80.78, "c": 10.0, "b": 0.0, "d": 10.98}]'
Mark Ransom
  • 299,747
  • 42
  • 398
  • 622
  • The answer works fine - o/p is values without strings and rounded off to 1 decimal on my machine. When I send the data to the URL after `urlencode`, it appears there with all 9/10 decimal points instead of the 1 decimal place that it shows upon printing after `json.dumps` which is what I want to avoid. – askance Oct 02 '13 at 21:31
  • @user2480526 in that case `urlencode` is converting the string back into numbers before converting to another string. The problem needs to be fixed there. I wonder if `urlencode` would work with strings instead? – Mark Ransom Oct 02 '13 at 21:48
  • Tried it with string values. It is decoded at the URL with all the decimal places - `9.100000381469727` while running my `print json.dumps` returns {"a": "9.1"} – askance Oct 02 '13 at 22:02
  • [This](http://stackoverflow.com/questions/1447287/format-floats-with-standard-json-module) answer leads to the same decoding at the URL. – askance Oct 02 '13 at 22:05
  • @user2480526 the second answer on that thread does something similar to what I did. What exactly are you passing to `urlencode`? – Mark Ransom Oct 02 '13 at 22:29
  • I am doing a `json.dumps()` and then passing the value to `urlencode`. Right now, I am passing `[{"a": "88.9", "b": "0.0", "sc": "0.0", "d": "11.1"}, {"a": "100.0", "b": "0.0", "c": "0.0", "d": "0.0"}, {"a": "81.8", "b": "0.0", "c": "9.1", "d": "9.1"} and at the URL for "d" I get 9.1068906293. I tried your solution and got the dict with values as floats - 9.1 instead of "9.1" but at the URL I get the same 10-15 decimal places. – askance Oct 02 '13 at 22:34
  • @user2480526, you're not being specific enough - what is the exact code you're using for `urlencode`? Maybe you should edit it into the question. – Mark Ransom Oct 02 '13 at 23:03
  • Oops! I edited the question with a solution from elsewhere. Edited it again to include your code. – askance Oct 02 '13 at 23:41
2
import json


y = [{'a': 80.0, 'b': 0.0786235, 'c': 10.0, 'd': 10.6742903}, {'a': 80.73246, 'b': 0.0, 'c':   
10.780323, 'd': 10.0}, {'a': 80.7239, 'b': 0.7823640, 'c': 10.0, 'd': 10.0}, {'a': 
80.7802313217234, 'b': 0.0, 'c': 10.0, 'd': 10.9762304}]

def roundingVals_toTwoDeci(y):

    for d in y:
        for k, v in d.items():
            v = round(v,2) # <--- round() does exact that.
            d[k] = v # <--- You need to put the rounded v back in d
            print v
    return

roundingVals_toTwoDeci(y)
s = json.dumps(y)
print s
Martin
  • 391
  • 1
  • 4
  • 15
2

JSONEncoder uses repr, and repr prints floats with all their available precision. The only possible solutions are to inherit from JSONEncoder and round while actually converting the values to a string (which implies to copy and adapt some code from the json.encoder module), or else wrap the floats into your own type RoundedFloat and register a serializer for that. Also note that repr's behaviour depends on the Python version used.

As often with non-obvious behaviour, the observation during debugging can trick you: print uses str(), and str() rounds at a certain point, unlike repr() which shows the naked ugliness of floating point maths.

The proof is in the code:

>>> class F(float):
...     def __str__(self): return "str"
...     def __repr__(self): return "repr"
...     
... 
>>> print F(1)
str
>>> F(1)
repr
>>> repr(1-1e-15)
'0.999999999999999'
>>> str(1-1e-15)
'1.0'
jhermann
  • 2,071
  • 13
  • 17
  • PS: The `json` module's author probably intended `json.encoder.FLOAT_REPR = lambda f: "%.2f" % f` to be possible, but then that has a bug (`FLOAT_REPR` is only used at import time, not at runtime). – jhermann Oct 02 '13 at 20:30
  • I am trying several solutions given here and [here](http://stackoverflow.com/questions/1447287/format-floats-with-standard-json-module). I now get the 2 decimal places but when the URL decodes it, it shows e.g., 9.1087978601324 instead of 9.1. After `w=json.dumps`, I do a `urlencode(w)`. – askance Oct 02 '13 at 22:48
1

Answering the second part of your question

Try replacing line 5 of your code with:

 v = round(v, 2)

This will round the number to two decimal places. Using round, I get

[{'a': 80.0, 'c': 10.0, 'b': 0.08, 'd': 10.67}, {'a': 80.73, 'c': 10.78, 'b': 0.0, 'd': 10.0}, {'a': 80.72, 'c': 10.0, 'b': 0.78, 'd': 10.0}, {'a': 80.78, 'c': 10.0, 'b': 0.0, 'd': 10.98}]

I am using Python 2.7.2. Here's all the code:

from math import ceil 
import json

y = [{'a': 80.0, 'b': 0.0786235, 'c': 10.0, 'd': 10.6742903},
     {'a': 80.73246, 'b': 0.0, 'c': 10.780323, 'd': 10.0},
     {'a': 80.7239, 'b': 0.7823640, 'c': 10.0, 'd': 10.0},
     {'a': 80.7802313217234, 'b': 0.0, 'c': 10.0, 'd': 10.9762304}]

def roundingVals_toTwoDeci(y):
    for d in y:
        for k, v in d.items():
            v = round(v, 2)
            #print v
            d[k] = v
    return

roundingVals_toTwoDeci(y)
s = json.dumps(y)
print s
mdml
  • 22,442
  • 8
  • 58
  • 66
  • I am getting the same exact result as before. Could it be the way I am calling the function or using the 'return' statement? – askance Oct 02 '13 at 19:33
  • I ran your code exactly as is besides replacing line 5 as in my answer, and commenting out line 6 where you print the rounded value. Doing that, I obtain the output in my answer. – mdml Oct 02 '13 at 19:37
  • I am not sure why I am not getting it then. I am doing the exact same thing. I am using 2.6 (The values in the data, i.e., in 'y' - the list of dicts, keep changing whenever the script is called though I don't think it should matter.) – askance Oct 02 '13 at 19:42
  • Do you mean that your output still isn't rounded, or is the output somehow different? – mdml Oct 02 '13 at 19:47
  • By same as before I mean: `[{"a": 0.0, "b": 0.0, "c": 0.0, "d": 90.900000000000006, "e": 9.0999999999999996, "f": 0.0, "g": 0.0, "h": 0.0, "i": 0.0}]` When I do a print statement inside the loop, I get the earlier values- rounded off to 1 decimal place but the print statement outside the function gives what is above. – askance Oct 02 '13 at 19:52
  • I will add all my code to the answer. I would try running that completely separately just to see. – mdml Oct 02 '13 at 20:05
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/38506/discussion-between-mtitan8-and-user2480526) – mdml Oct 02 '13 at 20:09
  • I wonder if it has to do with your platform? With 2.7.1 on Windows I get the same results as @user2480526. – Mark Ransom Oct 02 '13 at 21:20
1

I don't understand what relates to json, but I can propose:

from math import ceil

y = [{'a': 80.0, 'b': 0.0786235, 'c': 10.0, 'd': 10.6742903},
     {'a': 80.73246, 'b': 0.0, 'c': 10.780323, 'd': 10.0},
     {'a': 80.7239, 'b': 0.7823640, 'c': 10.0, 'd': 10.0},
     {'a': 80.7802313217234, 'b': 0.0, 'c': 10.0, 'd': 10.9762304}]

class TwoDec(float):
    def __repr__(self):
        return "%.2f" % self

def roundingVals_to_TwoDeci(y,ceil=ceil,TwoDec=TwoDec):
    for d in y:
        for k, v in d.iteritems():
            d[k] = TwoDec(ceil(v*100)/100)

roundingVals_to_TwoDeci(y)
for el in y:
    print el

result

{'a': 80.00, 'c': 10.00, 'b': 0.08, 'd': 10.68}
{'a': 80.74, 'c': 10.79, 'b': 0.00, 'd': 10.00}
{'a': 80.73, 'c': 10.00, 'b': 0.79, 'd': 10.00}
{'a': 80.79, 'c': 10.00, 'b': 0.00, 'd': 10.98}
eyquem
  • 26,771
  • 7
  • 38
  • 46
  • Thanks. +1 for using iteritems() - forgot about that :) This gives the desired o/p but the JSON/urlencode thing is driving me nuts! – askance Oct 03 '13 at 00:36
1

I know this question is old, but here is a quick one-liner solution that works at least here in Linux Python 2.7.6 and might be interesting to someone else:

y = [{ x : round(z, 2) for x,z in yi.items()} for yi in y ]

However, this might be inefficient for larger data sets, as it re-generates the list/dict structure.

bluesceada
  • 76
  • 5
0
[[round(d[key],2) for key in d] for d in y]
[{key:round(d[key], 2) for key in d} for d in y]
HatLess
  • 10,622
  • 5
  • 14
  • 32
Lars
  • 1
  • 1
  • 4
    As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers [in the help center](/help/how-to-ask). – Community Sep 17 '21 at 11:48
0

Round dictionary values one-liner

with d as the dict and here to 2 decimal places

d = {k: round(v, 2) for k, v in d.items()}

Given the subject line Round off dict values to 2 decimals, this is what most will be coming here to find.

dictionary comprehension might be used in a search

gseattle
  • 986
  • 1
  • 14
  • 23