如何避免浮点错误计算postgres db中的平均值并在java应用程序中获取它?

时间:2017-03-01 08:27:06

标签: java postgresql floating-point precision

我遇到的问题是我想在postgres 9.6数据库上建立一个平均超过6个值,该数据库应该导致结果5.0但是将在我的java应用程序4.99999999中。

创建sql表和值:

CREATE TABLE mytesttable(
    value double precision
);
INSERT INTO mytesttable (value)
    VALUES (5),
    (5.1),
    (5.3),
    (5),
    (5.4),
    (4.2)
;

现在,如果您在pgAdminIII中处理以下 SELECT语句,它将在gui中返回正确的5:

SELECT AVG(value) AS value_avg FROM mytesttable;

但在Java中它将是4.9999 ....我使用以下 postgres jdbc驱动程序

<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
    <version>9.4.1212</version>
</dependency>

要从数据库中获得平均值,我创建一个会话并执行该语句,如您在我的java代码中所见:

Class.forName(driver);
Connection connection = DriverManager.getConnection(host, user, password);
String sql = "SELECT AVG(value) AS value_avg FROM mytesttable";

Statement statement = connection.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE);
ResultSet rs = statement.executeQuery(sql);
while (rs.next()) {
    Double doubleValue = rs.getDouble("value_avg");
    System.out.println("own table - double-value: "+doubleValue);

    String doubleString = rs.getString("value_avg");
    System.out.println("own table - string-value: "+doubleString);

    BigDecimal bigDecimal = rs.getBigDecimal("value_avg");
    System.out.println("own table - bigdecimal-value: "+bigDecimal);
}

java的控制台中的 结果是:

  

自己的表 - 双值:4.999999999999999

     

自己的表 - 字符串值:4.9999999999999991

     

自己的表 - bigdecimal-value:4.9999999999999991

正如您所看到的,我还尝试将值检索为BigDecimal以及String - 不起作用。 有人知道如何避免这种浮点错误吗?

2 个答案:

答案 0 :(得分:2)

如果该值仅用于表示,您可以考虑在Java端显示较少的数字,而不是更改SQL语句。

double doubleValue = rs.getDouble("value_avg");
System.out.format("own table - double-value: %.4f", doubleValue);
// should print `5.0000`.

浮点错误源自PostgreSQL,因此没有必要提高Java端的精度。虽然你可能会看到&#34; 5&#34;从SELECT语句出来,事实是你的PostgreSQL客户端没有显示结果的所有数字。

您可以将数字转换为numeric type以获得定点运算。

  

类型numeric可以存储具有大量数字的数字并完全执行计算。特别推荐用于存储需要准确性的货币金额和其他数量。但是,与整数类型或下一节中描述的浮点类型相比,numeric值的算术运算速度非常慢。

下面的前3列表明PostgreSQL方面的平均值确实不精确。最后3列显示使用定点计算而不是浮点计算(您可能希望更改numeric(4, 2)以提高精度)。

SELECT
    avg(n),  -- 5
    avg(n) = 5,  -- false
    avg(n) - floor(avg(n)),  -- 0.999999999999999
    avg(n :: numeric(4, 2)), -- 5
    avg(n :: numeric(4, 2)) = 5,   -- true
    avg(n :: numeric(4, 2)) - floor(avg(n :: numeric(4, 2)))  -- 0
FROM (VALUES 
    (5 :: double precision),
    (5.1 :: double precision),
    (5.3 :: double precision),
    (5 :: double precision),
    (5.4 :: double precision),
    (4.2 :: double precision)
) t(n)

答案 1 :(得分:2)

浮点值的所有数值运算都是不精确的。

你没有注意到,通常是PostgreSQL在将realdouble precision值转换为文本时,会在一定数量的数字之后进行舍入,这样才能得到结果在所有平台上都是一样的。

这由参数extra_float_digits控制。如果将该参数从其默认值0(最大值为3)增加,您将获得更多数字,这将使文本表示更准确,但会显示舍入错误:

SET extra_float_digits=3;

SELECT AVG(value) AS value_avg FROM mytesttable;
      value_avg
---------------------
 4.99999999999999911
(1 row)

或者更令人惊讶的是:

SELECT 0.3::double precision;
        float8
----------------------
 0.299999999999999989
(1 row)

现在,PostgreSQL JDBC驱动程序将extra_float_digits设置为2或3,以避免丢失任何精度,这可能会导致您观察到的效果。

如果您不在乎这些额外的数字并且宁愿有一个很好的圆值,请将extra_float_digits更改回0:

conn.createStatement().execute("SET extra_float_digits=0");