| From: | PG Bug reporting form <noreply(at)postgresql(dot)org> |
|---|---|
| To: | pgsql-bugs(at)lists(dot)postgresql(dot)org |
| Cc: | 303677365(at)qq(dot)com |
| Subject: | BUG #19810: to_char(): float V-format overflow yields 'Infinity' or NaN; format after decimal point dropped |
| Date: | 2026-10-29 06:32:46 |
| Message-ID: | 19810-e14756b84da7afc8@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: 19810
Logged by: chunling qin
Email address: 303677365(at)qq(dot)com
PostgreSQL version: 18.6
Operating system: Linux x86_64
Description:
Two independent issues in to_char() formatting.
Issue 1: float4/float8 V (decimal shift) format has no overflow check.
The V template multiplies the value by 10^n, where n is the number of digits
after V. For the float types the scale factor is computed as a double; when
10^n itself overflows, the multiplication silently produces Infinity or NaN,
and to_char() returns that as literal text.
- A perfectly representable result is destroyed:
to_char(1e-300::float8, 'FM9V' || repeat('9', 309)) -> "Infinity"
The true result is 1e-300 * 10^309 = 1e9; the same format with numeric
returns 1000000000.
- Zero becomes NaN:
to_char(0::float8, 'FM9V' || repeat('9', 309)) -> "NaN" (0 * inf)
float4 already does this at n = 39.
- Ordinary overflow returns literal text instead of "#" or an error:
to_char(1e308::float8, 'FM9V9999999') -> "Infinity"
while the equivalent SQL arithmetic 1e308::float8 * 1e7 raises
ERROR: value out of range: overflow
and the sibling int8 path raises "bigint out of range".
One fewer 9, to_char(1e308::float8, 'FM9V999999'), returns "#######" -
the documented overflow marker - so two adjacent format widths disagree on
how overflow is represented.
- An already-infinite input is also inconsistent within the same function:
to_char('Infinity'::float8, 'FM9999') -> "####"
to_char('Infinity'::float8, 'FM9999999999') -> "Infinity"
The sibling branches are guarded (int4/int8 raise errors, numeric is exact);
only the float4/float8 paths lack the check.
Issue 2: template patterns after the decimal point are silently dropped,
losing the sign.
SG and PL are documented template patterns and work before or after the
digit positions. Placed after the decimal point they are silently dropped -
together with any literal text that follows - and the sign is lost:
to_char('-100'::numeric, 'FM999') -> '-100' (default
minus, correct)
to_char('-100'::numeric, 'FM999.SG') -> '100' (sign lost)
to_char('-100'::numeric, 'FM999.SG "items"') -> '100' (sign and
literal text lost)
to_char('123.4'::numeric, 'FM999.PL') -> '123' ('+' and
even the fraction dropped)
to_char('-100'::numeric, '999.SG') -> '100'
Controls - the same patterns work correctly in adjacent positions:
to_char('-100'::numeric, 'FMSG999') -> '-100'
to_char('123.4'::numeric, 'FM999PL') -> '123.4+'
to_char('-100'::numeric, 'FM999MI') -> '100-'
A negative number formatted with a format string that contains a sign
pattern (SG) must not render as an unsigned positive number.
| From | Date | Subject | |
|---|---|---|---|
| Next Message | PG Bug reporting form | 2026-10-29 06:37:56 | BUG #19811: numeric log(base, x): wrong sign of Infinity result when base < 1; base 1 accepted with x = Infinity |
| Previous Message | PG Bug reporting form | 2026-10-29 06:26:24 | BUG #19809: oidvector index dedup still returns wrong results on 18.6; CTAS persists the corrupted rows |