web-dev-qa-db-fra.com

Comment fmt.Printf un entier avec des milliers de virgules

Est-ce que Go fmt.Printf Prend en charge la sortie d'un nombre avec la virgule des milliers?

fmt.Printf("%d", 1000) affiche 1000, quel format puis-je spécifier pour afficher 1,000 à la place?

Les docs ne semblent pas mentionner de virgules, et je n'ai rien vu immédiatement dans les source .

51
BrandonAGr

Aucun des verbes d'impression fmt ne prend en charge des milliers de séparateurs.

25
zzzz

Utilisation golang.org/x/text/message pour imprimer en utilisant une mise en forme localisée pour n'importe quelle langue dans le nicode 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
}
46
dolmen

J'ai écrit ne bibliothèque pour cela ainsi que quelques autres problèmes de représentation humaine.

Exemples de résultats:

0 -> 0
100 -> 100
1000 -> 1,000
1000000000 -> 1,000,000,000
-100000 -> -100,000

Exemple d'utilisation:

fmt.Printf("You owe $%s.\n", humanize.Comma(6582491))
46
Dustin

J'ai publié un extrait de Go sur Github d'une fonction pour rendre un nombre (float64 ou int) selon le séparateur de milliers spécifié par l'utilisateur, le séparateur décimal et la précision décimale.

https://Gist.github.com/gorhill/528519

 Utilisation: s: = RenderFloat (format, n) 
 
 Le paramètre format indique comment rendre le nombre n. 
 
 Exemples de format chaînes, étant donné n = 12345.6789: 
 
 "#, ###. ##" => "12 345,67" 
 "#, ###." => "12 345" 
 "#, ###" => "12345 678" 
 "#\U202F ###, ##" => "12345,67" 
 "#. ###, ###### => 12.345,678900 
" "(Alias ​​format par défaut) => 12.345.67 
11
R. Hill

Le package fmt ne prend pas en charge le regroupement des décimales.

Nous devons en implémenter un nous-mêmes (ou utiliser un existant).

Le code

Voici une solution compacte et vraiment efficace (voir explication ci-dessous):

Essayez-le sur le 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] = ','
        }
    }
}

Le tester:

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

Production:

         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

Explication:

Fondamentalement, la fonction Format() formate le nombre sans regroupement, puis crée une autre tranche suffisamment grande et copie les chiffres du symbole de regroupement par virgule (',') En insérant le cas échéant (après groupes de chiffres de 3 s'il y a plus de chiffres) en prenant soin du signe négatif à conserver.

La longueur de la sortie:

Il s'agit essentiellement de la longueur de l'entrée plus le nombre de signes de regroupement à insérer. Le nombre de signes de regroupement est:

numOfCommas = (numOfDigits - 1) / 3

Étant donné que la chaîne d'entrée est un nombre qui ne peut contenir que des chiffres ('0..9') Et éventuellement un signe négatif ('-'), Les caractères sont simplement mappés en octets de façon 1 à 1 dans Encodage UTF-8 (c'est ainsi que Go stocke les chaînes en mémoire). Nous pouvons donc simplement travailler avec des octets au lieu de runes. Le nombre de chiffres correspond donc à la longueur de la chaîne d'entrée, éventuellement moins 1 Pour le chiffre de signe '-'.

S'il y a un chiffre de signe, il sera dans in[0]. La valeur numérique de '-' Est 45, Tandis que la valeur numérique des caractères numériques '0'..'9' Est 48..57. Le caractère du signe est donc inférieur aux chiffres possibles. Donc, si nous divisons le premier caractère (il y a toujours au moins 1 caractère) par '0', Nous obtenons 0 S'il s'agit d'un signe négatif et 1 S'il s'agit d'un chiffre (division entière).

Ainsi, le nombre de chiffres dans la chaîne d'entrée est:

numOfDigits = len(in) - 1 + int(in[0]/'0')

Et donc le nombre de signes de regroupement:

numOfCommas = (len(in) - 2 + int(in[0]/'0')) / 3

Par conséquent, la tranche de sortie sera:

out := make([]byte, len(in)+(len(in)-2+int(in[0]/'0'))/3)

Gestion du caractère signe négatif:

Si le nombre est négatif, nous coupons simplement la chaîne d'entrée pour l'exclure du traitement et nous copions manuellement le bit de signe dans la sortie:

if in[0] == '-' {
    in, out[0] = in[1:], '-'
}

Par conséquent, le reste de la fonction n'a pas besoin de connaître/se soucier du caractère de signe négatif facultatif.

Le reste de la fonction est une boucle for qui copie simplement les octets (chiffres) du nombre de la chaîne d'entrée dans la sortie, en insérant un signe de regroupement (',') Après chaque groupe de 3 chiffres s'il y a plus de chiffres. La boucle descend, il est donc plus facile de suivre les groupes de 3 chiffres. Une fois terminé (plus de chiffres), la tranche d'octets de sortie est renvoyée sous la forme d'un string.

Variations

Gérer le négatif avec la récursivité

Si vous vous souciez moins de l'efficacité et de la lisibilité, vous aimerez peut-être cette version:

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] = ','
        }
    }
}

Fondamentalement, cela gère les nombres négatifs avec un appel récursif: si le nombre est négatif, s'appelle lui-même (récursif) avec la valeur absolue (positive) et ajoute le résultat avec une chaîne "-".

Avec les tranches append()

Voici une autre version utilisant la fonction intégrée append() et les opérations de tranche. Un peu plus facile à comprendre mais pas si bon en termes de performances:

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

La première instruction if prend en charge le premier groupe facultatif "incomplet" de moins de 3 chiffres s'il existe, et la boucle for suivante gère le reste, en copiant 3 chiffres à chaque itération et en ajoutant une virgule (',') signe de regroupement s'il y a plus de chiffres.

11
icza

Voici une fonction qui prend un séparateur d'entier et de regroupement et renvoie une chaîne délimitée par le séparateur spécifié. J'ai essayé d'optimiser pour l'efficacité, pas de concaténation de chaîne ou de mod/division dans la boucle serrée. D'après mon profilage, il est plus de deux fois plus rapide que l'implémentation humanize.Commas (~ 680ns vs 1642ns) sur mon Mac. Je suis nouveau sur Go, j'adorerais voir des implémentations plus rapides!

Utilisation: s: = NumberToString (n int, sep rune)

Exemples

Illustre en utilisant différents séparateurs (',' vs ''), vérifiés avec une plage de valeurs int.

s: = NumberToString (12345678, ',')

=> "12 345 678"

s: = NumberToString (12345678, '')

=> "12 345 678"

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

=> "-9.223.372.036.854.775.807"

Implémentation de la fonction

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()
}
7
Ivan Tung

Voici une fonction simple utilisant regex:

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
}

Exemple:

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

Production:

1,000
-1,000,000,000

https://play.golang.org/p/vnsAV23nUXv

2
jchavannes

Utilisez https://github.com/dustin/go-humanize .. il a un tas d'assistants pour gérer ces choses. En plus d'octets comme MiB, MB et autres goodies.

1
abourget

Le package humanize peut faire la magie! Reportez-vous à la documentation de ce package ici . Pour utiliser ce package, installez-le d'abord en utilisant un outil comme Git SCM. Si vous utilisez Git Bash, ouvrez la fenêtre Shell et tapez:

go get -u github.com/dustin/go-humanize

Une fois cela fait, vous pouvez utiliser le code de solution suivant (Say, 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)));
}

Il existe d'autres variantes de Commaf comme BigComma, Comma, BigCommaf etc. qui dépend du type de données de votre entrée.

Ainsi, lors de l'exécution de ce programme à l'aide de la commande:

go run main.go

Vous verrez une sortie comme celle-ci:

123,456,789
-1,000,000,000
-100,000.005
100,000
0