使用 PHP sprintf 打印时,PostgreSQL 实数类型丢失整数精度

PostgreSQL real type losing integer precision when printed with PHP sprintf

我已将 this table from Wikipedia 复制到 PostgreSQL 数据库中。列 Cultivated land (km2) 成为类型 real 的列。然后我使用 PHP 命令

echo rtrim(rtrim(sprintf('%.10F',$v),'0'),'.');

在 table(整数和浮点数)中显示数字 ($v),但有些值会失去精度。比如美国的值,1669302,变成1669300,奇怪,因为I expected 10 decimal digits of precision。我以为我在保存到 real 列时失去了精度,但是将列转换为 double precision 会使差异 (02) 再次出现,所以它就在那里。

我认为我不需要双精度,那么如何才能正确显示真实值呢?请记住,有些列有小数位,而有些列是 bigint,它们也应该正确显示。

问题似乎源于 PHP return 的结果。这些值未 return 编辑为相应的数据类型,而是 formatted as a string using PostgreSQL default formatting. This formatting, is different for real and double precision types hence you are seeing different results when you convert the column types of your table. The reason you are seeing this specific result is that PostgreSQL guarantees 6 decimal places for real types and 15 decimal places for double precision.

设置extra_float_digits

手册states

Note: The extra_float_digits setting controls the number of extra significant digits included when a floating point value is converted to text for output. With the default value of 0, the output is the same on every platform supported by PostgreSQL. Increasing it will produce output that more accurately represents the stored value, but may be unportable.

因此,解决您问题的一个简单方法是在发出 SELECT-查询之前增加 extra_float_digits

pg_query($connection, "set extra_float_digits = 3");

或者,您也可以在连接到数据库时通过将 options 添加到 connection string 来指定此更改,如下所示:

$connection = pg_connect("host=localhost port=5432 dbname=test user=php password=pass connect_timeout=5 options='-c extra_float_digits=3'");

另一个选项是 set this flag 在 PostgreSQL 服务器的 postgresql.conf 配置文件中,如果您可以访问服务器并想全局更改选项。

转换值

一个不同的解决方案是让 PostgreSQL return 一个与 PHP 后端不同的字符串。这可以通过将您的列转换为具有不同默认格式的类型来实现,从而避免切断某些数字。在您的情况下,您可以转换为 integerdouble precision,即不使用

select cultivated_land from table

你可以使用

select cultivated_land::integer from table

select cultivated_land::double precision from table

更改数据类型

查看您指定的数据,我注意到除了那些指定百分比的列之外的所有数值都包含整数,因此在这种情况下使用 integer 数据类型更合适 table。它可以容纳此 table 的所有整数值(最大值为 149,000,000,因此不需要 bigint),需要与 real 相同的存储大小(4 字节)并暗示默认值您要查找的整数格式。

更新:PostgreSQL 的背景-PHP 接口和浮点表示

如上所述,PostgreSQL-PHP 接口的工作方式是从 PostgreSQL 发送到 PHP 的所有值都以某种类型相关的方式格式化为字符串。 pg_fetch_* 函数和 pg_copy_to 都不会提供原始值,所有这些函数都以相同的方式将值转换为字符串。据我所知,当前的 PHP 界面不会为您提供任何与字符串不同的东西(在我看来,这不是最好的界面设计)。

18.22 被 return 编辑为 18.2199993 的原因可以在 PostgreSQL 如何将 float4 转换为字符串中找到。您可以 check the code 了解 PostgreSQL 如何在内部使用 float4out 并找到执行字符串转换的相关行:

snprintf(ascii, MAXFLOATWIDTH + 1, "%.*g", ndig, num);

num 是要作为字符串打印的 float4-数字。但是请注意,C 将 when calling snprintf. This conversion to double precision results in the value 18.219999313354492 which is why you end up seeing 18.2199993 (you can check this here 并且还将在此站点上找到有关浮点数表示的一些详细信息)。

要点是,您的所有 float4 值都将使用此函数进行转换,并且您可以通过改变 extra_float_digits 来影响的唯一参数是 ndig,但是没有单个值这个变量将满足您在表示所需值时的所有需求。因此,只要您继续使用 float4 作为您的数据类型并使用当前的 PHP 接口获取数据,您就会 运行 陷入这些问题。

因此,我仍然建议为您的列选择不同的数据类型。如果您认为您需要十进制数,您可能需要调查 decimal data types,您可以在其中根据应用程序的需要指定精度和小数位数。如果您想坚持使用浮点数,我建议在将 PHP 中的值显示给用户之前对其进行四舍五入。