BUG #19744: contrib/seg output truncates to 6 significant digits, breaking dump/restore

From: PG Bug reporting form <noreply(at)postgresql(dot)org>
To: pgsql-bugs(at)lists(dot)postgresql(dot)org
Cc: kehan5800(at)gmail(dot)com
Subject: BUG #19744: contrib/seg output truncates to 6 significant digits, breaking dump/restore
Date: 2026-10-04 05:16:30
Message-ID: 19744-8c700582bbb1d075@postgresql.org
Views: Whole Thread | Raw Message | Download mbox | Resend email
Thread:
Lists: pgsql-bugs

The following bug has been logged on the website:

Bug reference: 19744
Logged by: Ke
Email address: kehan5800(at)gmail(dot)com
PostgreSQL version: 18.6
Operating system: Ubuntu 22.04.2 x86_64
Description:

The seg documentation (doc/src/sgml/seg.sgml, "Precision") says:

seg values are stored internally as pairs of 32-bit floating point
numbers. This means that numbers with more than 7 significant digits
will be truncated.

Numbers with 7 or fewer significant digits retain their original
precision. That is, if your query returns 0.00, you will be sure that
the trailing zeroes are not the artifacts of formatting: they reflect
the precision of the original data.

In practice a 7-significant-digit value is stored exactly but printed with
6 digits:

CREATE EXTENSION seg;

SELECT '1234567'::seg AS printed,
seg_lower('1234567'::seg)::float8 AS stored,
'1234567'::seg::text::seg = '1234567'::seg AS round_trips;
printed | stored | round_trips
-------------+---------+-------------
1.23457e+06 | 1234567 | f

SELECT '1.234567'::seg, '0.0001234567'::seg,
'1234567 .. 7654321'::seg, '1.000000'::seg;
1.23457 | 0.000123457 | 1.23457e+06 .. 7.65432e+06 | 1.00000

The last one is the "trailing zeroes" case the documentation uses as its
example: seven significant digits in, six out.

1234567 is well inside float4's exact-integer range, so the storage side of
the promise holds; it is the digit count that is cut. Walking the boundary:
1 to 6 significant digits print in full and round-trip; 7, 8 and 9 print as
6 and do not.

Because pg_dump writes seg_out()'s text, a dump/restore changes the data:

CREATE TABLE segt (s seg);
INSERT INTO segt VALUES ('1234567'), ('1.234567');

$ pg_dump -t segt --data-only
COPY public.segt (s) FROM stdin;
1.23457e+06
1.23457
\.

and both restored values compare unequal to the originals.

Cause: the per-boundary digit count (sigd) is clamped to FLT_DIG, which is
6, in two places:

contrib/seg/segparse.y:177, sig_digits():
/* Clamp, to ensure value will fit in sigd fields */
return Min(n, FLT_DIG);

contrib/seg/seg.c:943-946, restore() (called by seg_out()):
if (n <= 0)
n = FLT_DIG;
else
n = Min(n, FLT_DIG);

The parse-side clamp already discards the 7th digit's worth of l_sigd/u_sigd
before anything is stored, so changing restore() alone would not help.
FLT_DIG is the decimal->float->decimal guarantee; the number of digits
needed to reproduce a float4 from its text is FLT_DECIMAL_DIG (9).

Expected: either values with up to 7 significant digits round-trip as
documented, or the documentation states the actual limit (6).

Actual: values with 7 significant digits are printed with 6 and do not
survive their own text form or pg_dump.

Possible fix: use 7 (to match the documentation) or FLT_DECIMAL_DIG (to make
output exact for every stored value) in both places. Both fit the "char"
sigd fields that the parse-side comment is protecting, and restore()'s
25-byte buffer still has room. The existing regression test "Digits
truncated" (12.34567890123456 -> 12.3457) would change accordingly. If the
6-digit behaviour is intended, the "7 or fewer" sentence in seg.sgml should
be corrected instead.

Responses

Browse pgsql-bugs by date

  From Date Subject
Next Message PG Bug reporting form 2026-10-04 05:17:04 BUG #19745: tsquery input: 33 nested "!" raise XX000 via elog(), escaping pg_input_is_valid()
Previous Message PG Bug reporting form 2026-10-04 05:10:34 BUG #19743: pgcrypto crypt(): sha-crypt rounds= silently wraps modulo 2^32