专栏首页陈林峰的专栏一个数据精度引发的血案
原创

一个数据精度引发的血案

前言

最近在做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 删除。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 一个MySQL时间戳精度引发的血案

    最近工作中遇到两例mysql时间戳相关的问题,一个是mysql-connector-java和msyql的精度不一致导致数据查不到;另一例是应用服务器时区错误导...

    猿天地
  • 一个Sqrt函数引发的血案

    好吧,我承认我标题党了,不过既然你来了,就认真看下去吧,保证你有收获。 我们平时经常会有一些数据运算的操作,需要调用sqrt,exp,abs等函数,那么时候你...

    范蠡
  • 一个 Array.concat 引发的血案

    在之前的 提升 Node.js 服务稳定性,需要关注哪些指标?这篇文章中,我们介绍了服务端稳定性需要关注的一些指标,其中有一个非常重要的指标 Libuv lat...

    JowayYoung
  • jQuery.unique引发一个血案

    项目开发过程中,PM说系统只要在一个特定的浏览器中运行就好,但是在其他的浏览器中不能出现逻辑的错误,所以在开发过程中,前端和后台选择是Chrome浏览器,没有仔...

    八哥
  • 一个MySQL索引引发的血案

    本人在做测试服务的过程中,开发了一个功能,就是从两个库的两张表从查出来一个账号的login_id和user_id,功能非常简单,就是执行sql语句,处理返回结果...

    FunTester
  • 一个编译参数引发的血案

    提示:公众号展示代码会自动折行,建议横屏阅读 问题描述 前几天进行测试,发现一个神奇的现象:不加任何优化的版本与加了-O2参数的版本测试结果不一致! 主要代...

    腾讯数据库技术
  • 一个域名引发的血案……

    6月29日凌晨,无数球迷正放下小龙虾、握紧啤酒杯,屏气凝神观看三狮军团英格兰鏖战欧洲红魔比利时。

    腾讯游戏云
  • 一个空格引发的血案

    系统运维从来就是一个精细化的工作,除了规则与规范的约束之外,运维人员的严谨、谨慎也必不可少,有时候一个简单的错误就会导致一场灾难,小到一个字符,一个空格。 本文...

    数据和云
  • 一个基因引发的血案

    大家好,我是老米,学习生信一个月,这是我的第二篇Markdown。不知道多少人还记得我的第一个作品:原来一个星期真的可以零基础入门TCGA数据挖掘,甚至mark...

    生信技能树

扫码关注云+社区

领取腾讯云代金券