一个数据精度引发的血案

前言

最近在做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型数据去处理,这是数据被篡改的原因

问题是数据并不是浮点型,而是整形,而正常用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的转换,大量都采用流算子实现,大家在日常的业务代码开发中,用它处理浮点型的数据要十分注意数据精度丢失问题。

原创声明,本文系作者授权云+社区-专栏发表,未经许可,不得转载。

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

编辑于

我来说两句

5 条评论
登录 后参与评论

相关文章

来自专栏大内老A

[WCF安全系列]消息的保护等级[下篇]

一、契约的保护等级为绑定进行消息保护设置了“最低标准” 二、显式地将保护等级设置成ProtectionLevel.None与没有设置保护等级有区...

1887
来自专栏祝威廉

StreamingPro添加Scala script 模块支持

SQL 在解析字符串方面,能力还是有限,因为支持的算子譬如substring,split等有限,且不具备复杂的流程表达能力。我们内部有个通过JSON描述的DSL...

493
来自专栏SDNLAB

码农学ODL之Toaster代码解析

Toaster(烤面包机)是OpenDaylight的一个例子,该例子的目的不是让你如何烤面包,而是借这个例子学习OpenDaylight的特性。在Toaste...

3766
来自专栏张善友的专栏

Protocol Buffers的应用

1. Protocol Buffers的介绍 Protocol buffers are Google’s language-neutral, platform-...

17810
来自专栏崔庆才的专栏

腾讯云上 Winpcap 网络编程四之主机通信

由于腾讯云上提供了Windows系统,所以我们这次Winpcap编程选用腾讯云主机实验,让大家简要了解两台云主机的通信方法以及实践过程。

3640
来自专栏加米谷大数据

Hive的数据类型

本文介绍hive的数据类型,数据模型以及文件存储格式。这些知识大家可以类比关系数据库的相关知识。

892
来自专栏我的博客

Tp3.1.2模型学习

1.模型定义 命名规则是除去表前缀的数据表名称,采用驼峰命名,并且首字母大写,然后加上后缀Model 其中tableName是不包含表前缀的数据表名称,一般用...

3094
来自专栏技术博客

Asp.Net Web API 2第六课——Web API路由和动作选择

      Asp.Net Web API第一课——入门http://www.cnblogs.com/aehyok/p/3432158.html

622
来自专栏Java帮帮-微信公众号-技术文章全总结

day26.MySQL【Python教程】

1066
来自专栏用户画像

mysql模拟题二

  3) MSSQLServer2005Enterprise Edition是哪一种版本?

766

扫码关注云+社区