web-dev-qa-db-fra.com

Arrondi de Golang au plus proche 0,05

Je cherche une fonction à arrondir au 0,05 le plus proche à Golang. Le résultat final de l'utilisation de la fonction doit toujours être un facteur de 0,05.


Voici quelques exemples de sorties pour la fonction que je recherche: (La fonction Round n'existe pas encore, j'espère qu'elle pourra être incluse dans la réponse)

Round(0.363636) // 0.35
Round(3.232)    // 3.25
Round(0.4888)   // 0.5

J'ai cherché depuis des siècles et je n'ai trouvé aucune réponse.

28
Acidic

Go 1.10 a été publié et il ajoute une fonction math.Round() . Cette fonction arrondit à l'entier le plus proche (qui est fondamentalement une opération "arrondir à 1,0" ), et en utilisant cela, nous pouvons très facilement construire une fonction qui arrondit à l'unité de notre choix:

func Round(x, unit float64) float64 {
    return math.Round(x/unit) * unit
}

Le tester:

fmt.Println(Round(0.363636, 0.05)) // 0.35
fmt.Println(Round(3.232, 0.05))    // 3.25
fmt.Println(Round(0.4888, 0.05))   // 0.5

fmt.Println(Round(-0.363636, 0.05)) // -0.35
fmt.Println(Round(-3.232, 0.05))    // -3.25
fmt.Println(Round(-0.4888, 0.05))   // -0.5

Essayez-le sur le Go Playground .

La réponse d'origine suit qui a été créée avant Go 1.10 alors qu'il n'existait pas de math.Round(), et qui détaille également la logique derrière notre fonction personnalisée Round(). C'est ici à des fins éducatives.


Dans l'ère pré-Go1.10, il n'y avait pas de math.Round(). Mais...

Les tâches d'arrondi peuvent facilement être implémentées par une converison float64 => int64, Mais il faut faire attention car la conversion float en int n'est pas arrondie mais en gardant la partie entière (voir les détails dans Aller: Convertir float64 en int avec multiplicateur ).

Par exemple:

var f float64
f = 12.3
fmt.Println(int64(f)) // 12
f = 12.6
fmt.Println(int64(f)) // 12

Le résultat est 12 Dans les deux cas, la partie entière. Pour obtenir la "fonctionnalité" d'arrondi, ajoutez simplement 0.5:

f = 12.3
fmt.Println(int64(f + 0.5)) // 12
f = 12.6
fmt.Println(int64(f + 0.5)) // 13

Jusqu'ici tout va bien. Mais nous ne voulons pas arrondir à des entiers. Si nous voulions arrondir à 1 chiffre de fraction, nous multiplierions par 10 avant d'ajouter 0.5 Et de convertir:

f = 12.31
fmt.Println(float64(int64(f*10+0.5)) / 10) // 12.3
f = 12.66
fmt.Println(float64(int64(f*10+0.5)) / 10) // 12.7

Donc, fondamentalement, vous multipliez par l'inverse de l'unité à laquelle vous souhaitez arrondir. Pour arrondir à 0.05 Unités, multipliez par 1/0.05 = 20:

f = 12.31
fmt.Println(float64(int64(f*20+0.5)) / 20) // 12.3
f = 12.66
fmt.Println(float64(int64(f*20+0.5)) / 20) // 12.65

Envelopper cela dans une fonction:

func Round(x, unit float64) float64 {
    return float64(int64(x/unit+0.5)) * unit
}

En l'utilisant:

fmt.Println(Round(0.363636, 0.05)) // 0.35
fmt.Println(Round(3.232, 0.05))    // 3.25
fmt.Println(Round(0.4888, 0.05))   // 0.5

Essayez les exemples sur le Go Playground .

Notez que l'arrondissement de 3.232 Avec unit=0.05 N'imprimera pas exactement 3.25 Mais 0.35000000000000003. En effet, les chiffres float64 Sont stockés avec une précision finie, appelée norme IEEE-754 . Pour plus de détails, voir Golang convertissant float64 en erreur int .

Notez également que unit peut être "n'importe quel nombre". Si c'est 1, Alors Round() arrondit essentiellement au nombre entier le plus proche. Si c'est 10, Il arrondit à des dizaines, si c'est 0.01, Il arrondit à 2 chiffres de fraction.

Notez également que lorsque vous appelez Round() avec un nombre négatif, vous pourriez obtenir un résultat surprenant:

fmt.Println(Round(-0.363636, 0.05)) // -0.3
fmt.Println(Round(-3.232, 0.05))    // -3.2
fmt.Println(Round(-0.4888, 0.05))   // -0.45

En effet, comme indiqué précédemment, la conversion conserve la partie entière et, par exemple, la partie entière de -1.6 Est -1 (Qui est supérieure à -1.6; Tandis que la partie entière de 1.6 Est 1 Qui est inférieur à 1.6).

Si vous voulez que -0.363636 Devienne -0.35 Au lieu de -0.30, Alors en cas de nombres négatifs, ajoutez -0.5 Au lieu de 0.5 À l'intérieur du Round() fonction. Voir notre fonction Round2() améliorée:

func Round2(x, unit float64) float64 {
    if x > 0 {
        return float64(int64(x/unit+0.5)) * unit
    }
    return float64(int64(x/unit-0.5)) * unit
}

Et en l'utilisant:

fmt.Println(Round2(-0.363636, 0.05)) // -0.35
fmt.Println(Round2(-3.232, 0.05))    // -3.25
fmt.Println(Round2(-0.4888, 0.05))   // -0.5

MODIFIER:

Pour répondre à votre commentaire: parce que vous n'aimez pas le 0.35000000000000003 Non exact, vous avez proposé de le formater et de le ré-analyser comme:

formatted, err := strconv.ParseFloat(fmt.Sprintf("%.2f", rounded), 64)

Et cela "apparemment" donne le résultat exact car l'impression donne exactement 0.35.

Mais ce n'est qu'une "illusion". Puisque 0.35 Ne peut pas être représenté avec des bits finis en utilisant la norme IEEE-754, peu importe ce que vous faites avec le nombre, si vous le stockez dans une valeur de type float64, Il ne le sera pas être exactement 0.35 (mais un numéro IEEE-754 en étant très proche). Ce que vous voyez est fmt.Println() en l'imprimant comme 0.35 Parce que fmt.Println() fait déjà un arrondi.

Mais si vous essayez de l'imprimer avec une plus grande précision:

fmt.Printf("%.30f\n", Round(0.363636, 0.05))
fmt.Printf("%.30f\n", Round(3.232, 0.05))
fmt.Printf("%.30f\n", Round(0.4888, 0.05))

Sortie: ce n'est pas mieux (peut-être encore plus laid): essayez-le sur le Go Playground :

0.349999999999999977795539507497
3.250000000000000000000000000000
0.500000000000000000000000000000

Notez qu'en revanche 3.25 Et 0.5 Sont exacts car ils peuvent être représentés avec des bits finis exactement, car représentant en binaire:

3.25 = 3 + 0.25 = 11.01binary
0.5 = 0.1binary

Quelle est la leçon? La mise en forme et l'analyse du résultat ne valent pas la peine, car elles ne seront pas exactes non plus (juste une valeur float64 Différente qui, selon les règles de mise en forme par défaut de fmt.Println(), pourrait être plus agréable à imprimer ). Si vous voulez un joli format imprimé, formatez-le avec précision, comme:

func main() {
    fmt.Printf("%.3f\n", Round(0.363636, 0.05))
    fmt.Printf("%.3f\n", Round(3.232, 0.05))
    fmt.Printf("%.3f\n", Round(0.4888, 0.05))
}

func Round(x, unit float64) float64 {
    return float64(int64(x/unit+0.5)) * unit
}

Et ce sera exact (essayez-le sur le Go Playground ):

0.350
3.250
0.500

Ou multipliez-les simplement par 100 Et travaillez avec des nombres entiers, afin qu'aucune erreur de représentation ou d'arrondi ne se produise.

83
icza