背景:

ES版本:6.3.2

对一个double类型字段进行sum求和时,出现丢失精度问题.

查出44条数据,求和结果为1004.9, 但是ES返回结果为1004.9000000000001

由于公司内部组件进行了封装,简单索引查询无需解析结果可直接供前端使用.所以不希望内存中重新解析,逐条处理数字,而是通过ES的功能直接实现保留2位小数.

于是开始全网搜索.

截止2021年11月29日21:19:42 的方案:

索引中的数字插入时*100, 取出时 / 100

每个条明细 / 100

GET /_search
{
    "query" : {
        "match_all": {}
    },
    "script_fields" : {
        "test1" : {
            "script" : {
                "lang": "painless",
                "inline": "doc['qty'].value / 100"
            }
        },
        "test2" : {
            "script" : {
                "lang": "painless",
                "inline": "doc['qty'].value / factor",
                "params" : {
                    "factor"  : 100
                }
            }
        }
    }
}

sum结果 / 100

GET /_search
{
  "query" : {
        "match_all": {}
  },
  "aggs": {
    "qty_name": {
      "sum": {
        "field": "qty",
        "script":"(doc['qty'].value) / 100"
      }
    }
  }
}

寻找过程

ES不支持直接对某一列字段加减乘除,需要借助painless 脚本语言中的 Script Fields 进行计算.

Script Fields 语法

在stackoverflow找到了相同的问题,但效果都不好.​​​​​​   stackoverflow同类问题https://stackoverflow.com/questions/36041812/how-to-round-up-double-to-2-decimal-point-elasticsearch#https://stackoverflow.com/questions/36041812/how-to-round-up-double-to-2-decimal-point-elasticsearch#

stackoverflow中文翻译后的内容

 stackoverflow例子实测

ES索引如下:

{
    "state": "open",
    "settings": {
        "index": {
            "creation_date": "1637576630382",
            "number_of_shards": "10",
            "number_of_replicas": "1",
            "uuid": "-----",
            "version": {
                "created": "6030299"
            },
            "provided_name": "test_double"
        }
    },
    "mappings": {
        "_doc": {
            "dynamic": "false",
            "properties": {
                "qty": {
                    "type": "double"
                }
            }
        }
    }
}

插入44条数据(从正式环境copy来的所以44条)

4.0 ,1.0 ,1.7 ,1.0 ,12.0 ,1.0 ,26.0 ,7.0 ,2.0 ,1.6 ,1.0 ,44.0 ,5.0 ,3.4 ,3.0 ,1.0 ,5.0 ,2.7 ,6.4 ,1.7 ,1.0 ,0.8 ,10.0 ,1.6 ,1.0 ,3.0 ,5.0 ,4.0 ,12.0 ,39.0 ,0.8 ,2.0 ,12.0 ,6.0 ,8.0 ,2.0 ,547.0 ,137.0 ,76.0 ,0.8 ,1.1 ,0.7 ,2.0 ,1.6 

普通求和:

math_round 保留小数:

无效

round(2)保留两位小数:. 查询报错. 后查明没有这个方法Lucene表达式语言-数字类型字段API-没有round()方法

BigDecimal保留两位小数:

 BigDecimal是将44条的值都做四舍五入后进行加和, 结果与真实sum值差异较大,不能使用.

综上所有测试得出, 暂时没有办法直接将结果保留两位小数,所以只能考虑换个方式,索引中的数字*100 进行sum,取出来时再除以100。

如果大家有更好的方法,同求分享。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