PetToys.BigDecimal.Npgsql 1.0.1

Prefix Reserved
dotnet add package PetToys.BigDecimal.Npgsql --version 1.0.1
                    
NuGet\Install-Package PetToys.BigDecimal.Npgsql -Version 1.0.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="PetToys.BigDecimal.Npgsql" Version="1.0.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="PetToys.BigDecimal.Npgsql" Version="1.0.1" />
                    
Directory.Packages.props
<PackageReference Include="PetToys.BigDecimal.Npgsql" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add PetToys.BigDecimal.Npgsql --version 1.0.1
                    
#r "nuget: PetToys.BigDecimal.Npgsql, 1.0.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package PetToys.BigDecimal.Npgsql@1.0.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=PetToys.BigDecimal.Npgsql&version=1.0.1
                    
Install as a Cake Addin
#tool nuget:?package=PetToys.BigDecimal.Npgsql&version=1.0.1
                    
Install as a Cake Tool

PetToys.BigDecimal.Npgsql

NuGet Version NuGet Downloads Unit Test Target frameworks License

PostgreSQL helpers for PetToys.BigDecimal.Core: they map the arbitrary numeric values that PostgreSQL allows - and that decimal cannot hold - onto BigDecimal when reading and writing through Npgsql.

Bring your own configured NpgsqlConnection or data source; this package only deals with the value mapping.

Installation

dotnet add package PetToys.BigDecimal.Npgsql

The core type's own package, PetToys.BigDecimal.Core, comes along as a dependency.

Releases go to nuget.org, so the command above is all that is needed. Prereleases are published to GitHub Packages instead: that feed has to be added to your nuget.config, and it requires a personal access token with read:packages even for a public package.

Usage

Register the mapping once, on the data source:

using Npgsql;
using PetToys.BigDecimal.Numerics;

await using var source = new NpgsqlDataSourceBuilder(connectionString)
    .UseBigDecimal()
    .Build();

Then ask for the type where you want it:

await using var command = source.CreateCommand(
    "SELECT total FROM invoices WHERE id = $1");
command.Parameters.Add(new NpgsqlParameter { Value = invoiceId });

await using var reader = await command.ExecuteReaderAsync();
await reader.ReadAsync();

BigDecimal total = reader.GetBigDecimal("total");

Writing needs nothing but the value:

command.Parameters.Add(new NpgsqlParameter { Value = total });

numeric[] maps as well, to BigDecimal[], List<BigDecimal> and their nullable forms; and a binary COPY import goes through the same mapping, which is the path to use for more than a handful of rows.

UseBigDecimal is also an extension on INpgsqlTypeMapper, so it works from a configuration callback and on NpgsqlSlimDataSourceBuilder.

Registering it does not change anything else

BigDecimal is for the columns that need it, not a replacement for System.Decimal across an application, and the registration is built that way. A numeric column read with GetValue is still a decimal, GetFieldType still reports decimal, and a DataTable still fills with decimal. Code written before this package was referenced reads exactly what it read before. BigDecimal is reached by asking for it, per read.

What round-trips, and what does not

Column Coverage
numeric(p, s), p up to 77 Lossless. This covers numeric(38, 18), the common money and blockchain precision, with room to spare.
numeric unconstrained, integer part within the magnitude Accepted; fractional digits beyond what the magnitude leaves are rounded half to even. PostgreSQL allows 16383 of them, so a value read from such a column can lose digits silently.
numeric unconstrained, integer part beyond the magnitude OverflowException. PostgreSQL allows 131072 integer digits.
NaN, Infinity, -Infinity Lossless, as BigDecimal.NaN, BigDecimal.PositiveInfinity and BigDecimal.NegativeInfinity. PostgreSQL sorts NaN above every other numeric value where this type sorts it below every other value; both make NaN equal to itself.

The type's own bounds are 77 significant digits, a largest magnitude of 2^256-1, and a range of 1e-255 to approximately 1.157e77.

Only one direction can fail. Every value of the type fits a numeric, so writing never overflows and never rounds. Reading is the direction with a boundary, and GetBigDecimal names the column when a value crosses it:

Column 'total' at ordinal 0 holds a numeric whose integer part is larger than
BigDecimal can represent: ...

