首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >一个数据精度引发的血案

一个数据精度引发的血案

原创
作者头像
陈林峰
修改2017-09-06 09:40:19
3.9K5
修改2017-09-06 09:40:19
举报
文章被收录于专栏:陈林峰的专栏陈林峰的专栏

前言

最近在做X项目的时候用到了弹性搜索引擎ES(Elasticsearch),在检索遇到了一个诡异的问题,当存储(长)整型数据超过某个值(具体就是百万),就会出现数据精度丢失的情况,比如put下面一个数据

put数据
{
    "uid": 251221233,
    "location": {
        "lat": 22.5405,
        "lon": 113.935
    },
    ......
}

然后get出来,发现uid被转成科学计数,存在精度丢失问题,uid在系统表示用户的身份,出现了偏差导致非常严重的后果,而浮点型数据却没有影响。

{
    "uid": 2.51221e+08
    "location": {
        "lat": 22.5405,
        "lon": 113.935
    },
    ......
}

一、问题定位

项目中首次采用ES,之前对这个搜索引擎了解不多,因此最开始怀疑数据是在搜索引擎那里转坏了,先查资料,后求达人,都没有找到答案,由于ES提供Restful接口,走HTTP协议,通过抓包最后发现get时候数据并没有被修改,那肯定是逻辑代码问题喽。

服务框架采用SRF,存储在ES的数据格式为JSON,编解码使用的是SRF框架的TC库,这个库在后台多个项目中使用过,之前一直都没有遇到过问题,最开始也没有怀疑到它,走了一段弯路。经过定位发现是将json对象转发string的时候出现了数据的改变,如下面的红框代码,出问题就是这一行代码。(这里为了方便其它服务访问ES,封装了一个通用的增删改查的SRF接口进行RPC调用)

JsonValueObjPtr pObj = JsonValueObjPtr::dynamicCast(TC_Json::getValue(httpRsp.getContent()));
JsonInput::readJson(rsp.isExisted, pObj->value["found"], false);
if (rsp.isExisted)
{
    JsonInput::readJson(rsp.sId, pObj->value["_id"], false);
    JsonInput::readJson(rsp.sDataBase, pObj->value["_index"], false);
    JsonInput::readJson(rsp.sTableName, pObj->value["_type"], false);
    rsp.sSource = TC_Json::writeValue(JsonValueObjPtr::dynamicCast(pObj->value["_source"]));
}

走进SRF框架代码,发现TC_Json将所有number数据对象按照double去处理,这样其实也是合理的,但是在转换成string的时候却用了 ostringstream,用流算子做转换的时候会区分数据类型,当数据是整形的时候问题不大,如果是浮点型数据会出现数据被截断,流算子默认按float型数据去处理,这是数据被篡改的原因

[1504593899425_8277_1504593899875.jpg]
[1504593899425_8277_1504593899875.jpg]

问题是数据并不是浮点型,而是整形,而正常用Jce结构体的时候整形转换成json字符串并没有问题,这又是什么原因呢?分析发布正常使用Jce对象的时候都会指定数据类型格式,而TC_Json做解析的时候并没有这样子去做(如下源码),也就是说如果使用TC库去解析json,然后再回写成string,出现大整数或double数据则会出现精度丢失。

JsonValueNumPtr TC_Json::getNum(BufferJsonReader & reader,char head)
{
    bool bFloat=false;
    double dResult=0;

    // ..... 此处省略解析代码
    
    JsonValueNumPtr p = new JsonValueNum();
    p->value=dResult;
    return p;
}

二、解决办法

2.1 TC_Json优化

找到了问题原因,解决起来自然就很容易,TC_Json在进行数据解析的时候指定对数据类型进行指定,避免整形数据转成string当成double型,这样改完之后整形数据再也不会有问题。

    JsonValueNumPtr p = new JsonValueNum();
    p->value=dResult;
    p->isInt=!bFloat;
    return p;

2.2 Double精度问题

改完之后整形数据自然就没啥问题,但是我们知道在计算机系统中,C/C++的浮点数据F/D分别占用32/64位,是按照指数+尾数方式存储,精确范围分别为小数点后6位和15位,采用流算子对double数据进行json转换还是存在精度丢失的问题,虽说浮点型数据在逻辑服务开发工作中比较少用到,但是从框架的角度希望能有一个比较完美的解决方案。

之前miloyip老师讲rapidjson实现的时候,他重点介绍了浮点型数据格式化处理问题,rapidjson处理地非常完美,但代码实现略显复杂,在这里使用标准库提供gcvt函数处理,基本能满足我们的精度要求,代码实现也会显优雅很多。

结语

SRF/TAF框架提供了一些公共函数实现Number到String的转换,大量都采用流算子实现,大家在日常的业务代码开发中,用它处理浮点型的数据要十分注意数据精度丢失问题。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 一、问题定位
  • 二、解决办法
  • 结语
相关产品与服务
数据库
云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档