| 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.
| 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 |