That naming is what the accessors are for. Reading the same column through GetFieldValue<BigDecimal> or through Dapper gets the same mapping and the same OverflowException, without the column name.

Through Entity Framework Core, add PetToys.BigDecimal.Npgsql.EntityFrameworkCore: it maps a numeric column to a BigDecimal property over this package's registration, and the value the caller can name there is the property, read off the DbUpdateException Entity Framework raises.

Requirements

  • Npgsql 10.0.3, up to but not including 11. The mapping registers through Npgsql.Internal, which is the driver's only extension point for a new type and is published as experimental, so the floor is the version this package was built against and the range is closed at the major. A driver major that reshapes the extension point therefore fails at restore rather than at the first read; the ceiling moves once the new major has been tested against.
  • Trimmed and Native AOT publishing both work. This assembly is marked IsAotCompatible, and Npgsql 10.0.3 is itself marked trimmable, so the closure a publish trims carries no unmarked assembly. Both forms are verified by publishing a probe over this package and running it against a real server, not by a clean analyzer pass. Under Native AOT, build the data source with NpgsqlSlimDataSourceBuilder, which is Npgsql's own route for that case and which UseBigDecimal overloads; the slim builder starts with nothing enabled, so a numeric[] needs EnableArrays() on it. A trimmed build can use either builder. Serializing a BigDecimal to JSON in such an application needs a source-generated context, which is a System.Text.Json rule rather than one of this package's; the core package's README carries the detail.
  • PostgreSQL 14 or later for the infinities. That is where numeric gained the sign codes that carry them. Everything else works on any supported server; against an older one, writing an infinity is refused by the server itself and this package does not emulate or substitute anything.

License

Provided under the Apache License, Version 2.0.

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (2)

Showing the top 2 NuGet packages that depend on PetToys.BigDecimal.Npgsql:

Package Downloads
PetToys.BigDecimal.Npgsql.EntityFrameworkCore

Entity Framework Core mapping that exposes PostgreSQL numeric columns as PetToys.BigDecimal.Core properties, over the handler PetToys.BigDecimal.Npgsql installs on the data source.

PetToys.BigDecimal.Npgsql.Dapper

Dapper mapping that reads and writes PostgreSQL numeric columns as PetToys.BigDecimal.Core values, over the handler PetToys.BigDecimal.Npgsql installs on the data source.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.1 109 9/13/2026
1.0.0 109 9/12/2026

A patch release over 1.0.0. One tag versions every package in the repository, so
all six are published together; what each of them carries is below.

PetToys.BigDecimal.Core

Fixed:
- Parsing a literal whose exponent is far below the scale floor no longer costs
 one division per digit it drops. A scale above 255 is reduced to it, so
 "1e-99999" asked the pack to drop 99744 decimal positions and it divided for
 every one of them, thousands of times over after the magnitude had already
 become zero. The parser caps an exponent at 100000, which is what bounds the
 worst case rather than what creates it. That input took 48 microseconds on
 .NET 10 before the change; after it, the benchmark reads the same literal at
 1.2x a parse that drops nothing, such as "1e-200". Every parsed value, scale
 and sign is unchanged - those literals are zero at scale 255 before the
 change and after it - and rounding, rescaling and the database wire paths
 reach the same helper and stop the same way.

Notes:
- What GetHashCode costs is documented now, in the README and in the method's
 own remarks. A hash has to agree with numeric equality, so it goes through
 the value's shortest form: one carrying no trailing zeros costs 12.7x to 16.1x
 decimal's hash, which strips them for the same reason, and one widened to a
 database column's scale about 2x as much, because that one pays a division
 pass over the magnitude. Nothing in the algorithm changed - the table of costs
 covered arithmetic, parsing and formatting and simply did not reach hashing.
- What Pow guarantees for a power it cannot represent exactly is stated now, in
 the method's own remarks and in the README, and the suite carries the deep
 chains that back it. The power is raised in a working width of 154 digits
 against the 77 a result keeps, so the rounding reads the digit the exact power
 reads unless the exact power sits nearer the midpoint than 1e-66 of a unit in
 the last place, which takes a 5 and then sixty-five zeros past the 77th
 digit, or a 4 and then sixty-five nines. Nothing in the algorithm changed and
 no value moved; what changed is that the documents no longer read as though
 the exact power were carried.