Summary
A cast to unconstrained NUMERIC (no precision/scale typmod) rounds the value to an integer (scale 0) instead of preserving the fractional digits. Specifying a typmod works correctly.
Discovered alongside #84 (the negative-sub-unit Display sign bug) during an analysis session; filing separately so it can be tracked after #84 closes.
Reproduction
SELECT
0.859::numeric AS bare_cast, -- returns 1.0 (expected 0.859)
CAST(0.859 AS numeric) AS bare_cast2, -- returns 1.0 (expected 0.859)
123.456::numeric AS bare_big, -- returns 123.0 (expected 123.456)
0.859::numeric(10,4) AS with_typmod; -- returns 0.859 (correct)
For comparison, PostgreSQL treats unconstrained numeric as "any precision/scale" and preserves 0.859.
Why this matters
It is an easy, silent footgun in analytical SQL: AVG(...)::numeric, (a/b)::numeric, correlation/ratio expressions, etc. all quietly round to integers unless the author remembers to add a typmod. Combined with #84 it produced sign- and magnitude-wrong numbers in an LLM-driven analysis.
Open question / where to look
Need to determine the layer:
- Is
hyperd itself returning the cast result typed as NUMERIC(_, 0) for an unconstrained cast, or returning a value already rounded to scale 0?
- Or is the Rust API defaulting
scale = 0 when the RowDescription typmod is -1 (unconstrained) during SqlType::Numeric { scale, .. } parsing? See the numeric scale extraction in the protocol/types layer and how row.get::<Numeric>() obtains scale.
If (2), the fix is to carry through the actual decimal scale (or treat unconstrained numeric as a high default scale) rather than defaulting to 0. If (1), it is an upstream hyperd semantics question.
Notes
Summary
A cast to unconstrained
NUMERIC(no precision/scale typmod) rounds the value to an integer (scale 0) instead of preserving the fractional digits. Specifying a typmod works correctly.Discovered alongside #84 (the negative-sub-unit
Displaysign bug) during an analysis session; filing separately so it can be tracked after #84 closes.Reproduction
For comparison, PostgreSQL treats unconstrained
numericas "any precision/scale" and preserves0.859.Why this matters
It is an easy, silent footgun in analytical SQL:
AVG(...)::numeric,(a/b)::numeric, correlation/ratio expressions, etc. all quietly round to integers unless the author remembers to add a typmod. Combined with #84 it produced sign- and magnitude-wrong numbers in an LLM-driven analysis.Open question / where to look
Need to determine the layer:
hyperditself returning the cast result typed asNUMERIC(_, 0)for an unconstrained cast, or returning a value already rounded to scale 0?scale = 0when theRowDescriptiontypmod is-1(unconstrained) duringSqlType::Numeric { scale, .. }parsing? See the numeric scale extraction in theprotocol/typeslayer and howrow.get::<Numeric>()obtains scale.If (2), the fix is to carry through the actual decimal scale (or treat unconstrained numeric as a high default scale) rather than defaulting to 0. If (1), it is an upstream
hyperdsemantics question.Notes
::numeric(38, 10).Display).