GNU numfmt: SI and IEC Units, Rounding, and Field Contracts
Use GNU numfmt without confusing SI and IEC suffixes, losing exact values to display rounding, or treating delimited text as CSV.
numfmt is useful when an operator needs to turn machine-oriented quantities into readable output, or normalize human-entered suffixes before comparison. The dangerous part is that a formatted number looks authoritative even when its unit convention, input field, locale, invalid-record policy, or rounding rule is wrong. GNU numfmt is a converter with explicit parsing and display options; it is not a schema validator and its human-readable output is not a lossless storage format.
This guide treats the unit and text boundaries as part of the command contract. The examples target GNU Coreutils numfmt; confirm the implementation and supported options on the host before putting them into a portable script. It is not a POSIX utility, and a command with the same name on another platform need not implement GNU’s options.
Make the suffix convention explicit
The --from and --to options select GNU numfmt’s suffix families. si uses powers of 1000, while iec uses powers of 1024 and iec-i uses the two-letter IEC-style spellings such as Ki and Mi. The auto mode is an input mode: it recognizes suffixes, but does not select a unit family for output. In this parser, a one-letter M means the SI factor of one million, while Mi means the IEC factor of 1,048,576. That one-character difference matters in inventory reports, quotas, and capacity calculations.
printf '%s\n' '1M' '1Mi' |
LC_ALL=C numfmt --from=auto --to=none
With GNU Coreutils, the converted values are 1000000 and 1048576. --to=none deliberately emits the integer values without a display suffix. The command does not infer that these inputs describe bytes: the suffix is a numeric scale, and the meaning of the quantity still comes from the producer’s schema. Do not compare values from two tools until you have established that both tools use the same units and the same underlying metric.
For output, choose a convention rather than relying on a reader’s assumptions:
printf '%s\n' 1000000 1048576 |
LC_ALL=C numfmt --to=si --format='%.2f'
printf '%s\n' 1500 |
LC_ALL=C numfmt --from=none --to=iec-i --format='%.3f'
These produce decimal SI output for the first two values and an IEC-style binary result for the second (1.465Ki with the shown format). A suffix is a compact presentation of a scaled value, not a claim that the original quantity was measured in bytes. Keep the raw integer alongside the rendered string when the value will be stored, signed, compared exactly, or sent to another program.
--from-unit and --to-unit are different from choosing a human-readable suffix family. They scale input or output by a fixed unit size. For example, if a documented producer emits counts of 512-byte blocks, --from-unit=512 can convert those counts to a byte quantity. That flag cannot prove the producer really used 512-byte blocks. Verify the producer’s interface first; otherwise the command can precisely calculate the wrong quantity.
Treat rounding as a display decision
Auto-scaling and a printf-style --format can round a value. GNU numfmt documents from-zero as the default rounding mode and supports up, down, towards-zero, and nearest. Output precision can also be set by a format such as %.3f. A short decimal display may be ideal for a dashboard and unsuitable for billing, threshold enforcement, checksums, or a later conversion back to an integer.
Make the rule visible in a report command, and preserve its source column:
printf '%s\n' 1499 1500 1501 |
LC_ALL=C numfmt --to=iec-i --round=nearest --format='%.2f'
This is a presentation example, not a serialization format. If another program will consume the values, pass the raw integer or a lossless decimal representation with an explicit unit. Avoid rounding before a comparison that is supposed to use the exact threshold: compare raw values first, then format the result for people.
Locale is another output dependency. --grouping follows the current locale’s digit-grouping rules and has no effect in the POSIX/C locale. If a downstream tool expects a dot decimal separator, do not let a human locale silently change the representation. For machine-facing output, set a deliberate locale such as LC_ALL=C, avoid optional grouping, and test the exact implementation in the deployment environment. For user-facing output, select the intended locale and test examples there rather than assuming every reader uses comma-grouped thousands.
Select fields without pretending the input is CSV
By default, numfmt converts a numeric field in line-oriented input. --field uses cut-style field numbering and ranges; --delimiter changes the field separator. These options are useful for simple, controlled records, but they do not make the parser a CSV implementation with quoted delimiters and escaped quotes.
For a deliberately simple comma-separated report whose second field is an unquoted quantity:
printf 'name,quantity\narchive,1M\nimage,1Mi\n' |
LC_ALL=C numfmt --header=1 --delimiter=, --field=2 \
--from=auto --to=none
The header passes through unchanged and the second fields are normalized to decimal integers. If a field can contain a comma inside quotes, this is the wrong parser: parse the file with a CSV-aware tool, validate its schema, and then pass only the numeric values to numfmt. Do not use a separator option as a substitute for quoting rules or record validation.
Check what a field operation means before applying it to production data. --header skips a configured number of initial lines; it does not validate that the header has the expected names. A field range may select more than one field, and custom delimiters affect numfmt’s automatic padding behavior. Build a fixture containing a header, empty field, leading/trailing spaces, a short row, and a malformed value, then verify both the transformed output and the exit status.
Choose how invalid values affect the pipeline
GNU numfmt’s default invalid-input behavior is to abort with status 2. --invalid=fail emits a warning for each conversion error and also exits 2. --invalid=warn warns but exits 0 even when conversions fail; --invalid=ignore omits those diagnostics. The latter modes can make a shell pipeline appear successful while rows remain unconverted. A downstream command that only checks $? may then mistake partial normalization for complete validation.
Keep the default abort policy when every row must be valid. If the report should continue while still flagging bad rows, capture and inspect diagnostics explicitly, retain the original input, and make the partial-result policy a documented product decision. Do not use --invalid=warn or --invalid=ignore as a quick fix for dirty data. In a pipeline, enable the shell’s pipeline-failure behavior where appropriate and test the exact failure you expect; for Bash, set -o pipefail makes the pipeline return a failing status when a command before the last pipeline element fails.
The following fixture demonstrates the difference without modifying files:
printf '%s\n' '1M' 'not-a-number' |
LC_ALL=C numfmt --from=auto --to=none --invalid=fail
status=$?
printf 'numfmt status: %s\n' "$status" >&2
The invalid row should produce a diagnostic and the utility should return status 2. Use a separate test for warn if its zero status is intentional; do not infer data validity from a successful process exit when that mode has been selected.
Review a conversion as a small data pipeline
Before shipping a numfmt command, record the producer’s unit, the accepted suffix convention, selected field, locale, rounding mode, and invalid-row policy. Test known values at unit boundaries: one unit below, exactly at, and one above a scale change. Also test values with both SI and IEC suffixes. Include negative values if the source contract permits them, and use values large enough to reveal integer/format limits for the target tool.
Then inspect the output as data, not just as a screenshot. Confirm that the number of rows and header are preserved, that only the intended field changed, and that malformed input has the intended status. Make sure the surrounding schema or report labels the resulting unit: --to=none emits no suffix and relies on that documented context. A rounded value such as 1.46Ki cannot identify its exact original integer. Keep authoritative numbers and unit metadata in a machine-readable channel; format a copy for logs or dashboards.
The safe mental model is simple: suffixes specify scale, --from-unit and --to-unit specify fixed multipliers, fields specify where conversion is attempted, locale and format influence the emitted text, and invalid/rounding options control information loss and failure behavior. numfmt performs those requested operations; it does not validate the wider meaning of a record. Make each assumption explicit and the command becomes a reviewable boundary rather than an attractive source of silent unit errors.
Related:
- cut in Shell Scripts: Delimiters, Character Sets, and Record Assumptions
- sort in Shell Pipelines: Locale, Keys, Stability, and Reproducible Output
Sources: