Как fmt.Е целое число с разделителями тысяч

идет fmt.Printf поддержка вывода числа с запятой тысяч?

fmt.Printf("%d", 1000) выходы 1000, какой формат я могу указать для вывода ?

на docs кажется, не упоминают запятые, и я не мог сразу увидеть что-нибудь в источник.

10 ответов


ни один из глаголов печати 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 Playground.

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]. Числовое значение '-' is 45, в то время как числовое значение цифровых символов '0'..'9' are 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 в плотном цикле. Из моего профилирования это более чем в два раза быстрее, чем гуманизация.Реализация запятых (~680ns против 1642ns) на моем Mac. Я новичок в Go, хотел бы видеть более быстрые реализации!

использование: s: = NumberToString(n int, sep руна)

примеры

иллюстрирует использование другого разделителя ( ' , 'vs''), проверенного с диапазоном значений int.

s:= NumberToString (12345678, ',')

=> "12,345,678"

s:= NumberToString (12345678, ' ')

=> "12 345 678"

s: = NumberToString (-9223372036854775807, ',')

=> "-9,223,372,036,854,775,807"


вот простая функция с использованием regex:

import (
    "strconv"
    "regexp"
)

func formatCommas(num int) string {
    str := strconv.Itoa(num)
    re := regexp.MustCompile("(\d+)(\d{3})")
    for i := 0; i < (len(str) - 1) / 3; i++ {
        str = re.ReplaceAllString(str, ",")
    }
    return str
}

пример:

fmt.Println(formatCommas(1000))
fmt.Println(formatCommas(-1000000000))

выход:

1,000
-1,000,000,000

https://play.golang.org/p/0v6wOzxJ1H


использовать 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, и приветствовать любые изменения от тех, кто может заставить мой работать отлично.


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]
}