使用Python和Google App Engine问题浮动格式化JSON转储

时间:2016-10-21 09:42:18

标签: python json google-app-engine webapp2

我目前正在使用python并在Google App Engine上创建一个简单的API,允许用户通过使用命令行进行API调用来使用GET,POST,DELETE和PUT谓词。我使用curl来处理API调用的数据。我已经能够成功添加数据并将其显示回来,但问题是我有一个属性为'价格'并且此价格作为浮点数存储在GAE(Google App Engine)的数据存储区中,并且数字在提交时格式化,但是当JSON显示数据存储区中的数据时,它具有不同的格式。例如,存储价格8.99 JSON响应显示8.9900000000000002。我很确定这不是GAE问题,而是JSON问题。

问题示例:

curl --data "name=t-shirt&description=Star Trek&price=8.99&user=test1" -H "Accept: application/json" https://mywebsite.appspot.com/product

正确存储在数据存储区中但返回JSON,如下所示:

{"description": "Star Trek", "price": 8.9900000000000002, "name": "t-shirt", "user": "test1", "key": 5206065687822336}

以下是我的models.py

from google.appengine.ext import ndb

class Model(ndb.Model):
    def to_dict(self):
        d = super(Model, self).to_dict()
        d['key'] = self.key.id()
        return d

class User(Model):
    username = ndb.StringProperty(required=True)
    password = ndb.StringProperty(required=True)

class Product(Model):
    name = ndb.StringProperty(required=True)
    description = ndb.StringProperty(required=True)
    price = ndb.FloatProperty()
    color = ndb.StringProperty()
    size = ndb.StringProperty()
    user = ndb.StringProperty(required=True)

class Sales(Model):
    products = ndb.KeyProperty(repeated=True)
    datetime = ndb.DateTimeProperty(required=True)
    quantity = ndb.IntegerProperty(repeated=True)
    product_cost = ndb.FloatProperty(repeated=True)
    total_cost = ndb.FloatProperty()
    latitude = ndb.FloatProperty()
    longitude = ndb.FloatProperty()
    user = ndb.KeyProperty(required=True)


    def to_dict(self):
        d = super(Sales, self).to_dict()
        d['products'] = [i.id() for i in d['products']]
        return d 

这是product.py:

import webapp2
from google.appengine.ext import ndb
import models
import json

class Product(webapp2.RequestHandler):
    #Create a Product entity
    def post(self):
        if 'application/json' not in self.request.accept:
            self.response.status = 406
            self.response.status_message = 'Not acceptable, API only supports application/json MIME type.'
            return
        new_product = models.Product()
        name = self.request.get('name', default_value=None)
        description = self.request.get('description', default_value=None)
        price = self.request.get('price', default_value=0)
        color = self.request.get('color', default_value=None)
        size = self.request.get('size', default_value=None)
        user = self.request.get('user', default_value=None)

        if name:
            new_product.name = name
        else:
            self.response.status = 400
            self.response.status_message = 'Invalid request, name required'
        if description:
            new_product.description = description
        else:
            self.response.status = 400
            self.response.status_message = 'Invalid request, description required'
        if price:
            new_product.price = float(price)
        if color:
            new_product.color = color
        if size:
            new_product.size = size
        if user:
            new_product.user = user
        else:
            self.response.status = 400
            self.response.status_message = 'Invalid request, username required'
        key = new_product.put()
        out = new_product.to_dict()
        self.response.write(json.dumps(out))
        return

    #Return an Product entity
    def get(self, **kwargs):
        if 'application/json' not in self.request.accept:
            self.response.status = 406
            self.response.status_message = 'Not acceptable, API only supports application/json MIME type.'
            self.response.write(self.response.status_message)
            return
        #Return selected product details
        if 'id' in kwargs:
            out = ndb.Key(models.Product, int(kwargs['id'])).get().to_dict()
            self.response.write(json.dumps(out))
        #Return all product ids
        else:
            q = models.Product.query()
            keys = q.fetch(keys_only=False)
            results = {x.key.id() : x.to_dict() for x in keys}
            self.response.write(json.dumps(results))

我为矫枉过正道歉,但我想确保一切都可以看到。任何帮助将非常感激。我看过一些类似的项目,但我在这里使用字典的方式似乎无法使这种格式正确。

提前谢谢!

1 个答案:

答案 0 :(得分:1)

我认为这实际上不是问题。如果您想了解为何存在这种不满,您可能需要阅读floating point numbers上的维基百科文章。 TLDR是在0和1之间已经存在无限多个有理数,但计算机只能存储有限数量的数据。因此,您通常希望将实数存储在32(单个)或64(双)位数据中,以实现高效计算。因此,机器编号只是实数的一个子集,您通常必须舍入到最接近的机器编号。如果点总是在同一个地方并且您使用的是基数为10的数字,那么这将不会成为一个问题,因此您可能知道舍入“8.99”时没有错误,但这不是正常浮点数的定义,如IEEE 754。

基本上8.99 = 8.9900000000000002在这里。

您可以通过多种方式解决问题:

1)不要解决问题,只需对显示的值进行四舍五入

警告:这可能不是您想要的 通常可以在显示时将错误四舍五入,因为通常错误非常小,例如在游戏中这可能是可以接受的。但是在这种情况下,您似乎正在创建一个商店,如果在不同的地方有不同的舍入,则可能会出现问题。例如,在某些情况下,它可能会在订单上产生0.01的差异,这可能会导致各种问题。不要将价格/金额存储在浮点数中。

2)使用整数值[可能是这里最好的选项]

而不是存储8.99,而是将899作为整数存储在数据库中。如果仅使用加法/减法,则整数算术不会出现舍入问题。

这可能有点不方便,因为它涉及更新数据库和显示数字(即你必须在正确的位置插入一个点)。但是,如果您无法访问专门的数字,这可能是您最安全的赌注,通常被认为是一个不错的选择。

3)使用具有正确基础的定点数

如果您使用这样的数字进行大量算术运算,那么使用定点数库可能会更方便,并且基本上使用量身定制的有理数表示来解决您的问题。它与选项2基本相同,但您不会自己实现所有内容并具有更大的灵活性。但是我个人会选择2,因为我不知道是否有这样的库可以在Python和Javascript中使用,而且它可能只涉及对项目的不必要的依赖。