tl; др
Используйте numfmt
, если доступны утилиты GNU , например, наLinux по умолчанию:
numfmt --grouping 12343423455.23353 # -> 12,343,423,455.23353 in locale en_US
В противном случае используйте printf
с флагом поля '
, заключенным в функция оболочки , которая сохраняет количество входных десятичных разрядов (без жесткого кодирования числа выходных десятичных разрядов).
groupDigits 12343423455.23353 # -> 12,343,423,455.23353 in locale en_US
- См. В нижней части этого ответа определение
groupDigits()
, которое также поддерживает множественные входные номера.
Специальные альтернативы, включающие подоболочки , которые также сохраняют количество десятичных разрядов ввода (предполагается, что десятичная метка ввода - .
или ,
):
- Модульный, но несколько неэффективный вариант, который принимает входной номер через stdin (и поэтому может также использоваться с конвейерным вводом ):
(n=$(</dev/stdin); f=${n#*[.,]}; printf "%'.${#f}f\n" "$n") <<<12343423455.23353
- Значительно более быстрая, но менее модульная альтернатива, использующая промежуточную переменную
$n
: n=12343423455.23353; (f=${n#*[.,]} printf "%'.${#f}f\n" "$n")
В качестве альтернативы рассмотрите возможность использования моего Linux / macOS grp
CLI (устанавливается с npm install -g grp-cli
):
Во всехслучаи предостережения ;см. ниже.
Ответ Игнасио Васкеса-Абрамса содержит критический указатель для использования с printf
: флаг поля '
(после %
) форматирует числос разделителем тысяч в активной локали:
- Обратите внимание, что
man printf
(man 1 printf
) сама не содержит эту информацию: утилита / shell, встроенная printf
, в конечном счете вызывает библиотечная функция printf()
, и только man 3 printf
дает полное представление о поддерживаемых форматах. - Переменные среды
LC_NUMERIC
и, косвенно, LANG
или LC_ALL
controlактивная локаль по отношению к форматированию чисел. - И
numfmt
, и printf
относятся к активной локали, как по отношению к разделителю тысяч, так и к десятичной метке («десятичная точка»). - Использование только
printf
само по себе, как в ответе Игнасио, требует от вас жесткого кода числа выходных данных десятичных разрядов, а не сохранения скольких десятичных разрядов, которые имеет вход;это ограничение, которое преодолевается groupDigits()
ниже. printf "%'.<numDecPlaces>f"
имеет одно преимущество перед numfmt --grouping
, однако: numfmt
принимает только десятичное числотогда как printf
%f
также принимает шестнадцатеричные целые числа (например, 0x3e8
) и цифры в десятичном виде научное обозначение (например, 1e3
).
Предостережения
Локали без группировки : Некоторые локали, в частности C
и POSIX
, по определению делаютНЕ применяется группировка, поэтому использование '
не оказывает никакого влияния на это событие.
Реальные несоответствия локали на разных платформах :
(LC_ALL='de_DE.UTF-8'; printf "%'.1f\n" 1000) # SHOULD yield: 1.000,0
- Linux : выход
1.000,0
, как и ожидалось. - macOS / BSD : непредвиденный выход
1000,0
- НЕТгруппировка (!).
- Формат ввода чисел : когда вы передаете число в
numfmt
или printf
, оно: - не должен уже содержать группировку цифр
- должен уже использовать активный десятичный знак локали
- Например:
(LC_ALL='lt_LT.UTF-8'; printf "%'.1f\n" 1000,1) # -> '1 000,1'
- OK: номер вводане группируется и использует литовский десятичный знак (запятую).
Переносимость : POSIX не требуется утилита printf
(в отличие от библиотечной функции C printf()
) для поддержки символов формата с плавающей точкой, таких как %f
,учитывая, что POSIX [-подобные] оболочки являются только целочисленными;на практике, однако, я не знаю никаких оболочек / платформ, которые этого не делают.
Ошибки округления и переполнение :
- При использовании
numfmt
и printf
, как описано, происходит циклическое преобразование (строка -> число ->строка), в которой возможны ошибки округления;другими словами: переформатирование с группировкой цифр может привести к другому числу . - Использование символа формата
f
для использования значений с плавающей точкой двойной точности IEEE-754 , только до 15 значащие цифры (цифры независимо от расположения десятичной метки) гарантированно будут точно сохранены (хотя для определенных чисел это можетработать с большим количеством цифр). На практике numfmt
и GNU printf
могут точно обрабатывать больше , чем это;увидеть ниже.Если кто-то знает, как и почему, сообщите мне. - При наличии слишком большого количества значащих цифр или слишком большого значения, поведение отличается от
numfmt
до printf
в общем и между printf
реализациями на разных платформах ;например:
numft
:
[Исправлено в coreutils 8.24, согласно @ pixelbeat] Начиная с 20 значащих цифр, значение тихо переполняется (!) - предположительно ошибка (по состоянию на GNU coreutils 8.23):
# 20 significant digits cause quiet overflow:
$ (fractPart=0000000000567890; num="1000.${fractPart}"; numfmt --grouping "$num")
-92.23372036854775807 # QUIET OVERFLOW
Напротив, слишком большое число генерирует ошибку по умолчанию.
printf
:
Linux printf
обрабатывает до 20 значащих цифр точно , тогда как реализация BSD / macOS ограничена 17:
# Linux: 21 significant digits cause rounding error:
$ (fractPart=00000000005678901; num="1000.${fractPart}"; printf "%'.${#fractPart}f\n" "$num")
1,000.00000000005678902 # ROUNDING ERROR
# BSD/macOS: 18 significant digits cause rounding error:
$ (fractPart=00000000005678; num="1000.${fractPart}"; printf "%'.${#fractPart}f\n" "$num")
1,000.00000000005673 # ROUNDING ERROR
Кажется, что версия Linux никогда не переполняется, тогда как версия BSD / macOS сообщает об ошибке с слишком большими числами.
Функция оболочки Bash groupDigits()
:
# SYNOPSIS
# groupDigits num ...
# DESCRIPTION
# Formats the specified number(s) according to the rules of the
# current locale in terms of digit grouping (thousands separators).
# Note that input numbers
# - must not already be digit-grouped themselves,
# - must use the *current* locale's decimal mark.
# Numbers can be integers or floats.
# Processing stops at the first number that can't be formatted, and a
# non-zero exit code is returned.
# CAVEATS
# - No input validation is performed.
# - printf(1) is not guaranteed to support non-integer formats by POSIX,
# though not doing so is rare these days.
# - Round-trip number conversion is involved (string > double > string)
# so rounding errors can occur.
# EXAMPLES
# groupDigits 1000 # -> '1,000'
# groupDigits 1000.5 # -> '1,000.5'
# (LC_ALL=lt_LT.UTF-8; groupDigits 1000,5) # -> '1 000,5'
groupDigits() {
local decimalMark fractPart
decimalMark=$(printf "%.1f" 0); decimalMark=${decimalMark:1:1}
for num; do
fractPart=${num##*${decimalMark}}; [[ "$num" == "$fractPart" ]] && fractPart=''
printf "%'.${#fractPart}f\n" "$num" || return
done
}