2016-09-17 7 views
11

Ich bin auf der Suche nach einer Funktion zum Runden auf die nächsten 0,05 in Golang. Das Endergebnis der Verwendung der Funktion muss immer ein Faktor von 0,05 sein.Golang Rund zur nächsten 0.05


Hier sind einige Beispiele von Ausgaben für die Funktion ich suche: (Die Funktion Runde noch nicht vorhanden ist, ich hoffe, es in der Antwort enthalten sein kann)

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

Ich habe schon seit Ewigkeiten gesucht und habe keine Antworten gefunden.

+0

Ich habe eine online gefunden, aber es tut nicht ganz das, was ich in der Frage gefragt:/ – Acidic

+0

Related: [Go: Converting Float64 zu Int mit Multiplikator] (http://StackOverflow.com/Questions/33206059/ go-converting-float64-to-int-with-multiplier) – icza

+0

Wenn Sie eine Rundungsfunktion haben, die korrekte Ergebnisse liefert, müssen Sie nur die Genauigkeit des Ergebnisses verwalten, indem Sie es abschneiden. Eine Art ham-fisted Ansatz für das wäre https://play.golang.org/p/DRTTkQQI42 (nicht getestet Flanke) – abhink

Antwort

50

Go 1.10 wurde freigegeben und fügt eine math.Round() Funktion hinzu. Diese Funktion rundet auf die nächste ganze Zahl (die im Grunde ein „round zur nächsten 1.0“ Betrieb), und die Verwendung dieser wir sehr leicht eine Funktion konstruieren kann, die an das Gerät unserer Wahl Runden:

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

Testing es:

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 

Versuchen sie es auf dem Go Playground.

Die ursprüngliche Antwort folgt, die in der Ära erstellt wurde, in der keine math.Round() existiert, und die auch die Logik hinter unserer benutzerdefinierten Round()-Funktion detailliert. Es ist hier für Bildungszwecke.


In der Pre-Go1.10 Ära gab es keine math.Round(). Aber ...

Rounding Aufgaben können leicht durch eine float64 =>int64 converison umgesetzt werden, sondern muss darauf geachtet werden, wie Schwimmer genommen wird Umwandlung in int wird nicht Rundung aber hält den ganzzahligen Teil (siehe Details in Go: Converting float64 to int with multiplier).

Zum Beispiel:

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

Ergebnis ist 12 in beiden Fällen der Integer-Teil. Um die Rundung "Funktionalität" zu erhalten, fügen Sie einfach 0.5:

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

So weit so gut. Aber wir wollen uns nicht auf ganze Zahlen konzentrieren. Wenn wir würden wollten 1-Fraktion Ziffer abzurunden, würden wir mit 10 multiplizieren, bevor 0.5 Hinzufügen und Konvertieren:

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 

Also im Grunde multiplizieren Sie mit dem Kehrwert der Einheit, die Sie runden möchten.Zur Abrundung zu 0.05 Einheiten, multipliziert mit 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 

Wrapping diese in eine Funktion:

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

es verwenden:

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 

die Beispiele Versuchen Sie, auf die Go Playground.

Beachten Sie, dass 3.232 mit unit=0.05 Rundung nicht exakt 3.25 drucken, sondern 0.35000000000000003. Dies liegt daran, dass float64 Zahlen unter Verwendung der endlichen Genauigkeit gespeichert werden, die IEEE-754 Standard genannt wird. Für Details siehe Golang converting float64 to int error.

Beachten Sie auch, dass unit sein kann "any" Zahl. Wenn es 1 ist, dann rundet Round() im Grunde die nächste Ganzzahl ab. Wenn es 10 ist, rundet es auf zehn, wenn es 0.01 ist, rundet es auf 2 Bruchstellen ab.

Beachten Sie auch, dass, wenn Sie Round() mit einer negativen Zahl nennen, könnte man überraschendes Ergebnis:

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 

Dies liegt daran, -as sagte earlier- Umwandlung des Integer-Teil hält, und zum Beispiel ganzzahlige Teil von -1.6 ist -1 (das ist größer als -1.6; während der ganze Teil von 1.6 ist 1, die weniger als 1.6 ist).

Wenn Sie -0.363636 wollen -0.35 werden statt -0.30, dann bei negativen Zahlen addieren -0.5 statt 0.5 innerhalb der Round() Funktion. Sehen Sie unsere verbesserte Round2() Funktion:

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 
} 

und dessen Verwendung:

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 

EDIT:

Um Ihren Kommentar Adresse: weil Sie nicht "wie" die nicht -exact 0.35000000000000003, schlug vor, es zu formatieren und es erneut zu analysieren:

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

Und diese „scheinbar“ Ergebnisse in dem genauen Ergebnis, da es Druck geben 0.35 genau.

Aber das ist nur eine "Illusion". Da 0.35 nicht mit endlichen Bits mit IEEE-754-Standard dargestellt werden, egal, was Sie mit der Nummer tun, wenn Sie es in einem Wert vom Typ speichern float64, wird es nicht genau seine 0.35 (aber eine IEEE-754-Nummer sehr nah dran zu sein). Was Sie sehen, ist fmt.Println() Drucken es als 0.35, weil fmt.Println() bereits einige Rundungen macht.

Aber wenn man es mit einer höheren Genauigkeit zu drucken versuchen:

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

Ausgang: es ist nicht besser (vielleicht sogar hässlicher sein): versuchen Sie es auf dem Go Playground:

0.349999999999999977795539507497 
3.250000000000000000000000000000 
0.500000000000000000000000000000 

Beachten Sie, dass auf die andere Hand 3.25 und 0.5 sind genau, weil sie mit endlichen Bits genau dargestellt werden können, da in binär dargestellt:

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

Was ist die Lektion? Es lohnt sich nicht formatieren und neu Parsen das Ergebnis, da es nicht genau sein wird, entweder (nur ein anderer float64 Wert, der fmt.Println() Formatierung auf Regeln auf Standard -nach könnte in Druck schöner sein). Wenn Sie schön gedruckte Format wollen, formatieren Sie nur mit Präzision, wie:

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 
} 

Und es wird genau (es versucht, auf den Go Playground) sein:

0.350 
3.250 
0.500 

Oder sich nur durch 100 multiplizieren und die Arbeit mit ganze Zahlen, so dass keine Darstellung oder Rundungsfehler auftreten können.

+5

Nichts als pures Genie. Danke icza. – Acidic

+0

@Acidic Bitte beachten Sie Edit hinsichtlich der Anwendung auf negative Zahlen. – icza

+0

Ja, ich sehe Danke. Ich habe Ihre Funktion bearbeitet, um das Problem von 0.363636 = 0.35000000000000003 zu lösen, das bei https://play.golang.org/p/jxILFBYBEF für jeden Interessierten verfügbar ist. – Acidic