GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — Read INT128-backed NUMERIC/DECIMAL values without overflowing or rounding by fdcastel · Pull Request #1305 · FirebirdSQL/NETProvider

Conversation

@fdcastel

@fdcastel fdcastel commented Nov 7, 2026

Copy link
Copy Markdown
Member

Fixes #1301.

Summary

INT128-backed NUMERIC/DECIMAL values are no longer converted to System.Decimal while the row is decoded. They are converted exactly instead, so a row can always be read, the connection stays usable, and nothing is rounded silently.

Changes

  • TypeDecoder.DecodeInt128Numeric / BigDecimal.Create return a decimal when the value fits exactly, using the smallest scale. Otherwise they return an internal BigDecimal (an unscaled BigInteger plus a scale).
  • Both clients use it: the managed reader (GdsStatement.ReadRawValue[Async]) and the native client (DbField.SetValue). The native client now also decodes NUMERIC(38, 0)/DECIMAL(38, 0) as numeric, consistent with GetFieldType() and with the managed client.
  • For a value that does not fit decimal:
    • GetValue, GetDecimal and GetFieldValue<decimal> throw OverflowException, for that column only.
    • GetString returns the exact value, in the current culture like decimal.
    • GetDouble and GetFloat return the nearest value.
    • GetFieldValue<BigInteger> returns integral values exactly. This also works for values that fit decimal; fractional values throw OverflowException.
  • DbValue.GetDecodedValue[Async]: the native client copies decoded values into the row without the conversion that GetValue performs.

Behavior changes (breaking)

  1. Values with more than 28 fractional digits now throw instead of being rounded. For example, cast('1E-30' as numeric(38, 30)): master returned 0, now GetValue/GetDecimal throw OverflowException and GetString returns 0.000000000000000000000000000001.
  2. Native client, NUMERIC(38, 0)/DECIMAL(38, 0). Master's GetValue returned BigInteger, now it returns decimal (as GetFieldType() always reported, and as the managed client always did). For values beyond decimal, GetValue now throws OverflowException; GetFieldValue<BigInteger> still returns them exactly.
  3. Side effect: an integral decimal parameter value can now be passed to an INT128 parameter, because the same DbValue.GetInt128 serves both. It threw InvalidCastException before.

All other values decode exactly as before. I compared master's decoding with this one on 2,000,000 random values (1 to 38 digits, scale 0 to 38, seeded):

Outcome Count
both decimal, identical value and scale 1,136,464
both decimal, different 0
master threw OverflowException; now exact 479,201
master returned a silently rounded decimal; now exact (change 1) 384,335
master returned the exact value, but now BigDecimal 0

The same comparison measured decoding time (Stopwatch, boxed like the row decoder; one machine):

Value Master This PR
22-digit NUMERIC(38,6) 164 ns 22 ns
1234.5 66 ns 50 ns
123 as NUMERIC(38,0) 2.5 ns 5.0 ns

Tests

  • BigDecimalTests (NoServer):
    • exact/inexact boundaries: decimal.MaxValue ± 1, scale 28/29, trailing zeros,
    • formatting, including a non-invariant culture,
    • rounding and integral conversion.
  • FbLongNumericsSupportTests:
    • values that fit decimal, including NUMERIC(38, 30) value 1 and NUMERIC(38, 0),
    • values that do not fit: the row is read, other columns are readable, GetString is exact, GetDouble is nearest, GetValue/GetDecimal throw, and the connection is still usable afterwards,
    • GetFieldValue<BigInteger>.
  • Without the fix, 14 tests fail on the embedded client. With it, the embedded + NoServer suite passes: 648 passed, 0 failed.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@fdcastel
Read INT128-backed NUMERIC/DECIMAL values without overflowing or roun…
07d2eeb
…ding

NUMERIC/DECIMAL with precision 19-38 were converted to System.Decimal while
the row was decoded:

- Values beyond System.Decimal (e.g. 38 digits) threw OverflowException in
  the middle of decoding the row. On the managed (wire) client this left the
  rest of the packet unread and desynchronized the connection: every later
  command on it failed with "ArgumentOutOfRangeException: operation=...".
- Values with large scale but small magnitude overflowed too, e.g.
  1.000...0 as NUMERIC(38,30).
- Values with more than 28 fractional digits were rounded silently, e.g.
  1E-30 as NUMERIC(38,30) was read as 0.
- The native client returned BigInteger for NUMERIC(38,0)/DECIMAL(38,0)
  although GetFieldType() reports System.Decimal (managed returned decimal).

Now the value is converted exactly: values that fit System.Decimal are
returned exactly as before (smallest scale, e.g. 1234.5), on both clients.
Values that do not fit are kept exactly (internal BigDecimal), so the row
is always read and the connection stays usable; for those values:

- GetValue/GetDecimal/GetFieldValue<decimal> throw OverflowException
  (only when that column is accessed),
- GetString returns the exact value,
- GetDouble/GetFloat return the nearest value,
- GetFieldValue<BigInteger> returns integral values exactly (also for
  values that fit decimal; fractional values throw OverflowException).

As GetFieldValue<BigInteger> and INT128 parameters share DbValue.GetInt128,
an integral decimal parameter value can now be passed to an INT128 parameter
(it threw InvalidCastException before).
@fdcastel fdcastel mentioned this pull request Nov 7, 2026
Support .NET 11 Decimal128/Decimal64/Decimal32 for DECFLOAT #1308
Open
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

No reviews

Uh oh!

There was an error while loading. Please reload this page.

Assignees

No one assigned

Labels

None yet

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

NUMERIC(19..38) values beyond System.Decimal break the row (and the connection), small values are rounded silently

Uh oh!

There was an error while loading. Please reload this page.

1 participant

@fdcastel
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.