Composable number formatting and parsing for Dart.
Numeral turns application numbers into display strings and back again with reusable codecs for decimals, compact values, percentages, byte sizes, currency text, and locale-aware numerals. Create a codec once, keep it in your app utilities, and use the same object for both formatting and parsing.
Add Numeral to your pubspec.yaml:
dependencies:
numeral: ^4.1.0Import the codec entrypoint for built-in number display codecs:
import 'package:numeral/codec.dart';
import 'package:numeral/en.dart' as en;package:numeral/numeral.dart contains the core protocols and shared models:
NumeralCodec, NumeralLanguage, NumeralUnitSet, NumeralUnit, and
Rounding.
Create reusable codec instances:
final fileSize = BytesCodec.binary(maxFractionDigits: 1);
final compact = en.compact(maxFractionDigits: 1);
final ratio = PercentCodec(maxFractionDigits: 2);
fileSize.format(1536); // 1.5 KiB
fileSize.parse('1.5 KiB'); // 1536
fileSize.encode(1536); // 1.5 KiB
fileSize.decode('1.5 KiB'); // 1536
compact.format(12345); // 12.3K
compact.parse('12.3K'); // 12300
ratio.format(0.1234); // 12.34%
ratio.parse('12.34%'); // 0.1234For one-off formatting, import the extension entry:
import 'package:numeral/extension.dart';
import 'package:numeral/zh.dart' as zh;
12345.compact(maxFractionDigits: 1); // 12.3K
1536.bytes(binary: true, maxFractionDigits: 1); // 1.5 KiB
1000000.currency('¥', style: zh.compact(maxFractionDigits: 0)); // ¥100万
1000000.formatWith(zh.cardinal()); // 一百万Each codec extends Codec<T, String> from dart:convert.
format and parse are readable aliases for encode and decode, so both
styles work:
final bytes = BytesCodec.binary();
bytes.format(1024); // 1 KiB
bytes.encode(1024); // 1 KiB
bytes.parse('1 KiB'); // 1024
bytes.decode('1 KiB'); // 1024Unit-style codecs such as compact numbers, percentages, byte sizes, and
currency can replace their internal number style with another codec. By
default, they use a DecimalCodec built from their decimal options.
final amount = DecimalCodec(
minFractionDigits: 2,
maxFractionDigits: 2,
);
amount.format(1234567.8); // 1,234,567.80
amount.parse('1,234,567.80'); // 1234567.8import 'package:numeral/en.dart' as en;
final compact = en.compact(maxFractionDigits: 1);
compact.format(1234); // 1.2K
compact.format(999999); // 1M
compact.parse('3 million'); // 3000000Custom unit sets use the same NumeralUnit model as byte codecs:
import 'package:numeral/codec.dart';
import 'package:numeral/numeral.dart';
final custom = CompactCodec(
unitSet: NumeralUnitSet([
NumeralUnit(1, ''),
NumeralUnit(1000, 'K', aliases: ['k']),
NumeralUnit(1000000, 'M', aliases: ['m']),
]),
);
custom.format(1234567); // 1.23M
custom.parse('3.5M'); // 3500000Custom unit scales must be finite, positive, and strictly ascending. Parser tokens are matched case-insensitively, so symbols and aliases should be unique after lowercasing.
final percent = PercentCodec(maxFractionDigits: 1);
percent.format(0.1234); // 12.3%
percent.parse('12.3%'); // 0.123final decimalBytes = BytesCodec();
final binaryBytes = BytesCodec.binary(maxFractionDigits: 1);
decimalBytes.format(1500); // 1.5 KB
decimalBytes.parse('1.5 MB'); // 1500000
binaryBytes.format(1536); // 1.5 KiB
binaryBytes.parse('1.5 KiB'); // 1536Currency formatting is display-oriented. Use a decimal or money type for financial calculation, then use Numeral to render and parse strings.
import 'package:numeral/zh.dart' as zh;
final usd = CurrencyCodec(r'$');
final cny = CurrencyCodec(
'¥',
style: zh.compact(maxFractionDigits: 0),
);
usd.format(1234.5); // $1,234.50
usd.parse(r'$1,234.50'); // 1234.5
cny.format(1000000); // ¥100万
cny.parse('¥100万'); // 1000000Every codec has parse and tryParse:
final bytes = BytesCodec.binary();
bytes.parse('1 KiB'); // 1024
bytes.tryParse('bad input'); // nullparse returns the natural numeric type for the codec:
DecimalCodec.parse(...)returnsnum.CompactCodec.parse(...)returnsnum.PercentCodec.parse(...)returnsdouble.BytesCodec.parse(...)returnsint.CurrencyCodec.parse(...)returnsnum.
Built-in language packs live behind separate import paths. This keeps the core API small while allowing locale-specific rules to include code, not only unit data.
import 'package:numeral/zh.dart' as zh;
final compact = zh.compact(maxFractionDigits: 2);
final words = zh.cardinal();
final year = zh.year();
final financial = zh.financial();
final rmb = zh.rmb();
compact.format(1234567); // 123.46万
words.format(1000000); // 一百万
words.format(2000000); // 二百万
words.parse('一百万'); // 1000000
words.parse('两百万'); // 2000000
words.parse('一万零十'); // 10010
words.parse('一万二'); // 12000
year.format(2026); // 二〇二六
financial.format(1000000); // 壹佰万
rmb.format(1000000); // 人民币壹佰万元整
rmb.format(1234567.89); // 人民币壹佰贰拾叁万肆仟伍佰陆拾柒元捌角玖分Traditional Chinese is available from its own language path:
import 'package:numeral/zh_hant.dart' as zh_hant;
final compact = zh_hant.compact(maxFractionDigits: 2);
final words = zh_hant.cardinal();
final year = zh_hant.year();
final financial = zh_hant.financial();
compact.format(1234567); // 123.46萬
compact.parse('2億'); // 200000000
words.format(1000000); // 一百萬
words.parse('兩百萬'); // 2000000
words.parse('一萬零十'); // 10010
year.format(2026); // 二〇二六
financial.format(1000000); // 壹佰萬French is available from its own language path:
import 'package:numeral/fr.dart' as fr;
final compact = fr.compact(maxFractionDigits: 1);
final words = fr.cardinal();
final year = fr.year();
compact.format(1500000); // 1,5 M
compact.parse('1,5 M'); // 1500000
words.format(2026); // deux-mille-vingt-six
words.parse('soixante-et-onze'); // 71
year.format(2026); // deux-mille-vingt-sixKorean is available from its own language path:
import 'package:numeral/ko.dart' as ko;
final compact = ko.compact(maxFractionDigits: 2);
final words = ko.cardinal();
final year = ko.year();
compact.format(12345); // 1.23만
compact.parse('2억'); // 200000000
words.format(1000000); // 백만
words.format(123456789); // 일억이천삼백사십오만육천칠백팔십구
words.parse('일억 이천만'); // 120000000
year.format(2026); // 이천이십육Japanese is available from its own language path:
import 'package:numeral/ja.dart' as ja;
final compact = ja.compact(maxFractionDigits: 2);
final words = ja.cardinal();
final year = ja.year();
compact.format(1234567); // 123.46万
compact.parse('2億'); // 200000000
words.format(1000000); // 百万
words.format(10000001); // 一千万一
words.parse('一万〇一'); // 10001
words.parse('千五百万'); // 15000000
year.format(2026); // 二〇二六Spanish is available from its own language path:
import 'package:numeral/es.dart' as es;
final compact = es.compact(maxFractionDigits: 2);
final words = es.cardinal();
compact.format(1500000); // 1,5 M
compact.parse('2 millones'); // 2000000
words.format(1000000); // un millón
words.format(1000000000); // mil millones
words.format(1000000000000); // un billón
words.parse('veintiún mil'); // 21000External packages can build the same style of language path by reusing
NumeralLanguage, NumeralUnitSet, and NumeralCodec.
This library is licensed under the MIT License.