Skip to content

Do not silently corrupt DECFLOAT values that do not fit the target format - #1304

Open
fdcastel wants to merge 1 commit into
FirebirdSQL:masterfrom
fdcastel:fix-decfloat-encode-range
Open

fdcastel wants to merge 1 commit into
FirebirdSQL:masterfrom
fdcastel:fix-decfloat-encode-range

Conversation

@fdcastel

@fdcastel fdcastel commented Nov 7, 2026

Copy link
Copy Markdown
Member

Fixes #1298.

Summary

DecimalCodec.EncodeDecimal silently corrupted DECFLOAT values whose coefficient or exponent does not fit the target format; only a Debug.Assert checked it. The encoder now brings the value into the format's ranges without changing its numeric value, or throws OverflowException.

Changes

DecimalCodec.FitToFormat (used by EncodeFinite):

  • drops trailing zeros of a coefficient that has too many digits (12345678901234567890123456789012340 fits DECFLOAT(34)),
  • moves an exponent that is too large into the coefficient (1E384 becomes 1000000000000000E369 in DECFLOAT(16)),
  • drops trailing zeros when the exponent is too small (100E-400 becomes 1E-398),
  • clamps the exponent of zero,
  • throws OverflowException ("cannot be represented exactly as DECFLOAT(n)") when none of that is possible, i.e. when the value would have to be rounded, or overflows or underflows the format.

DecimalCodec is used by both the managed and the native client, so both are fixed.

Tests

  • DecimalCodecTests (NoServer) covers, for both formats:
    • representable values, including the format limits,
    • values that are representable only after normalization,
    • values that must throw.
  • FbDecFloat16SupportTests and FbDecFloat34SupportTests:
    • server round trip of normalizable values, which were corrupted before,
    • PassesUnrepresentableValueThrows, which also checks that the connection stays usable afterwards.
  • Without the fix, 35 of the new tests fail. With it, the embedded + NoServer suite passes: 648 passed, 0 failed.

…rmat

DecimalCodec.EncodeDecimal wrote coefficients with more digits than the
format allows, and exponents outside its range, without any check (only a
Debug.Assert). In Release builds the excess bits overflowed into the
combination/exponent fields and the server stored a different value, e.g.:

  12345678901234567890123456789012345 -> DECFLOAT(34) stored 8345678901234567890123456789012345
  30000000000000004E-17               -> DECFLOAT(16) stored 0.08000000000000004
  1E400                               -> DECFLOAT(16) stored 8.000000000000001E-353

The encoder now brings the value into the format's ranges without changing
its numeric value (dropping trailing zeros of the coefficient, folding the
exponent into the coefficient, clamping the exponent of zero), and throws
OverflowException when the value cannot be represented exactly (too many
significant digits, exponent overflow or underflow). Values that previously
got corrupted although representable (e.g. 1E384 in DECFLOAT(16)) now work.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

DECFLOAT values that do not fit DECFLOAT(16)/DECFLOAT(34) are silently corrupted

1 participant