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

How to avoid floating point error calculate average value in postgres db and get it in java application?

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

创建 sql table 和值:

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

现在,如果您在 pgAdminIII 中处理以下 SELECT-statement 它 returns 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 的控制台 中此 printet 的结果 是:

own table - double-value: 4.999999999999999

own table - string-value: 4.9999999999999991

own table - bigdecimal-value: 4.9999999999999991

如您所见,我还尝试以 BigDecimal 和 String 的形式检索值 - 无效。 有人知道如何避免这个浮点错误吗?

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

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

浮点错误源自 PostgreSQL,因此提高 Java 端的精度没有用。尽管您可能会看到 SELECT 语句中出现“5”,但事实上您的 PostgreSQL 客户端并未显示结果的所有数字。

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

The type numeric can store numbers with a very large number of digits and perform calculations exactly. It is especially recommended for storing monetary amounts and other quantities where exactness is required. However, arithmetic on numeric values is very slow compared to the integer types, or to the floating-point types described in the next section.

下面的前 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)

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

您通常没有注意到的原因是 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");