一个数据精度引发的血案

前言

最近在做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 条评论
登录 后参与评论

相关文章

来自专栏CSDN技术头条

十五条有用的Golang编程经验

本文作者在很短的时间内就从对Golang一无所知到开发出真正的产品。在学习Golang的过程中,他总结出十五条编程经验以分享给读者。以下是译文。 ? 像许多其他...

2868
来自专栏Android开发经验

ExpandableStickyListHeadersListView遇到的一个问题

1334
来自专栏崔庆才的专栏

看完这篇文章还会不懂Python装饰器?掐死小编吧

2446
来自专栏令仔很忙

Spring从入门到精通(一)----IoC(控制反转)

在采用面向对象方法设计的软件系统中,它的底层实现都是由N个对象组成的,所有的对象通过相互合作,最终实现系统的业务逻辑。

902
来自专栏学习力

《Java从入门到放弃》框架入门篇:spring中AOP的配置方式

20511
来自专栏iOS技术

何为代码质量?——用脑子写代码引言正文总结

为什么项目维护困难、BUG 反复?实际上很多时候就是代码质量的问题。代码架构就像是建筑的钢筋结构,代码细节就像是建筑的内部装修,建筑的抗震等级、简装或豪装完全取...

562
来自专栏窗户

shell编程/字库裁剪(3)——验证

  程序写完了,必须要验证,这是重要的方法论。因为如果不验证,则不会知道程序写的对还是不对。学过人工智能或者控制论都知道,反馈非常重要,反馈形成闭环,可以用来指...

20810
来自专栏JAVA高级架构

Java设计模式-责任链模式

作者:Jet啟思 链接:https://juejin.im/post/5a126b146fb9a0450c490201 今天来说说程序员小猿和产品就关于需求发生...

36411
来自专栏Java成长之路

使用CompletableFuture构建异步应用(二)

本文主要介绍Java 8 中的异步处理的方式,主要是 CompletableFuture类的一些特性。 为了展示CompletableFuture的强大特性...

1314
来自专栏mySoul

设计模式 开闭原则

软件实体应该对扩展开放,对修改关闭,即实体应当通过扩展实现变化,而不是修改代码实现变化

1031

扫码关注云+社区