The problem seems to originate from the way PHP returns results. The values are not returned as the corresponding data type, but rather 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
.
Setting extra_float_digits
The manual 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.
Therefore, a simple solution to your problem is to increase extra_float_digits
before issuing your SELECT
-query:
pg_query($connection, "set extra_float_digits = 3");
Alternatively, you can also specify this change when connecting to your database by adding options
to your connection string as follows:
$connection = pg_connect("host=localhost port=5432 dbname=test user=php password=pass connect_timeout=5 options='-c extra_float_digits=3'");
Another option would be to set this flag in the postgresql.conf
configuration file of the PostgreSQL server if you have access to the server and want to change the option globally.
Casting the values
A different solution would be to have PostgreSQL return a different string to the PHP backend. This can be achieved by casting your columns to types with different default formatting which avoids cutting off some of the digits. In your case you could either cast to integer
or double precision
, i.e. instead of using
select cultivated_land from table
you could use
select cultivated_land::integer from table
or
select cultivated_land::double precision from table
Changing data types
Looking at the data you specified, I noticed that all numerical values except those columns specifying percentages contain integers, hence the usage of the integer
data type is more suitable in this case. It can fit all the integer values of this table (the maximum being 149,000,000, therefore bigint
is not required), requires the same storage size as real
(4 bytes) and implies the default formatting of integers that you are looking for.
Update: Background on PostgreSQL-PHP interface and floating point representation
As mentioned above the way the PostgreSQL-PHP interface works is that all values sent from PostgreSQL to PHP are formatted as a string in some type-dependent way. Neither any of the pg_fetch_*
functions nor pg_copy_to
will provide raw values and all of these functions convert the values to strings in the same manner. As far as I am aware the current PHP interface will not provide you with anything different from a string (which, in my opinion, is not the best interface design).
The reason 18.22
is returned as 18.2199993
can be found in how PostgreSQL converts float4
to strings. You can check the code of how PostgreSQL is internally using float4out
and find this relevant line that does the string-conversion:
snprintf(ascii, MAXFLOATWIDTH + 1, "%.*g", ndig, num);
num
is the float4
-number to be printed as a string. Note however that C will promote the float
-variable to a double
-variable 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 and will also find some details on floating point number representation on this site).
The takeaway message is that all your float4
values will be converted using this function and the only parameter you can influence is ndig
by varying extra_float_digits
, however no single value for this variable will suffice all your needs in representing the values as you want them. So as long as you keep using float4
as your data type and use the current PHP-interface to obtain the data you will run into these problems.
I therefore still recommend choosing different data types for your columns. If you think you have a requirement for decimal numbers you might want to investigate decimal
data types where you can specify precision and scale as required for your application. If you would like to stick with floating point numbers I suggest rounding the values in PHP before displaying them to the user.