Does Go fmt.Printf
поддерживает вывод числа с запятой тысяч?
fmt.Printf("%d", 1000)
выводит 1000
, какой формат я могу указать для вывода 1,000
вместо?
docs, похоже, не упоминают запятые, и я не мог сразу увидеть что-либо в source.
Does Go fmt.Printf
поддерживает вывод числа с запятой тысяч?
fmt.Printf("%d", 1000)
выводит 1000
, какой формат я могу указать для вывода 1,000
вместо?
docs, похоже, не упоминают запятые, и я не мог сразу увидеть что-либо в source.
Ни один из глаголов печати fmt не поддерживает тысячи разделителей.
Я написал библиотеку для этого, а также несколько других проблем с представлением интересов человека.
Примеры результатов:
0 -> 0
100 -> 100
1000 -> 1,000
1000000000 -> 1,000,000,000
-100000 -> -100,000
Пример использования:
fmt.Printf("You owe $%s.\n", humanize.Comma(6582491))
Используйте golang.org/x/text/message
для печати с использованием локализованного форматирования для любого языка в Unicode CLDR:
package main
import (
"golang.org/x/text/language"
"golang.org/x/text/message"
)
func main() {
p := message.NewPrinter(language.English)
p.Printf("%d\n", 1000)
// Output:
// 1,000
}
Я опубликовал фрагмент Go в Github функции для рендеринга числа (float64 или int) в соответствии с указанным пользователем разделителем тысяч, десятичным разделителем и десятичной точностью.
https://gist.github.com/gorhill/5285193
Usage: s := RenderFloat(format, n) The format parameter tells how to render the number n. Examples of format strings, given n = 12345.6789: "#,###.##" => "12,345.67" "#,###." => "12,345" "#,###" => "12345,678" "#\u202F###,##" => "12 345,67" "#.###,###### => 12.345,678900 "" (aka default format) => 12,345.67
Пакет fmt
не поддерживает группировку десятичных дробей.
Мы должны реализовать его сами (или использовать уже существующий).
Вот компактное и действительно эффективное решение (см. Объяснение после):
Попробуйте это на игровой площадке Go.
func Format(n int64) string {
in := strconv.FormatInt(n, 10)
out := make([]byte, len(in)+(len(in)-2+int(in[0]/'0'))/3)
if in[0] == '-' {
in, out[0] = in[1:], '-'
}
for i, j, k := len(in)-1, len(out)-1, 0; ; i, j = i-1, j-1 {
out[j] = in[i]
if i == 0 {
return string(out)
}
if k++; k == 3 {
j, k = j-1, 0
out[j] = ','
}
}
}
Тестирование это:
for _, v := range []int64{0, 1, 12, 123, 1234, 123456789} {
fmt.Printf("%10d = %12s\n", v, Format(v))
fmt.Printf("%10d = %12s\n", -v, Format(-v))
}
Выход:
0 = 0
0 = 0
1 = 1
-1 = -1
12 = 12
-12 = -12
123 = 123
-123 = -123
1234 = 1,234
-1234 = -1,234
123456789 = 123,456,789
-123456789 = -123,456,789
По сути, функция Format()
выполняет форматирование числа без группировки, затем создает достаточно большой другой фрагмент и копирует цифры номера, вставляя запятую (','
), когда это необходимо (после групп цифр из 3, если есть). больше цифр) тем временем заботясь о сохранении отрицательного знака.
Длина выхода:
Это в основном длина ввода плюс количество группирующих знаков для вставки. Количество признаков группировки:
numOfCommas = (numOfDigits - 1) / 3
Поскольку входная строка представляет собой число, которое может содержать только цифры ('0..9'
) и, возможно, отрицательный знак ('-'
), символы просто отображаются в байты в формате 1:1 в UTF-8. кодирование (это то, как Go хранит строки в памяти). Таким образом, мы можем просто работать с байтами вместо рун. Таким образом, количество цифр - это длина входной строки, необязательно минус 1
для знака '-'
.
Если есть знак, он будет in[0]
. Числовое значение '-'
равно 45
, а числовое значение цифр '0'..'9'
48..57
. Таким образом, символ знака меньше возможных цифр. Таким образом, если мы разделим первый символ (всегда есть хотя бы 1 символ) на '0'
, мы получим 0
если это отрицательный знак, и 1
если это цифра (целочисленное деление).
Итак, количество цифр во входной строке:
numOfDigits = len(in) - 1 + int(in[0]/'0')
И поэтому количество группирующих признаков:
numOfCommas = (len(in) - 2 + int(in[0]/'0')) / 3
Поэтому выходной срез будет:
out := make([]byte, len(in)+(len(in)-2+int(in[0]/'0'))/3)
Обработка отрицательного знака:
Если число отрицательное, мы просто нарезаем входную строку, чтобы исключить ее из обработки, и вручную копируем бит знака на выход:
if in[0] == '-' {
in, out[0] = in[1:], '-'
}
И, следовательно, остальная часть функции не должна знать/заботиться о необязательном символе отрицательного знака.
Остальная часть функции представляет собой цикл for
который просто копирует байты (цифры) числа из входной строки в выходной файл, вставляя знак группировки (','
) после каждой группы из 3 цифр, если есть больше цифр. Цикл идет вниз, чтобы было легче отслеживать группы из 3 цифр. После этого (больше никаких цифр) срез выходного байта возвращается в виде string
.
Если вас меньше заботит эффективность и больше читабельность, вам может понравиться эта версия:
func Format2(n int64) string {
if n < 0 {
return "-" + Format2(-n)
}
in := strconv.FormatInt(n, 10)
out := make([]byte, len(in)+(len(in)-1)/3)
for i, j, k := len(in)-1, len(out)-1, 0; ; i, j = i-1, j-1 {
out[j] = in[i]
if i == 0 {
return string(out)
}
if k++; k == 3 {
j, k = j-1, 0
out[j] = ','
}
}
}
В основном это обрабатывает отрицательные числа с помощью рекурсивного вызова: если число является отрицательным, вызывает себя (рекурсивно) с абсолютным (положительным) значением и добавляет результат к строке "-"
.
append()
ломтиками Здесь другая версия, использующая встроенную функцию append()
и операции срезов. Несколько проще для понимания, но не так хорошо с точки зрения производительности:
func Format3(n int64) string {
if n < 0 {
return "-" + Format3(-n)
}
in := []byte(strconv.FormatInt(n, 10))
var out []byte
if i := len(in) % 3; i != 0 {
if out, in = append(out, in[:i]...), in[i:]; len(in) > 0 {
out = append(out, ',')
}
}
for len(in) > 0 {
if out, in = append(out, in[:3]...), in[3:]; len(in) > 0 {
out = append(out, ',')
}
}
return string(out)
}
Первый оператор if
заботится о первой необязательной "неполной" группе, которая меньше 3 цифр, если существует, а последующий цикл for
обрабатывает остальное, копируя 3 цифры в каждой итерации и добавляя знак группировки через запятую (','
) если есть еще цифры.
Вот функция, которая принимает разделитель целых чисел и группировки и возвращает строку, ограниченную указанным разделителем. Я попытался оптимизировать эффективность, отсутствие конкатенации строк или mod/division в узком цикле. Из моего профилирования это более чем в два раза быстрее, чем реализация humanize.Commas(~ 680ns против 1642ns) на моем Mac. Я новичок в Go, хотел бы видеть более быстрые реализации!
Использование: s: = NumberToString (n int, sep rune)
Примеры
Иллюстрирует использование разных разделителей (',' vs ''), проверенных с использованием диапазона значений int.
s: = NumberToString (12345678, ',')
= > "12,345,678"
s: = NumberToString (12345678, '')
= > "12 345 678"
s: = NumberToString (-9223372036854775807, ',')
= > "-9,223,372,036,854,775,807"
Выполнение функций
func NumberToString(n int, sep rune) string {
s := strconv.Itoa(n)
startOffset := 0
var buff bytes.Buffer
if n < 0 {
startOffset = 1
buff.WriteByte('-')
}
l := len(s)
commaIndex := 3 - ((l - startOffset) % 3)
if (commaIndex == 3) {
commaIndex = 0
}
for i := startOffset; i < l; i++ {
if (commaIndex == 3) {
buff.WriteRune(sep)
commaIndex = 0
}
commaIndex++
buff.WriteByte(s[i])
}
return buff.String()
}
Вот простая функция с использованием регулярных выражений:
import (
"regexp"
)
func formatCommas(num int) string {
str := fmt.Sprintf("%d", num)
re := regexp.MustCompile("(\\d+)(\\d{3})")
for n := ""; n != str; {
n = str
str = re.ReplaceAllString(str, "$1,$2")
}
return str
}
Пример:
fmt.Println(formatCommas(1000))
fmt.Println(formatCommas(-1000000000))
Выход:
1,000
-1,000,000,000
Используйте https://github.com/dustin/go-humanize.. у него есть куча помощников, чтобы справиться с этими вещами. В дополнение к байтам, как MiB, MB и другие лакомства.
Если вы не хотите использовать библиотеку (по какой-то причине), я ее сбил. Кажется, что он работает и может использовать любую указанную руну в качестве разделителя:
import (
"strconv"
)
func delimitNumeral(i int, delim rune) string {
src := strconv.Itoa(i)
strLen := utf8.RuneCountInString(src)
outStr := ""
digitCount := 0
for i := strLen - 1; i >= 0; i-- {
outStr = src[i:i+1] + outStr
if digitCount == 2 {
outStr = string(delim) + outStr
digitCount = 0
} else {
digitCount++
}
}
return outStr
}
Примечание: после дальнейшего тестирования эта функция работает не так. Я бы предложил использовать решение, отправленное @IvanTung, и приветствовать любые изменения от всех, кто может заставить мои работать отлично.
Гуманизированный пакет может творить чудеса! Обратитесь к документации этого пакета здесь. Чтобы использовать этот пакет, сначала установите его с помощью такого инструмента, как Git SCM. Если вы используете Git Bash, откройте окно оболочки и введите:
go get -u github.com/dustin/go-humanize
Как только это будет сделано, вы можете использовать следующий код решения (скажем, main.go):
package main
import (
"fmt"
"github.com/dustin/go-humanize"
)
func main() {
fmt.Println(humanize.Commaf(float64(123456789)));
fmt.Println(humanize.Commaf(float64(-1000000000)));
fmt.Println(humanize.Commaf(float64(-100000.005)));
fmt.Println(humanize.Commaf(float64(100000.000)));
}
Существуют и другие варианты Commaf
такие как BigComma, Comma, BigCommaf
и т.д. BigComma, Comma, BigCommaf
зависят от типа данных BigComma, Comma, BigCommaf
вами.
Итак, при запуске этой программы с помощью команды:
go run main.go
Вы увидите вывод, такой как этот:
123,456,789
-1,000,000,000
-100,000.005
100,000
import ("fmt"; "strings")
func commas(s string) string {
if len(s) <= 3 {
return s
} else {
return commas(s[0:len(s)-3]) + "," + s[len(s)-3:]
}
}
func toString(f float64) string {
parts := strings.Split(fmt.Sprintf("%.2f", f), ".")
if parts[0][0] == '-' {
return "-" + commas(parts[0][1:]) + "." + parts[1]
}
return commas(parts[0]) + "." + parts[1]
}