Skip to main content
Exact decimal values

Exact decimal arithmetic for Flutter financial and business data

Dart provides int, double, and BigInt, but no built-in fixed-scale decimal type. FdcDecimal fills that gap with exact base-10 values that remain decimal from user input to dataset storage, calculations, filtering, totals, and serialization.

COMMUNITYAvailable in the Community package

The workflow described on this page is available in the public flutter_data_components package.

THE MISSING NUMERIC DOMAIN

Financial values are decimal values, not approximate binary measurements.

Dart's fractional numeric type is double, a 64-bit IEEE 754 binary floating-point value. It is excellent for graphics, physics, animation, statistics, and other domains where approximation is expected. Ordinary decimal fractions such as 0.1, however, usually have no exact finite binary representation.

That distinction matters in invoices, tax calculations, prices, discounts, exchange rates, quantities, and percentages. A tiny representation difference can appear after repeated arithmetic, affect an equality check, produce an unexpected rounding boundary, or force application code to repair values before persistence and display.

final subtotal = 0.1 + 0.2;

print(subtotal); // 0.30000000000000004
print(subtotal == 0.3); // false
THE FDC DECIMAL MODEL

FdcDecimal stores the integer coefficient and decimal scale explicitly.

FdcDecimal represents a value as scaledValue × 10^-scale. The coefficient is a BigInt, so decimal digits are never converted to a binary fraction. For example, 123.45 is stored as the integer 12345 with scale 2.

Scale is part of the value, while dataset fields add schema-level precision and scale. This makes capacity, normalization, rounding, validation, and database mapping explicit instead of scattering them across formatters and callbacks.

final price = FdcDecimal.parse('19.95');
final taxRate = FdcDecimal.parse('0.0825');
final fixedScale = 100.decimalScale(2); // 100.00

const amountField = FdcDecimalField(
name: 'amount',
precision: 12,
scale: 2,
);
ARITHMETIC AND ROUNDING

Decimal rules stay visible in the code that performs the calculation.

Addition and subtraction align operand scales. Multiplication combines scales. Division preserves the larger operand scale and uses at least 12 fractional digits. Normalization and toStringAsFixed apply deterministic decimal rounding rather than relying on a formatted approximation of a binary value.

Equality is intentionally limited to other FdcDecimal values, preserving symmetric Dart equality and reliable Set and Map keys. Ordering operators can still accept finite numeric operands for convenient range checks.

final net = '125.50'.decimal;
final tax = '25.10'.decimal;

final gross = net + tax; // 150.60
final doubled = net * 2; // 251.00
final third = 1.decimal / 3; // 0.333333333333

final normalized = FdcDecimal.parse(
'1.999',
scale: 2,
); // 2.00
ONE DECIMAL PIPELINE

Exactness is preserved beyond the value object.

A decimal class solves only part of the problem if editors immediately convert it to double, filters compare formatted strings, or totals accumulate through floating-point variables. FDC carries FdcDecimal through the complete data-aware workflow.

Decimal fields normalize and validate precision. FdcDecimalEdit parses localized input without changing the numeric domain. FdcDecimalColumn keeps editing, filtering, sorting, and display connected to the same field. Dataset aggregates return exact decimal sums and averages, while adapters and serializers receive a stable decimal representation.

final orders = FdcDataSet(
fields: const [
FdcDecimalField(
name: 'amount',
precision: 12,
scale: 2,
),
],
);

final amount = orders.field('amount').asDecimal;
final total = orders.aggregates.sum('amount');
PRACTICAL BOUNDARIES

Use double where approximation belongs; convert only at explicit integration boundaries.

FdcDecimal is designed for values whose decimal digits carry business meaning. Use it for money, rates, quantities, percentages, measurements with mandated scale, and database decimal columns. Continue using double for rendering coordinates, animation progress, scientific computation, and APIs whose contract is explicitly floating point.

When an external API requires double, convert at that boundary and treat the conversion as potentially lossy. When source text or JSON decimal text is authoritative, parse that text directly so the original digits are never first approximated as floating point.

COMMUNITY PACKAGE

Start with one dataset and one record lifecycle.

Install Flutter Data Components from pub.dev, define the first typed dataset, and connect it to a grid or data-aware editor.