Go: math/bits: eine Bibliothek zum Spielen mit ganzzahligen Bits

Erstellt am 11. Jan. 2017  ·  168Kommentare  ·  Quelle: golang/go

Frühere Diskussionen unter https://github.com/golang/go/issues/17373 und https://github.com/golang/go/issues/10757.

Abstrakt

Dieser Vorschlag führt einen API-Satz für das Integer-Bit-Twiddling ein.

Hintergrund

Dieser Vorschlag führt einen API-Satz für das Integer-Bit-Twiddling ein. Für diesen Vorschlag interessieren uns folgende Funktionen:

  • ctz - zählt nachfolgende Nullen.
  • clz - führende Nullen zählen; log_2.
  • popcnt - Bevölkerung zählen; Hamming-Distanz; ganzzahlige Parität.
  • bswap - kehrt die Reihenfolge der Bytes um.

Diese Funktionen wurden durch Umfragen ausgewählt:

Wir haben uns auf diese vier Funktionen beschränkt, weil andere Twiddles
Tricks sind mit der vorgeschlagenen Bibliothek sehr einfach zu implementieren,
oder bereits verfügbare Go-Konstrukte.

Wir haben Implementierungen für eine Teilmenge der ausgewählten Twiddle-Funktionen gefunden
in vielen Paketen inklusive Runtime, Compiler und Tools:

| Paket | clz | ctz | popcnt | bswap |
| --- | --- | --- | --- | --- |
| Mathe/Groß | X | X | | |
| Laufzeit/intern/sys | X | X | | X |
| Werkzeuge/Container/Intsets | X | | X | |
| cmd/kompilieren/intern/ssa | X | | X | |
| code.google.com/p/intmath | X | X | | |
| github.com/hideo55/go-popcount | | | X (asm) | |
| github.com/RoaringBitmap/roaring | | X | X (asm) | |
| github.com/tHinqa/bitset | X | X | X | |
| github.com/willf/bitset | X | | X (asm) | |
| gopl.io/ch2/popcount | | | X | |
| GCC-Integrationen | X | X | X | X |

Viele andere Pakete implementieren eine Teilmenge dieser Funktionen:

Auch Hardwareanbieter haben die Bedeutung erkannt
solcher Funktionen und inklusive Unterstützung auf Maschinenebene.
Ohne Hardwareunterstützung sind diese Operationen sehr teuer.

| Bogen | clz | ctz | popcnt | bswap |
| --- | --- | --- | --- | --- |
| AMD64 | X | X | X | X |
| ARM | X | X | ? | X|
| ARM64 | X | X | ? | X|
| S390X | X | X | ? | X|

Alle Bittwiddling-Funktionen, außer popcnt, sind bereits von runtime/internal/sys implementiert und erhalten spezielle Unterstützung vom Compiler, um "zur bestmöglichen Performance zu verhelfen". Allerdings beschränkt sich die Compiler-Unterstützung auf das Laufzeitpaket und andere Golang-Anwender müssen die langsamere Variante dieser Funktionen neu implementieren.

Vorschlag

Wir führen eine neue std-Bibliothek math/bits mit der folgenden externen API ein, um Compiler-/Hardware-optimierte Implementierungen von clz-, ctz-, popcnt- und bswap-Funktionen bereitzustellen.

package bits

// SwapBytes16 reverses the order of bytes in a 16-bit integer.
func SwapBytes16(uint16) uint16
// SwapBytes32 reverses the order of bytes in a 32-bit integer.
func SwapBytes32(uint32) uint32
// SwapBytes64 reverses the order of bytes in a 64-bit integer.
func SwapBytes64(uint64) uint64

// TrailingZeros32 counts the number of trailing zeros in a 32-bit integer, and if all are zero, then 32.
func TrailingZeros32(uint32) uint
// TrailingZeros64 counts the number of trailing zeros in a 64-bit integer, and if all are zero, then 64.
func TrailingZeros64(uint64) uint

// LeadingZeros32 counts the number of trailing zeros in a 32-bit integer, and if all are zero, then 32.
func LeadingZeros32(uint32) uint
// LeadingZeros64 counts the number of trailing zeros in a 64-bit integer, and if all are zero, then 64.
func LeadingZeros64(uint64) uint

// Ones32 counts the number of bits set in a 32-bit integer.
func Ones32(uint32) uint
// Ones64 counts the number of bits set in a 64-bit integer.
func Ones64(uint64) uint

Begründung

Alternativen zu diesem Vorschlag sind:

  • Bit-Twiddling-Funktionen sind in einer externen Bibliothek implementiert, die vom Compiler nicht unterstützt wird. Dieser Ansatz funktioniert und ist der aktuelle Stand der Dinge. Runtime verwendet die vom Compiler unterstützten Methoden, während Golang-Benutzer weiterhin die langsameren Implementierungen verwenden.
  • Die externe Bibliothek wird vom Compiler unterstützt. Da wir davon ausgehen, dass diese Bibliothek runtime/internal/sys ersetzt, bedeutet dies, dass diese Bibliothek mit dem Compiler gesperrt und in der Standardbibliothek leben muss.

Kompatibilität

Dieser Vorschlag ändert oder unterbricht keine vorhandene stdlib-API und entspricht kompatiblen Richtlinien.

Implementierung

SwapBytes, TrailingZeros und LeadingZeros sind bereits implementiert. Die einzige fehlende Funktion ist Ones, die ähnlich wie die anderen Funktionen implementiert werden kann. Wird dieser Vorschlag angenommen, kann er rechtzeitig für Go1.9 umgesetzt werden.

Offene Probleme (falls zutreffend)

Namen sind hart, Fahrradschuppen steht in den Kommentaren.

Bitte schlagen Sie zusätzliche Funktionen vor, die in die Kommentare aufgenommen werden sollen. Geben Sie idealerweise an, wo eine solche Funktion in stdlib (zB math/big), Tools oder beliebten Paketen verwendet wird.

Bisher wurden folgende Funktionen vorgeschlagen und in Erwägung gezogen:

  • Links drehen / Rechts drehen

    • Vorteile: &63 ist nicht mehr erforderlich, um eine Rotation mit einem nicht-const-Argument zu einer einzigen Anweisung auf x86, x86-64 zu kompilieren.

    • Nachteile: sehr kurz/einfach zu implementieren und inline.

    • Verwendet: crypto/ verwendet die Konstante Rotate, die vom Compiler richtig gehandhabt wird.

  • ReverseBits

    • Vorteile: ?

    • Nachteile: ?

    • Gebraucht: ?

  • Add/Sub mit Carry Return

    • Positiv: Teuer sonst

    • Nachteile: ?

    • Verwendet: Mathe/Groß

Geschichte

14.Jan: Die Ausgabe von TrailingZeros und LeadingZeros wurde klargestellt, wenn das Argument 0 ist.
14.Jan: Methoden umbenannt: CountTrailingZeros -> TrailingZeros, CountLeadingZeros -> LeadingZeros, CountOnes -> Ones.
13.Jan: Architekturname korrigiert.
11.Jan: Erster Vorschlag für die Öffentlichkeit zugänglich gemacht.

FrozenDueToAge Proposal-Accepted

Hilfreichster Kommentar

Leute, die Code schreiben, möchten nach links oder nach rechts rotieren. Sie wollen sich nicht nach links oder rechts drehen, je nachdem. Wenn wir nur rotieren-links bereitstellen, muss jeder, der nach rechts rotieren möchte, einen Ausdruck schreiben, um seine rechte Rotation in eine linke Rotation umzuwandeln. Dies ist die Art von Ausdrücken, die Computer trivial machen können, aber Programmierer werden Fehler machen. Warum Programmierer dazu bringen, es selbst zu schreiben?

Alle 168 Kommentare

@brtzsnr vielleicht sollten Sie dieses Dokument an den Vorschlag Repo einreichen , wie in den skizzierten Vorschlag Prozessschritte ?

Da es bereits Markdown nach der Vorlage ist, sollte es einfach sein, in eine CL zu kopieren und einzufügen, um eine Datei design/18616-bit-twiddleing.md (oder was auch immer) zu erstellen.

@cespare von https://github.com/golang/proposal "Wenn der Autor ein Designdokument schreiben möchte, kann er eines schreiben". Es begann als Designdokumentation, wenn ich das starke Gefühl habe, das einzureichen, bin ich völlig in Ordnung.

Ich wäre damit in Ordnung, es ist eine übliche Funktionalität, die in vielen algorithmischen Bibliotheken verwendet wird, und Mathematik / Bits scheinen ein geeigneter Ort zu sein.

(Zum einen implementiert math/big auch nlz (== clz).)

Es gibt wahrscheinlich einige Fahrradschuppen wegen der Namen. Ich für meinen Teil würde es vorziehen, dass die Funktionen sagen, was sie zurückgeben, anstatt was sie tun; was wiederum zu kürzeren Namen führen kann. Zum Beispiel:

bits.TrailingZeros64(x) statt bits.CountTrailingZeros64(x)

und so weiter.

Der Vorschlag scheint ziemlich klar und minimalistisch zu sein - ein Designdokument scheint übertrieben. Ich denke, ein CL wäre an dieser Stelle angebrachter.

(Das ist eine CL mit API und grundlegender Implementierung - zu Diskussionszwecken anstelle eines Designdokuments. Wir müssen noch entscheiden, ob dieser Vorschlag angenommen werden soll oder nicht.)

@brtzsnr hat das Problembeschreibung und folgt der Vorlage . Ich nahm an, dass es einen gewissen Wert hat, diese Dokumente alle an einem Ort zu haben.

Der letzte in der Hardware-Support-Tabelle aufgeführte Bogen ist "BSWAP" - Tippfehler?

Danke, dass du das aufgeschrieben hast.

Der doc-String für ctz und clz sollte das Ergebnis angeben, wenn 0 übergeben wird.

Ich bevorzuge auch (zB) TrailingZeros32 gegenüber CountTrailingZeros32. Ich würde mich auch über Ctz32 freuen. Es ist prägnant, den meisten vertraut und für den Rest leicht zu googlen.

Danke für den Vorschlag.
Ich möchte nur hinzufügen, dass wir uns wahrscheinlich auch auf die Verwendung konzentrieren möchten, anstatt sich nur auf die Anweisungen zu konzentrieren. Zum Beispiel hat @dr2chase gefunden, dass es mehrere log2 Funktionen im Compiler/Assembler gibt. Es ist in der Nähe von CLZ, aber nicht dasselbe. Funktionen wie diese sollten wohl auch im Bit-Twiddler-Paket enthalten sein. Und vielleicht auch nützliche Funktionen, die nichts mit dieser Anleitung zu tun haben.

Wie wäre es, wenn wir ein Paket für alle Bit Twddling-Primitive bereitstellen, die von . definiert sind?
das Hacker's Delight?

Bei der Gestaltung des Pakets müssen wir nicht berücksichtigen, ob die Funktion
kann intrinsisch sein oder nicht. Die Optimierung kann später erfolgen. Das ist,
Lassen Sie die Low-Level-Implementierung nicht das Upper-Level-Paket kontrollieren
Schnittstelle. Wir wollen eine gute Paketschnittstelle, auch wenn manche das nicht können
einer einzelnen Anweisung zugeordnet werden.

@minux , zum Glück ist jede kleine Drehfunktion, die ich bisher benötigt habe, genau die, die in diesem Vorschlag enthalten sind.

Hacker's Delight zu folgen hat den Vorteil, dass wir keine Zeit damit verschwenden müssen, über die Namen zu streiten.

Ich möchte folgendes hinzufügen:

ReverseBits (für uint32 und uint64)
RotateLeft/Right (kann inline mit zwei Shifts erweitert werden, aber der Compiler
kann die Transformation aufgrund von Problemen mit dem Schaltbereich nicht immer durchführen)

Vielleicht auch zwei Ergebnisformen von Addieren und Subtrahieren? Z.B

func AddUint32(x, y, carryin uint32) (carryout, sum uint32) // Carryin muss
0 oder 1 sein.
Und ähnlich für uint64.

Und SqrtInt.

Viele meiner Vorschläge lassen sich nicht in einer einzigen Anleitung umsetzen, aber
das ist der punkt: wir wollen ein gutes bastelpaket, nicht nur
intrisics-Paket. Lassen Sie nicht zu, dass das High-Level-Paket der Hardware einschränkt
Schnittstelle.

Verwandte Funktionen, um zu überprüfen, ob ein Addieren/Multiplizieren überläuft.

Ein verwandter Datenpunkt: Es gibt verschiedene Probleme, die für eine schnellere cgo eingereicht wurden. In einem solchen Beispiel (Vorschlag #16051) ist die Tatsache, dass die schnelle Implementierung von bsr/ctz/etc. passieren könnte, wurde erwähnt, dass hoffentlich die Reihe von Anwendungsfällen wegfällt, in denen Leute, die go schreiben, versucht sind, cgo zu verwenden.

@aclements kommentierte am 1. Juli 2016:

@eloff , es gab einige Diskussionen (wenn auch keine konkreten Vorschläge meines Wissens), um Funktionen für Dinge wie popcount und bsr hinzuzufügen, die als intrinsisch kompiliert würden, wo sie unterstützt werden (wie heute math.Sqrt). Mit 1.7 probieren wir dies in der Runtime aus, die jetzt auf amd64 SSA über einen intrinsischen ctz verfügt. Offensichtlich löst dies nicht das Gesamtproblem, aber es würde einen weiteren Grund vernichten, cgo in einem Kontext mit geringem Overhead zu verwenden.

Viele Leute (mich eingeschlossen) fühlen sich wegen der Aufführung dazu angezogen, dorthin zu gehen, also würden Dinge wie dieser aktuelle Vorschlag für ein bisschen Twiddling helfen. (Und ja, cgo ist jetzt auch in 1.8 schneller, was auch schön ist).

RotateLeft/Right (kann inline mit zwei Shifts erweitert werden, aber der Compiler
kann die Transformation aufgrund von Problemen mit dem Schaltbereich nicht immer durchführen)

@minux Können Sie erläutern, was das Problem ist?

@brtzsnr : Ich denke, was Minux meint, ist, dass Sie beim Schreiben von (x << k) | (x >> (64-k)) wissen, dass Sie 0 <= k < 64 , aber der Compiler kann Ihre Gedanken nicht lesen, und das ist nicht offensichtlich aus dem Code ableitbar. Wenn wir die Funktion hätten

func leftRot(x uint64, k uint) uint64 {
   k &= 63
   return (x << k) | (x >> (64-k))
}

Dann können wir (über &63) sicherstellen, dass der Compiler weiß, dass der Bereich von k begrenzt ist.
Wenn der Compiler also nicht beweisen kann, dass die Eingabe beschränkt ist, brauchen wir ein zusätzliches UND. Das ist besser, als die Rotationsbaugruppe überhaupt nicht zu generieren.

Am Freitag, 13. Januar 2017 um 22:37 Uhr, Keith Randall [email protected]
schrieb:

@brtzsnr https://github.com/brtzsnr : Ich denke, was Minux meint
zu ist, wenn Sie schreiben (x << k) | (x >> (64-k)), wissen Sie, dass Sie 0 . verwenden
<= k < 64, aber der Compiler kann deine Gedanken nicht lesen, und das ist es nicht offensichtlich
aus dem Code ableitbar. Wenn wir die Funktion hätten

func leftRot(x uint64, k uint) uint64 {
k &= 63
zurück (x << k) | (x >> (64-k))
}

Dann können wir (über das &63) sicherstellen, dass der Compiler weiß, dass der Bereich
von k ist beschränkt.
Wenn der Compiler also nicht beweisen kann, dass die Eingabe beschränkt ist, brauchen wir ein Extra
UND. Das ist besser, als die Rotationsbaugruppe überhaupt nicht zu generieren.

Rechts. Wenn wir die Funktionen RotateLeft und RotateRight definieren, können wir formal
Definiere die Funktion links/rechts drehen k Bits (egal was k ist). Das ist
ähnlich wie unser Schichtbetrieb definiert ist. Und diese Definition auch
Maps zu den tatsächlichen Rotationsanweisungen gut (im Gegensatz zu Verschiebungen, bei denen unsere mehr
intuitive Definition erfordert einen Vergleich auf bestimmten Architekturen).

Wie wäre es mit Byte- und Bit-Shuffling- (und Unshuffling-) Funktionen, die von der blosc- Komprimierungsbibliothek verwendet werden? Die Folien (das Mischen beginnt bei Folie 17). Diese Funktionen können SSE2/AVX2 beschleunigt werden.

Am Freitag, den 13. Januar 2017 um 23:24 Uhr schrieb opennota [email protected] :

Wie wäre es mit Byte- und Bit-Shuffling-Funktionen, die vom blosc . verwendet werden?
https://github.com/Blosc/c-blosc Komprimierungsbibliothek? Die Folien
http://www.slideshare.net/PyData/blosc-py-data-2014 (das Mischen
beginnt mit Folie 17). Diese Funktionen können SSE2/AVX2 beschleunigt werden.

SIMD ist ein größeres Problem und liegt außerhalb des Umfangs dieses Pakets. Es ist

17373.

Die derzeit vorgeschlagenen Funktionen haben eine native Go-Implementierung, die viel größer und unverhältnismäßig teurer ist als das Optimum. Auf der anderen Seite ist rotate einfach inline so zu schreiben, dass der Compiler es erkennt.

@minux und auch alle anderen: Wissen Sie, wo

Rotate ist einfach inline so zu schreiben, dass der Compiler es erkennt.

Es ist einfach für diejenigen, die mit den Interna des Compilers vertraut sind. Wenn Sie es in ein Mathe / Bits-Paket packen, ist es für jeden einfach.

Wissen Sie, wo links/rechts drehen mit einer nicht konstanten Anzahl von gedrehten Bits verwendet wird?

Hier ist ein Beispiel aus #9337:

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

Bei jedem Aufruf ist es jedes Mal eine konstante Anzahl von rotierten Bits, aber die Funktion selbst ist derzeit nicht inline eingebunden, sodass sie ohne Rotationsbefehle kompiliert wird. Eine Math/Bits-Bibliotheksfunktion würde hier definitiv helfen.

Am Samstag, 14. Januar 2017 um 5:05 Uhr, Alexandru Moșoi [email protected]
schrieb:

Die derzeit vorgeschlagenen Funktionen haben eine viel größere Go-native Implementierung
und unverhältnismäßig teurer als optimal. Auf der anderen Seite drehen
ist einfach inline so zu schreiben, dass der Compiler es erkennt.

Wie ich in dieser Ausgabe schon oft betont habe, ist dies nicht der richtige Weg
Entwerfen Sie ein Go-Paket. Es bindet zu sehr an die zugrunde liegende Hardware. Was wir
want ist ein ziemlich nützliches Paket, das im Allgemeinen nützlich ist. Ob
Funktionen können zu einer einzigen Anweisung erweitert werden ist irrelevant solange
da die API-Schnittstelle bekannt und allgemein nützlich ist.

@minux https://github.com/minux und auch alle anderen: Weißt du schon
wo links/rechts drehen mit einer nicht konstanten Anzahl von gedrehten Bits verwendet wird?
crypto/sha256 verwendet zum Beispiel Rotate, jedoch mit konstanter Anzahl von Bits.

Auch wenn im eigentlichen Problem die Anzahl der Rotationsbits konstant ist, ist die
Compiler kann das nicht sehen. ZB wenn die Schichtzahl gespeichert wird
in einem Array, oder verstecken Sie sich in einem Schleifenzähler oder sogar einem Aufrufer eines nicht inlined
Funktion.

Ein einfaches Beispiel für die Verwendung einer variablen Anzahl von Drehungen ist ein interessantes
Implementierung von Popcount:
// https://play.golang.org/p/ctNRXsBt0z

func RotateRight(x, k uint32) uint32
func Popcount(x uint32) int {
    var v uint32
    for i := v - v; i < 32; i++ {
        v += RotateRight(x, i)
    }
    return int(-int32(v))
}

@josharian Das Beispiel sieht nach einer schlechten Inliner-Entscheidung aus, wenn rot nicht inline ist. Haben Sie versucht, die Funktion als func rot(x, n) statt als rot = func(x, n) zu schreiben?

@minux : da stimme ich dir zu. Ich versuche nicht, die API an einen bestimmten Befehlssatz zu binden; die Hardwareunterstützung ist ein netter Bonus. Mein Hauptaugenmerk liegt darauf, Verwendungen im echten Code (kein Spielzeugcode) zu finden, um den Kontext zu verstehen, was die beste Signatur ist und wie wichtig es ist, die Lieblingsfunktion aller bereitzustellen. Kompatibilitätsversprechen werden uns später beißen, wenn wir das jetzt nicht richtig machen.

Beispiel: Wie sollte die Signatur von add mit Carry Return lauten? Add(x, y uint64) (c, s uint64) ? Wenn wir uns math/big ansehen, brauchen wir wahrscheinlich auch Add(x, y uintptr) (c, s uintptr) .

Das Beispiel sieht nach einer schlechten Inliner-Entscheidung aus, wenn rot nicht inline ist.

Jawohl. Es ist Teil eines Fehlers, der sich über Inlining beschwert. :)

Haben Sie versucht, die Funktion als func rot(x, n) statt als rot = func(x, n) zu schreiben?

Es ist nicht mein Code - und das ist ein Teil des Punktes. Und trotzdem ist es vernünftiger Code.

Es wäre schön zu garantieren (in der Paketdokumentation?), dass die Rotations- und Byte-Swap-Funktionen zeitkonstante Operationen sind, damit sie in Kryptoalgorithmen sicher verwendet werden können. Eventuell auch für andere Funktionen etwas zu bedenken.

Am Donnerstag, 19. Januar 2017 um 11:50 Uhr, Michael Munday [email protected]
schrieb:

Es wäre schön zu garantieren (in der Paketdokumentation?), dass die
Rotations- und Byte-Swap-Funktionen sind zeitkonstante Operationen, so dass sie
kann sicher in Kryptoalgorithmen verwendet werden. Vielleicht etwas zum Nachdenken
auch für andere Funktionen.

triviale Implementierung von Byte-Swap ist konstante Zeit, aber wenn der Basiswert
Architektur bietet keine variablen Schaltbefehle, es wird schwierig
um eine konstante Time-Rotation-Implementierung zu gewährleisten. Vielleicht wird Go niemals laufen
jedoch auf diesen Architekturen.

Allerdings besteht auch eine nicht zu vernachlässigende Chance, dass der Basiswert
Mikroarchitektur verwendet einen Multi-Cycle-Shifter, und wir können nicht garantieren
konstante Zeitrotation bei diesen Implementierungen.

Wenn eine strikt konstante Zeit erforderlich ist, ist möglicherweise die einzige Möglichkeit das Schreiben der Assemblierung
(und selbst in diesem Fall wird davon ausgegangen, dass alle verwendeten
Anweisungen sind selbst konstante Zeit, die implizit von der
Mikroarchitektur.)

Ich verstehe zwar die Notwendigkeit solcher Garantien, aber es geht tatsächlich darüber hinaus
unsere Kontrolle.

Ich bin geneigt, @minux zuzustimmen. Wenn Sie Krypto-Primitive mit konstanter Zeit wünschen, sollten sie in Krypto/Subtil leben. crypto/subtle kann auf Plattformen, auf denen diese Implementierungen verifiziert wurden, leicht auf Mathe/Bits umgeleitet werden. Sie können etwas anderes tun, wenn eine langsamere, aber zeitkonstante Implementierung erforderlich ist.

Dies scheint es wert zu sein. Gehen für @randall77 zum Tierarzt. Der nächste Schritt scheint wie ein Design-Dokument, das an der üblichen Stelle eingecheckt wurde (ich sehe die Skizze oben).

Da dieser Vorschlag nun fortgeführt werden kann, sollten wir uns auf die Details der API einigen. Fragen, die ich im Moment sehe:

  • welche Funktionen sollen enthalten sein?
  • welche Typen behandelt werden sollen ( nur uint64 , uint32 oder uint64 , uint32 , uint16 , uint8 oder uintptr )?
  • Signaturdetails (z. B. TrailingZeroesxx(x uintxx) uint , mit xx = 64, 32 usw., vs. TrailingZeroes(x uint64, size uint) uint wobei die Größe 64, 32 usw. ist - letzteres würde zu einer kleineren API führen und kann noch schnell genug sein je nach Umsetzung)
  • einige Namensfahrradabfälle (ich mag die "Hacker's Delight"-Terminologie, aber es könnte angemessener sein, sie zu buchstabieren)
  • Gibt es einen besseren Ort als Mathematik/Bits für dieses Paket.

@brtzsnr Möchten Sie diese Bemühungen weiterhin

Ich persönlich bevorzuge die ausgeschriebenen xx-Namen: bits.TrailingZeroes(uint64(x), 32) ist umständlicher und weniger lesbar als bits.TrailingZeroes32(x), imo, und dieses Namensschema würde mit der Plattformvariablen-Größe funktionieren uintptr einfacher (xx = Ptr?).

Es würde es auch deutlicher machen, wenn Sie beispielsweise uint8 nicht in die erste Version aufnehmen, sondern später hinzufügen würden – plötzlich gäbe es viele neue xx = 8-Funktionen anstelle einer Reihe aktualisierter Doc-Kommentare, die 8 hinzufügen zur Liste der gültigen Größen mit einem leicht zu übersehenden Hinweis in den Release-Dokumenten.

Wenn die ausgeschriebenen Namen verwendet werden, sollten die Hacker-Enthusiasten-Begriffe meiner Meinung nach als "auch bekannt als" in die Beschreibungen aufgenommen werden, damit Suchen (in der Seite oder über die Suchmaschine) den kanonischen Namen leicht finden, wenn Sie nach der falschen Schreibweise suchen .

Ich denke, Mathe/Bits ist ein guter Ort – es ist Mathe mit Bits.

Ich sollte beachten, dass SwapBytes{16,32,64} mit Funktionen in sync/atomic konsistenter ist als SwapBytes(..., bitSize uint) .

Obwohl das strconv -Paket das ParseInt(..., bitSize int) -Muster verwendet, gibt es mehr Beziehungen zwischen bits und atomic als bei strconv .

Drei Gründe, warum ich SwapBytes nicht mag (uint64, size uint):

  1. Benutzer können es in Fällen verwenden, in denen die Größe keine Kompilierzeitkonstante ist (at
    zumindest zum aktuellen Compiler),
  2. es erfordert viele Typkonvertierungen. Konvertieren Sie zuerst ein uint32 in
    uint64, und dann zurück zu konvertieren.
  3. wir sollten den generischen Namen SwapBytes für ein zukünftiges Generikum reservieren
    Version der Funktion (wenn Go Generika-Unterstützung erhält).

@griesemer Robert, bitte übernehme den Vorschlag. Ich werde in einem Monat Zeit haben, diesen Vorschlag voranzutreiben, aber bis dahin sollten die Fortschritte nicht ins Stocken geraten.

Es sieht so aus, als ob es eine klare Präferenz für Signaturen des Formulars gibt:

func fffNN(x uintNN) uint

was offen lässt, welches NN unterstützt werden soll. Konzentrieren wir uns für die weitere Diskussion auf NN=64. Die restlichen Typen können nach Bedarf (inkl. uintptr) einfach hinzugefügt werden.

Was uns direkt zum ursprünglichen Vorschlag von @brtzsnr und den folgenden Funktionen (und ihren jeweiligen Variationen für verschiedene Typen) sowie zu den Vorschlägen zwischenzeitlich führt:

// LeadingZeros64 returns the number of leading zero bits in x.
// The result is 64 if x == 0.
func LeadingZeros64(x uint64) uint

// TrailingZeros64 returns the number of trailing zero bits in x.
// The result is 64 if x == 0.
func TrailingZeros64(x uint64) uint

// Ones64 returns the number of bits set in x.
func Ones64(x uint64) uint

// RotateLeft64 returns the value of x rotated left by n%64 bits.
func RotateLeft64(x uint64, n uint) uint64

// RotateRight64 returns the value of x rotated right by n%64 bits.
func RotateRight64(x uint64, n uint) uint64

Wir können auch eine Reihe von Swap-Funktionen haben. Persönlich bin ich skeptisch gegenüber diesen: 1) Ich habe noch nie ein SwapBits benötigt, und die meisten Verwendungen von SwapBytes sind auf Code zurückzuführen, der Endian-bewusst ist, obwohl dies nicht der Fall sein sollte. Kommentare?

// SwapBits64 reverses the order of the bits in x.
func SwapBits64(x uint64) uint64

// SwapBytes64 reverses the order of the bytes in x.
func SwapBytes64(x uint64) uint64

Dann kann es eine Menge von ganzzahligen Operationen geben. Sie wären nur in diesem Paket enthalten, weil einige von ihnen (zB Log2) möglicherweise in der Nähe anderer Funktionen in diesem Paket sind. Diese Funktionen könnten woanders sein. Eventuell muss der Paketname bits angepasst werden. Kommentare?

// Log2 returns the integer binary logarithm of x.
// The result is the integer n for which 2^n <= x < 2^(n+1).
// If x == 0, the result is -1.
func Log2(x uint64) int

// Sqrt returns the integer square root of x.
// The result is the value n such that n^2 <= x < (n+1)^2.
func Sqrt(x uint64) uint64

Schließlich schlug @minux Operationen wie AddUint32 usw. vor. Ich werde diese vorerst weglassen , weil ich denke, dass es schwieriger ist, sie richtig anzugeben (und es mehr Vielfalt gibt). Wir können später auf sie zurückkommen. Lassen Sie uns zu einem gewissen Konsens über das oben Gesagte kommen.

Fragen:

  • Irgendwelche Meinungen zu Funktionsnamen?
  • Irgendwelche Meinungen zu den Integer-Ops?
  • Irgendwelche Meinungen zum Paketnamen/Speicherort?

Ich würde lange Funktionsnamen bevorzugen. Go vermeidet Abkürzungen in Funktionsnamen. Die Kommentare können ihre Spitznamen ("nlz", "ntz", usw.) für Leute sagen, die das Paket auf diese Weise durchsuchen.

@griesemer , du

Ich verwende SwapBits in Komprimierungsanwendungen. Bieten die Hauptarchitekturen jedoch die Möglichkeit, Bitumkehr effizient durchzuführen? Ich hatte vor, nur in meinem eigenen Code zu tun:

v := bits.SwapBytes64(v)
v = (v&0xaaaaaaaaaaaaaaaa)>>1 | (v&0x5555555555555555)<<1
v = (v&0xcccccccccccccccc)>>2 | (v&0x3333333333333333)<<2
v = (v&0xf0f0f0f0f0f0f0f0)>>4 | (v&0x0f0f0f0f0f0f0f0f)<<4

@bradfitz , @dsnet : Aktualisierte Signaturen (

Sofern es keine native Unterstützung für Bit-Swapping gibt, würde ich dafür stimmen, SwapBits . Es kann allein schon vom Namen her ein wenig mehrdeutig sein, ob Bits nur innerhalb jedes Bytes oder über das gesamte uint64 getauscht werden.

Warum etwas ausschließen, das ausschließlich auf der Verfügbarkeit von Anweisungen basiert? Umkehrbit
hat bemerkenswerte Verwendungen in FFT.

Um Ihre Frage zur Verfügbarkeit von Reverse-Bit-Befehlen zu beantworten: arm has
die RBIT-Anweisung.

@griesemer Soweit die

func Ones64(x uint64) uint
func RotateLeft64(x uint64, n uint) uint64

Nimmt RotateLeftN usw. uints, weil dies für die einfache Intrinisierung (wenn möglich) erforderlich ist, oder nur weil dies die Domäne ist? Wenn kein starker technischer Bedarf besteht, gibt es einen stärkeren Präzedenzfall für Ints, auch wenn negative Werte keinen Sinn ergeben. Wenn es einen starken technischen Bedarf gibt, sollten sie uintN für das entsprechende N für Regularität sein?

Unabhängig davon sollten OnesN und ähnliches ints zurückgeben, es sei denn, diese Operationen können mit anderen Bitoperationen zusammengesetzt werden. In diesem Fall sollten sie ein uintN für das entsprechende N zurückgeben.

@jimmyfrasche Weil es die Domäne ist. Der CPU ist das egal. Ob etwas ein int oder ein uint ist, ist für die meisten Operationen außer Vergleichen irrelevant. Das heißt, wenn wir es zu einem Int machen, müssen wir erklären, dass es niemals negativ ist (Ergebnis) oder dass es nicht negativ sein darf (Argument) oder angeben, was es tun soll, wenn es negativ ist (Argument für Rotation). In diesem Fall könnten wir vielleicht mit nur einem Rotate davonkommen, was schön wäre.

Ich bin nicht überzeugt, dass die Verwendung von int die Dinge einfacher macht. Bei richtiger Gestaltung fließen uints in uints (das ist in math/big der Fall) und es sind keine Konvertierungen erforderlich. Aber selbst wenn eine Konvertierung erforderlich ist, wird sie in Bezug auf die CPU-Kosten kostenlos sein (wenn auch nicht in Bezug auf die Lesbarkeit).

Aber ansonsten freue ich mich, überzeugende Argumente zu hören/sehen.

Die Anzahl der Rotationsbits sollte uint (ohne bestimmte Größe) sein, um der
Shift-Operator der Sprache.

Der Grund für die Nichtverwendung von int ist, dass es Mehrdeutigkeiten geben wird, ob
RotateRight -2 Bit entspricht RotateLeft 2 Bit.

@minux Es gibt keine Mehrdeutigkeit, wenn Rotate(x, n) definiert ist, um x um n mod 64 Bits zu drehen: Nach links drehen um 10 Bit ist dasselbe wie nach rechts drehen um 64-10 = 54 Bit, oder (wenn wir int-Argumente zulassen) um -10 Bits (vorausgesetzt, ein positiver Wert bedeutet nach links drehen). -10 mod 64 = 54. Möglicherweise erhalten wir den Effekt sogar mit einem CPU-Befehl kostenlos. Zum Beispiel betrachten die 32-Bit-x86-ROL/ROR-Befehle bereits nur die unteren 5 Bits des CL-Registers und führen effektiv Mod 32 für 32-Bit-Rotationen durch.

Das einzige wirkliche Argument ist die Regelmäßigkeit mit dem Rest der stdlib. Es gibt zahlreiche Stellen, an denen uints viel Sinn machen, aber stattdessen ints verwendet werden.

Da Mathe / Big eine bemerkenswerte Ausnahme ist, habe ich kein Problem damit, dass dies eine andere ist, aber es ist etwas zu berücksichtigen.

Ich gehe davon aus, dass debuggt , und nicht Mehrdeutigkeit in der Implementierung, was ein überzeugendes Argument dafür ist, dass es uint nimmt.

Soll bits.SwapBits wirklich mit einem bits-Suffix benannt werden, wenn der Name des Pakets bereits bits heißt? Wie wäre es mit bits.Swap?

Eine andere Idee ist bits.SwapN(v uint64, n int) anstelle von bits.SwapBytes, wobei n die Anzahl der Bits darstellt, nach denen für den Swap gruppiert werden soll. Bei n=1 werden die Bits vertauscht, bei n=8 werden die Bytes vertauscht. Ein Vorteil ist, dass bits.SwapBytes nicht mehr existieren muss, obwohl ich nie eine Notwendigkeit für einen anderen Wert von n gesehen habe, vielleicht werden andere anderer Meinung sein.

Wie oben angegeben , LeadingZeros64 , TrailingZeros64 , Ones64 alle LGTM.

Ein einzelnes Rotate64 mit einem vorzeichenbehafteten Rotationsbetrag ist ansprechend. Die meisten Rotationen erfolgen ebenfalls um einen konstanten Betrag, so dass (wenn / wenn dies zu einem intrinsischen wird) keine Laufzeitkosten entstehen, wenn keine separaten Rechts/Links-Funktionen vorhanden sind.

Ich habe keine eindeutige Meinung zu Bit/Byte-Shuffling/Swapping/Reversing. Für Umkehrbits scheint bits.Reverse64 ein besserer Name zu sein. Und vielleicht bits.ReverseBytes64 ? Ich finde Swap64 etwas unklar. Ich sehe den Reiz darin, Swap64 eine Gruppengröße nehmen zu lassen, aber wie verhält es sich, wenn die Gruppengröße 64 nicht gleichmäßig teilt? Wenn die einzigen Gruppengrößen, die von Bedeutung sind, 1 und 8 , wäre es einfacher, ihnen einfach separate Funktionen zuzuweisen. Ich frage mich auch, ob eine Art allgemeiner Bitfeld-Manipulation besser sein könnte, vielleicht schaue ich mir die arm64- und amd64-BMI2-Anweisungen zur Inspiration an.

Ich bin nicht überzeugt, dass ganzzahlige mathematische Funktionen in dieses Paket gehören. Sie scheinen eher in die Paketmathematik zu gehören, wo sie Bitfunktionen in ihrer Implementierung verwenden könnten. Verwandte, warum nicht func Sqrt(x uint64) uint32 (anstatt uint64 )?

Obwohl AddUint32 und Freunde kompliziert sind, hoffe ich, dass wir sie irgendwann noch einmal besuchen. Neben anderen Dingen würde MulUint64 den Zugang zu HMUL ermöglichen, den sogar der superminimalistische RISC-V ISA als Anweisung bereitstellt. Aber ja, lassen Sie uns zuerst etwas einbringen; wir können später immer noch erweitern.

bits scheint eindeutig der richtige Paketname zu sein. Ich bin versucht zu sagen, dass der Importpfad nur bits , nicht math/bits , aber ich fühle mich nicht stark.

bits scheint eindeutig der richtige Paketname zu sein. Ich bin versucht zu sagen, dass der Importpfad nur Bits sein sollte, nicht Mathe/Bits, aber ich fühle mich nicht stark.

IMO, wenn es nur bits (ohne die Paketdokumentation zu lesen), würde ich erwarten, dass es dem Paket bytes etwas ähnlich ist. Das heißt, zusätzlich zu Funktionen, die mit Bits arbeiten, würde ich erwarten, Reader und Writer zum Lesen und Schreiben von Bits in/von einem Byte-Stream zu finden. Wenn Sie es math/bits machen, wird es offensichtlicher, dass es sich nur um eine Reihe von zustandslosen Funktionen handelt.

(Ich schlage nicht vor, dass wir Reader/Writer für Bitstreams hinzufügen)

Ein einzelnes Rotate64 mit einem vorzeichenbehafteten Rotationsbetrag ist ansprechend. Die meisten Rotationen erfolgen ebenfalls um einen konstanten Betrag, so dass (wenn / wenn dies zu einem intrinsischen wird) keine Laufzeitkosten entstehen, wenn keine separaten Rechts/Links-Funktionen vorhanden sind.

Ich kann sehen, wie es praktisch sein könnte, eine etwas kleinere Paket-API zu haben, indem man Rotieren nach links / rechts zu einem einzigen Rotate kombiniert, aber dann gibt es auch noch die Frage, den Parameter n effektiv zu dokumentieren weisen darauf hin, dass:

  • negativ n führt zu einer Linksdrehung
  • null n führt zu keiner Änderung
  • positives n führt zu einer Rechtsdrehung

Für mich scheint der zusätzliche mentale und Dokumentationsaufwand die Kombination der beiden in einem einzigen Rotate nicht zu rechtfertigen. Ein explizites RotateLeft und RotateRight wobei n ein uint fühlt sich für mich intuitiver an.

@mdlayher Denken Sie daran, dass für ein n-Bit-Wort, das um k Bits nach links rotiert, dasselbe ist wie das Drehen von nk Bits nach rechts. Auch wenn Sie dies in zwei Funktionen aufteilen, müssen Sie immer noch verstehen, dass eine Drehung um k Bit nach links immer eine Drehung um (k mod n) Bit bedeutet (wenn Sie um mehr als n Bit drehen, beginnen Sie von vorne). Sobald Sie das getan haben, können Sie einfach sagen, dass die Rotationsfunktion immer um k mod n Bits rotiert und Sie sind fertig. Der negative Wert oder eine zweite Funktion müssen nicht erklärt werden. Es klappt einfach. Es ist eigentlich einfacher.

Darüber hinaus erledigt die Hardware (zB auf x86) dies sogar automatisch für Sie, selbst bei nicht konstantem k.

@griesemer , ein Nachteil von Rotate ist, dass es nicht sagt, welche Richtung positiv ist. Entspricht Rotate32(1, 1) 2 oder 0x80000000? Wenn ich beispielsweise Code lese, der dies verwendet, würde ich erwarten, dass das Ergebnis 2 ist, aber anscheinend würde @mdlayher erwarten, dass es 0x80000000 ist. Andererseits sind RotateLeft und RotateRight eindeutige Namen, unabhängig davon, ob sie ein vorzeichenbehaftetes oder vorzeichenloses Argument annehmen. (Ich stimme @minux nicht zu, dass es mehrdeutig ist, ob RotateRight von -2 gleich RotateLeft 2 ist. Diese scheinen mir offensichtlich äquivalent zu sein und ich sehe nicht, wie Sie sie sonst angeben könnten.)

@aclements , die behoben werden könnten, indem nur eine Funktion namens RotateLeft64 verwendet wird und negative Werte zum Drehen nach rechts verwendet werden. Oder mit Dokumenten. (FWIW, in Ihrem Beispiel würde ich auch 2 erwarten, nicht 0x800000000.)

@josharian , ich stimme zu, obwohl es ein bisschen seltsam erscheint, ein RotateLeft ohne auch ein RotateRight . Die Konsistenz einer vorzeichenbehafteten Drehung scheint potenziell wertvoll zu sein, wenn die Drehung berechnet wird, aber für konstante Drehungen würde ich viel lieber Code lesen, der "um zwei Bits nach rechts drehen" sagt, als "nach links drehen, indem Sie nur negative zwei Bits

Das Problem bei der Behebung dieses Problems mit Docs besteht darin, dass Docs die Lesbarkeit von Code, der die Funktion aufruft, nicht verbessern.

Leute, die Code schreiben, möchten nach links oder nach rechts rotieren. Sie wollen sich nicht nach links oder rechts drehen, je nachdem. Wenn wir nur rotieren-links bereitstellen, muss jeder, der nach rechts rotieren möchte, einen Ausdruck schreiben, um seine rechte Rotation in eine linke Rotation umzuwandeln. Dies ist die Art von Ausdrücken, die Computer trivial machen können, aber Programmierer werden Fehler machen. Warum Programmierer dazu bringen, es selbst zu schreiben?

Ich bin überzeugt.

@aclements @ianlancetaylor Punkt vergeben. (Die Implementierung von RotateLeft/Right wird jedoch wahrscheinlich nur eine Implementierung von Rotate aufrufen.)

Eine Namensidee besteht darin, den Typ in den Paketnamen und nicht in den Signaturnamen aufzunehmen. bits64.Ones statt bits.Ones64 .

@btracey , da habe ich mich ein bisschen in den Mund übergeben. :) Wir haben nicht flag64.Int oder math64.Floatfrombits oder sort64.Floats oder atomic64.StoreInt .

@bradfitz golang.org/x/image/math/f64 und golang.org/x/image/math/f32 existieren jedoch

Ich hatte nicht an atomic als Präzedenzfall gedacht (zum Glück nicht in meiner normalen Go-Nutzung). Die anderen Beispiele unterscheiden sich, da die Pakete größer sind als eine Reihe von Funktionen, die für eine Reihe verschiedener Typen wiederholt werden.

Die Dokumentation ist einfacher zu sehen (sagen wir auf godoc), wenn Sie zu bits64 und sehen
Ones LeadingZeros TrailingZeros

Anstatt zu bits und zu sehen
Ones8 Ones16 Ones32 Ones64 LeadingZeros8 ...

@iand , das ist auch ziemlich

Es gibt einen ziemlich starken Präzedenzfall in Go, den pkg.foo64-Namensstil anstelle von pkg64.foo zu verwenden.

Die Paket-API wird n-mal größer, wenn wir n Typen haben, die API-Komplexität jedoch nicht. Ein besserer Ansatz zur Lösung dieses Problems könnte die Dokumentation und die Präsentation der API über ein Tool wie go doc sein. Statt zum Beispiel:

// TrailingZeros16 returns the number of trailing zero bits in x.
// The result is 16 if x == 0.
func TrailingZeros16(x uint16) uint

// TrailingZeros32 returns the number of trailing zero bits in x.
// The result is 32 if x == 0.
func TrailingZeros32(x uint32) uint

// TrailingZeros64 returns the number of trailing zero bits in x.
// The result is 64 if x == 0.
func TrailingZeros64(x uint64) uint

was beim Betrachten der API deutlich zu einem mentalen Overhead führt, könnten wir sie wie folgt präsentieren:

// TrailingZerosN returns the number of trailing zero bits in a uintN value x for N = 16, 32, 64.
// The result is N if x == 0.
func TrailingZeros16(x uint16) uint
func TrailingZeros32(x uint32) uint
func TrailingZeros64(x uint64) uint

godoc könnte bei in diesem Sinne ähnlichen Funktionen klug sein und sie als Gruppe mit einem einzigen doc-String präsentieren. Dies wäre auch in anderen Paketen hilfreich.

Zusammenfassend denke ich, dass das vorgeschlagene Namensschema in Ordnung ist und in der Go-Tradition steht. Das Präsentationsproblem ist eine separate Frage, die an anderer Stelle erörtert werden kann.

+1 beim Einfügen der Bitgröße in den Paketnamen.

bits64.Log2 liest sich besser als bits.Log264 (ich denke, Log2 gehört dort hin - und die Paketbenennung ist kein guter Grund, es draußen zu lassen)

Am Ende ist es die gleiche API für jeden Typ, der nach Skalartyp "parametrisiert" ist. Wenn die Funktionen also typübergreifend den gleichen Namen haben, ist der Code einfacher mit separaten Paketen zu refaktorisieren - ändern Sie einfach den Importpfad (Ersetzungen ganzer Wörter sind trivial mit gofmt -r, aber benutzerdefinierte Suffixtransformationen sind umständlicher).

Ich stimme @griesmeyer zu, dass das Bitsize-Suffix des Funktionsnamens idiomatisch sein kann, aber ich denke, das lohnt sich nur, wenn das Paket nicht trivialen Code enthält, der vom Typ unabhängig ist.

Wir könnten ein Spiel aus der Kodierung/Binärdatei stehlen und Variablen namens Uint64, Uint32, ... erstellen, die Methoden haben, die die zugehörigen vordefinierten Typen akzeptieren.

bits.Uint64.RightShift
bits.Uint8.Reverse
bits.Uintptr.Log2
...

@griesemer wie würde es wissen, wie man sie gruppiert? Ein Doc-String mit dem Funktionsnamen, der in der Dokumentation auf N endet, anstelle des tatsächlichen Namens und dann ein gemeinsames Präfix unter funcs ohne Docstrings finden, das der Inkongruenz entspricht? Wie lässt sich das verallgemeinern? Es scheint ein komplizierter Sonderfall zu sein, nur sehr wenige Pakete zu bedienen.

@rogpeppe es könnte bits.Log64 sein und die Dokumentation sagt, es sei Base 2 (was sonst wäre es in einem bits-Paket?) machen sie jeden einzeln sauberer. (Außerdem sieht es so aus, als ob Ihre Übertragung mitten im Satz unterbrochen wurde.)

@nerdatmath dies wäre etwas anders, da sie im umgangssprachlichen Sinne die gleiche Schnittstelle hätten, aber nicht im Sinne von Go, wie dies bei der Kodierung/Binär der Fall ist (beide implementieren binär.ByteOrder), sodass die Variablen nur für den Namensraum bestimmt sind und godoc würde keine der Methoden (#7823) aufnehmen, es sei denn, die Typen wurden exportiert, was auf eine andere Weise chaotisch ist.

Ich bin mit den Größenzusätzen einverstanden, aber insgesamt würde ich die separaten Pakete bevorzugen. Aber nicht genug, um sich die Mühe zu machen, diesen Kommentar hinter sich zu lassen.

@nerdatmath Wenn es sich um vars handelt, sind sie veränderbar (Sie könnten bits.Uint64 = bits.Uint8 tun), was den Compiler daran hindern würde, sie als intrinsisch zu behandeln, was eine der Motivationen für (zumindest einen Teil) des Pakets war.

Variablen sind nicht wirklich änderbar, wenn sie von einem nicht exportierten Typ sind und keinen Status haben (nicht exportierte leere Struktur). Aber ohne eine gemeinsame Schnittstelle wäre die godoc (heute) eklig. Reparierbar, wenn das die Antwort ist.

Wenn Sie jedoch alle mit mehreren Paketen arbeiten, wenn es genug Wiederholungen und Volumen gibt, um dies zu rechtfertigen, benennen Sie die Pakete zumindest vielleicht wie "X/bits/int64s", "X/bits/ints", "X/ bits/int32s", um "errors", "strings", "bytes" zu entsprechen. Scheint immer noch eine Menge Paketexplosion zu sein.

Ich denke nicht, dass wir den Weg zu mehreren typspezifischen Paketen gehen sollten, da einzelne Paketbits einfach sind und die Anzahl der Pakete in der Go-Bibliothek nicht erhöht wird.

Ich glaube nicht, dass Ones64 und TrailingZeroes64 gute Namen sind. Sie informieren nicht, dass sie eine Zählung zurückgeben. Durch das Hinzufügen des Präfixes Count werden die Namen noch länger. In meinen Programmen sind diese Funktionen oft in größere Ausdrücke eingebettet, sodass kurze Funktionsnamen die Lesbarkeit erhöhen. Obwohl ich die Namen aus dem Buch "Hacker's Delight" verwendet habe, schlage ich vor, den Intel-Assembler-Mnemoniken zu folgen: Popcnt64, Tzcnt64 und Lzcnt64. Die Namen sind kurz, folgen einem konsistenten Muster, weisen darauf hin, dass eine Zählung zurückgegeben wird und sind bereits festgelegt.

Normalerweise werden Anzahlen in Go als Ints zurückgegeben, auch wenn eine Anzahl niemals negativ sein kann. Das beste Beispiel ist die eingebaute Funktion len, aber Read, Write, Printf usw. geben auch ganze Zahlen zurück. Ich finde die Verwendung des uint-Werts für eine Zählung ziemlich überraschend und schlage vor, einen int-Wert zurückzugeben.

Wenn die Wahl zwischen (um die Notation zu missbrauchen) math/bits/int64s.Ones und math/bits.Ones64 besteht, dann würde ich definitiv ein einzelnes Paket bevorzugen. Es ist wichtiger, Bits in der Paketkennung zu haben, als nach Größe zu partitionieren. Das Vorhandensein von Bits im Paketbezeichner erleichtert das Lesen des Codes, was wichtiger ist als die geringfügige Unannehmlichkeit der Wiederholung in der Dokumentation des Pakets.

@ulikunitz Sie können immer ctz := bits.TrailingZeroes64 und dergleichen in Code ausführen, der Bits ausgiebig verwendet. Das schafft ein Gleichgewicht zwischen global selbstdokumentierenden Namen und lokal kompakten Namen für komplexen Code. Die umgekehrte Transformation, countTrailingZeroes := bits.Ctz64 , ist weniger nützlich, da die netten globalen Eigenschaften bereits verloren gehen.

Ich spiele sehr selten mit Sachen auf Bit-Ebene herum. Ich muss jedes Mal viel von diesem Zeug nachschlagen, nur weil es Jahre her ist, seit ich darüber nachdenken musste, also finde ich alle Namen im Hacker's Delight / asm-Stil ärgerlich kryptisch. Mir wäre es lieber, wenn es nur gesagt hat, was es getan hat, damit ich das finden kann, was ich brauche, und zum Programmieren zurückkehren.

Ich stimme @ulikunitz zu, die langen Namen sind eine Augenweide. Wenn Sie bits.TrailingZeroes64 zum ersten Mal sehen, ist es möglicherweise selbstdokumentierend. Jedes weitere Mal nervt es. Ich kann verstehen, warum Leute ctz := bits.TrailingZeroes64 machen würden, aber ich denke, ein Name, der einem kürzeren Namen zugewiesen werden muss, ist ein schlechter Name. Es gibt Orte, an denen Go abkürzt, zum Beispiel:

init() instead of initialize(), 
func instead of function
os instead of operatingsystem
proc instead of process
chan instead of channel

Außerdem würde ich bezweifeln, dass bits.TrailingZeroes64 selbstdokumentierend ist. Was bedeutet es, wenn eine Null nachgestellt wird? Die Eigenschaft Selbstdokumentation existiert unter der Annahme, dass der Benutzer zunächst etwas über die Semantik der Dokumentation weiß. Wenn Sie Hacker's Delight nicht aus Konsistenzgründen verwenden, warum nennen Sie es nicht einfach ZeroesRight64 und ZeroesLeft64, um RotateRight64 und RotateLeft64 zu entsprechen?

Um es klarzustellen, ich wollte nicht implizieren, dass Sie bei jeder Verwendung als erstes einen kurzen Alias ​​erstellen und niemals den Vornamen verwenden sollten. Ich meinte, wenn Sie es wiederholt verwenden, können Sie es mit einem Alias ​​versehen, wenn dies die Lesbarkeit des Codes verbessert.

Zum Beispiel, wenn ich strings.HasSuffix aufrufe, verwende ich meistens den Vornamen, weil ich ihn ein- oder zweimal aufrufe, aber hin und wieder in einem einmaligen ETL-Skript oder ähnlichem Am Ende nenne ich es einen Haufen, also mache ich ends := strings.HasSuffix was kürzer ist und den Code klarer macht. Es ist in Ordnung, denn die Definition des Alias ​​ist in der Nähe.

Abgekürzte Namen sind in Ordnung für Dinge, die sehr häufig vorkommen. Viele Nicht-Programmierer wissen, was OS bedeutet. Jeder, der Code liest, auch wenn er Go nicht kennt, wird feststellen, dass func die Abkürzung für Funktion ist. Kanäle sind von grundlegender Bedeutung für Go und werden häufig verwendet, daher ist Chan in Ordnung. Die Bits-Funktionen werden nie sehr verbreitet sein.

TrailingZeroes kann Ihnen möglicherweise nicht genau sagen, was vor sich geht, wenn Sie mit dem Konzept nicht vertraut sind, aber es gibt Ihnen eine viel bessere Vorstellung davon, zumindest grob, als Ctz.

@jimmyfrasche Zu https://github.com/golang/go/issues/18616#issuecomment -275828661: Für go/doc wäre es ziemlich einfach, eine zusammenhängende Folge von Funktionen in derselben Datei zu erkennen, deren Namen alle mit beginnen das gleiche Präfix und haben ein Suffix, das nur eine Folge von Zahlen ist und/oder vielleicht ein Wort, das relativ zum Präfix "kurz" ist. Ich würde es mit der Tatsache kombinieren, dass nur die erste dieser Funktionen einen doc-String hat und die andere mit passendem Präfix nicht. Ich denke, das könnte in allgemeineren Einstellungen ganz gut funktionieren. Lassen Sie uns dies jedoch an anderer Stelle besprechen, um diesen Vorschlag nicht zu entführen.

Zu Ihrer Information, #18858 erstellt, um die go/doc Änderungen zu besprechen und diese Unterhaltung von dieser zu trennen.

@mdlayher danke dafür.

@rogpeppe Das Einfügen der Größe in den Paketnamen klingt faszinierend, aber nach den Kommentaren und angesichts des vorhandenen Go-Stils denke ich, dass die Größe vorzugsweise mit dem Funktionsnamen angegeben wird. In Bezug auf Log2 würde ich sagen, lassen wir die 2 weg. (Bitte wiederholen Sie auch, wenn Sie noch etwas hinzufügen möchten. Ihre Kommentare werden mitten im Satz abgeschnitten.)

@ulikunitz Ich bin normalerweise einer der ersten, die für Kurznamen stimmen; insbesondere im lokalen Kontext (und im globalen Kontext für extrem häufig verwendete Namen). Aber hier haben wir eine Paket-API, und (zumindest nach meiner Erfahrung) werden die Funktionen in Clients nicht allgegenwärtig. Mathe/Big verwendet zum Beispiel LeadingZeros, Log2 usw., aber es sind nur ein oder zwei Aufrufe. Ich denke, ein längerer beschreibender Name ist in Ordnung, und der Diskussion nach zu urteilen, ist die Mehrheit der Kommentare dafür. Wenn Sie eine Funktion häufig aufrufen, ist es billig, sie in eine Funktion mit einem Kurznamen einzuschließen. Der zusätzliche Anruf wird inline entfernt. FWIW, die Intel-Mnemonik ist auch nicht so toll, die Betonung liegt auf "count" (cnt) und dann bleiben 2 Buchstaben, die entschlüsselt werden müssen.

Ich stimme zu, dass die 2 in Log2 nicht notwendig ist. bits.Log impliziert eine Basis von 2.

bits.Log64 SGTM.

@griesemer Mein abgeschnittener Satz war wahrscheinlich ein Artefakt ungeschickter Bearbeitung. Ich habe es gerade entfernt.

@griesemer Ich frage mich, ob es sich lohnt, den Namen Fahrradschuppen weiterzuführen. Ich mache es kurz: Ich hatte zwei Argumente: Kürze und Klarheit bei der Rückgabe einer Zahl. Package big verwendet intern nlz, nicht LeadingZeros. Ich stimme zu, dass die Anzahl der Verwendungen für diese Funktionen gering ist.

@ulikunitz Ich denke, die Mehrheit des Feedbacks hier spricht sich für klare Funktionsnamen aus, die keine Abkürzungen sind.

@ulikunitz , zusätzlich:

Package big verwendet intern nlz, nicht LeadingZeros.

Interne private nicht exportierte Namen haben hier keinen Einfluss.

Alles, was zählt, ist die Konsistenz und das Standardgefühl der öffentlichen API.

Bikeshedding mag ärgerlich sein, aber Namen sind wichtig, wenn wir für immer an ihnen festhalten.

CL https://golang.org/cl/36315 erwähnt dieses Problem.

Ich habe https://go-review.googlesource.com/#/c/36315/ als erste (teilweise getestete) API (mit vorläufiger Implementierung) zur Überprüfung (anstelle eines Designdokuments) hochgeladen.

Wir stellen immer noch sicher, dass die API korrekt ist. Kommentieren Sie daher vorerst nur die API (bits.go). Bei kleineren Problemen (Tippfehler etc.) bitte die CL kommentieren. Bei Designproblemen kommentieren Sie bitte dieses Problem. Ich werde die CL so lange aktualisieren, bis wir mit der Benutzeroberfläche zufrieden sind.

Machen Sie sich insbesondere keine Sorgen um die Implementierung. Es ist einfach und nur teilweise getestet. Sobald wir mit der API zufrieden sind, ist es einfach, die Implementierung zu erweitern.

@griesemer Ich bin sicher, es geht Ihnen gut, aber wenn es nützlich ist, habe ich eine CLZ-Assembly-Implementierung mit Tests https://github.com/ericlagergren/decimal/tree/ba42df4f517084ca27f8017acfaeb69629a090fb/internal/arith , die Sie frei haben stehlen/fummeln mit/was auch immer.

@ericlagergren Danke für den Link. Sobald sich die API eingerichtet hat und wir überall Tests haben, können wir mit der Optimierung der Implementierung beginnen. Ich werde das im Hinterkopf behalten. Vielen Dank.

@griesemer , es sieht so aus, als ob Ihre vorgeschlagene API-Dokumentation davon abhängt, dass go/doc Funktionen mit demselben Präfix, aber einem ganzzahligen Suffix angemessen dokumentieren kann.

Wäre es geplant, auch vor 1.9 daran zu arbeiten?

18858, das ist!

@mdlayher Das wäre ideal. Aber es wäre kein Showstopper, da es "nur" die Dokumentation betrifft, nicht die API (die in Zukunft abwärtskompatibel bleiben muss).

@griesemer @bradfitz Ich verbringe einige Zeit damit, meine Position zu den Funktionsnamen zu überdenken. Ein wichtiges Ziel bei der Namenswahl muss die Lesbarkeit des Codes sein, der die API verwendet.

Der folgende Code ist in der Tat leicht zu verstehen:

n := bits.LeadingZeros64(x)

Ich habe immer noch ein wenig Zweifel an der Klarheit von Ones64(x), aber es stimmt mit LeadingZeros und TrailingZeros überein. Also lasse ich meinen Fall zurück und unterstütze den aktuellen Vorschlag. Als Experiment habe ich meinen bestehenden Code für eine experimentelle reine Go-Implementierung des Pakets inklusive Testfällen verwendet.

Repository: https://github.com/ulikunitz/bits
Dokumentation: https://godoc.org/github.com/ulikunitz/bits

Wir können auch eine Reihe von Swap-Funktionen haben, [...] die meisten Verwendungen von SwapBytes sind auf Code zurückzuführen, der Endian-bewusst ist, obwohl dies nicht der Fall sein sollte. Kommentare?

Aus diesem Grund möchte ich SwapBytes aus der API heraushalten. Ich befürchte, dass die Leute anfangen werden, es auf nicht tragbare Weise zu verwenden, weil es einfacher ist (oder als schneller angenommen wird) als binary/encoding.BigEndian .

Aus diesem Grund möchte ich SwapBytes aus der API heraushalten. Ich befürchte, dass die Leute anfangen, es auf nicht tragbare Weise zu verwenden, weil es einfacher ist (oder als schneller angenommen wird) als binär/encoding.BigEndian.

@mundaym werden diese Routinen jemals intrinsisch sein? SwapBytes64 direkt als BSWAP SwapBytes64 kompilieren zu lassen, könnte Leute aufwiegen, die es falsch verwenden, imo.

Ich befürchte, dass die Leute anfangen werden, es auf nicht tragbare Weise zu verwenden, weil es einfacher ist (oder als schneller angenommen wird) als Binär-/Kodierung.BigEndian

Ich glaube, dass die Verwendung von ReverseBytes nur dann nicht portabel ist, wenn sie in Verbindung mit unsafe wo Sie auf Daten auf niedriger Ebene zugreifen, so wie die Maschine sie im Speicher anordnet. Die Verwendung von encoding.{Little,Big}Endian.X ist jedoch genauso nicht portabel, wenn sie auf diese Weise verwendet wird. Ich freue mich auf ReverseBytes für die Verwendung in der Komprimierung und wäre traurig, wenn es nicht enthalten wäre.

@ericlagergren

werden diese Routinen jemals intrinsisch sein?

Ja, die Funktionen in encoding/binary , die Byte-Umkehrungen durchführen, verwenden Codemuster, die vom Compiler erkannt werden und auf Plattformen, die nicht ausgerichtete Daten unterstützen, auf einzelne Anweisungen (z. B. BSWAP ) optimiert sind (oder könnten). Zugriffe (zB 386, amd64, s390x, ppc64le und wahrscheinlich andere).

SwapBytes64 direkt als BSWAP kompilieren zu lassen, könnte Leute, die es unsachgemäß verwenden, überwiegen, imo.

Ich neige dazu, anderer Meinung zu sein. Es kann legitime Verwendungen von SwapBytes in Endianness-unbewusstem Code geben, aber ich vermute, dass es häufiger missbraucht wird, als es richtig verwendet wird.

Beachten Sie, dass runtime/internal/sys bereits optimierte Go-, Assembly- und Compiler-intrinsische Versionen von nachgestellten Nullen und Byte-Swap hat, sodass wir diese Implementierungen wiederverwenden können.

@dsnet Interessant, hast du etwas Bestimmtes im Sinn?

@aclements Wissen Sie, wo der intrinsische Byte-Swap in der Laufzeit verwendet wird? Außerhalb der Tests habe ich keine Verwendung gefunden.

@mundaym ,

Alle: Nur eine Erinnerung, sich vorerst auf die API zu konzentrieren. Die Implementierung in der CL ist einfach eine triviale und langsame Implementierung, die es uns ermöglicht, diese Funktionen tatsächlich zu verwenden und zu testen. Sobald wir mit der API zufrieden sind, können wir die Implementierung optimieren.

In diesem Sinne: Wir sollten wahrscheinlich Versionen für uintptr einschließen, da nicht garantiert (wenn auch wahrscheinlich) die gleiche Größe wie ein uint32 oder uint64 . (Beachten Sie, dass uint immer entweder uint32 oder uint64 ). Meinungen? Auf später lassen? Was wären gute Namen? ( LeadingZerosPtr ?).

Wir sollten jetzt uintptr ausführen, sonst kann math/big es nicht verwenden. Suffix Ptr SGTM. Ich denke, wir sollten auch uint tun, sonst muss der gesamte aufrufende Code bedingten Code um die int-Größe schreiben, um den konversionsfreien Aufruf durchzuführen. Keine Namensideen.

Ptr für uintptr, kein Suffix für uint. Ones, Ones8, Ones16, Ones32, Ones64, OnesPtr usw.

Ich mag hier eine Minderheit sein, aber ich bin gegen das Hinzufügen von Funktionen, die uintptr benötigen.
math/big hätte von Anfang an uint32/uint64 verwenden sollen. (Tatsächlich würde ich
Verwenden Sie einfach uint64 für alle Architekturen und überlassen Sie die Optimierung den
Compiler.)

Das Problem ist folgendes:
math/big möchte die native Wortgröße für eine optimale Leistung verwenden,
Uintptr ist jedoch in der Nähe nicht die richtige Wahl.
Es gibt keine Garantie dafür, dass die Größe eines Zeigers dieselbe Größe hat wie die des
native Wortgröße.
Das beste Beispiel ist amd64p32, und wir könnten auch mips64p32 und . hinzufügen
mips64p32le später, die für 64-Bit-MIPS-Hosts viel beliebter sind.

Ich möchte sicherlich nicht mehr zu solchen Verwendungen ermutigen. uintptr ist am meisten
für Zeiger, nicht für architekturneutrale ganze Zahlen.

Eine Lösung besteht jedoch darin, einen Typalias in die API einzuführen (vielleicht ist es
sollte ein Markentyp sein, dies steht zur Diskussion).
type Word = uint32 // oder uint64 auf 64-Bit-Architekturen
// wir müssen wahrscheinlich auch ein paar ideale Konstanten für Word bereitstellen wie
Anzahl der Bits, Maske usw.

Führen Sie dann Funktionen ohne Typenpräfix ein, um Word zu verwenden.

Tatsächlich stelle ich mir vor, dass, wenn math/bit die beiden Ergebnisse liefert add, sub, mul,
div; math/big kann ohne architekturspezifische Assemblierung umgeschrieben werden.

Mathematik/große Exporte type Word uintptr . Ich stimme zu, dass es möglicherweise ein Fehler war, aber ich glaube, dass die Kompatibilität erfordert, dass dies bleibt. Wenn wir also math/bits verwenden möchten, um math/big zu implementieren, was wir meiner Meinung nach tun, benötigen wir uintptr-APIs.

Ich schweife ein bisschen vom Thema ab, aber ist die native Wortgröße ohne Vorzeichen nicht nur uint? In diesem Fall scheint es richtig zu sein, kein Suffix für uint zu haben.

Ich möchte nicht, dass ein einzelner Typ, Word, auf verschiedenen Architekturen unterschiedlich ist. Das scheint für den Anrufer und die Dokumente eine Menge Komplexität einzuführen. Wenn Sie sich an vorhandene Typen halten, können Sie es einfach verwenden. Einen Typ zu haben, der je nach Architektur variiert, bedeutet, dass Sie Ihren gesamten Code um diesen Typ herum entwerfen oder eine Reihe von Adaptern in Build-Tag-geschützten Dateien verwenden.

FWIW könnte die Implementierung von math/big immer noch eine uint32/64-basierte Bit-API verwenden (wenn es keine uintptr-Version der Funktionen gab).

@minux Ich bin nicht gegen das 2-Ergebnis add,sub,mul,div - aber lass uns das in einem 2. Schritt tun. Wir benötigen jedoch immer noch Assemblercode, wenn wir eine anständige Leistung wünschen. Aber ich bin froh, vom Compiler vom Gegenteil überzeugt zu werden...

Ich habe der API die Versionen uint und uintptr hinzugefügt. Schauen Sie sich die CL an und kommentieren Sie. Mit der Funktionsvielfalt bin ich allerdings nicht zufrieden.

@josharian Das native uint kann sogar auf einem 64-Bit-Computer ein 32-Bit-Wert sein. Es gibt keine Garantie. Mir ist klar, dass uintptr auch keine Maschinenwortgröße garantiert; aber damals schien es die vernünftigere Wahl zu sein, wenn auch im Nachhinein falsch. Vielleicht brauchen wir wirklich einen Word-Typ.

Wenn die einzige legitime Notwendigkeit für die uintptr-Funktionen darin besteht, math/big zu unterstützen, könnte die Implementierung von math/bits vielleicht in einem internen Paket untergebracht werden: Verwenden Sie nur das uintptr-Zeug in math/big und legen Sie den Rest offen.

@jimmyfrasche , @griesemer wie würde sich das auf big.Int.Bits auswirken? dh wird es immer noch Null-Zuordnung sein?

uint ist auch falsch. es ist 32-bit auf amd64p32.
Eigentlich hätte math/big uint anstelle von uintptr verwenden können, aber bei Go 1
int/uint sind immer 32-Bit, was uintptr zur einzig möglichen Lösung macht
für mathe/big.word.

Wir entwerfen ein neues Paket, daher glaube ich nicht, dass Kompatibilität viel ausmacht
Anliegen. math/big verwendet den falschen Typ, aber das ist eher ein historischer
Unfall. Nehmen wir den Fehler nicht weiter.

Ich verstehe nicht, warum ein einheitlicher Typ "Wort" verwendet wird, der immer am häufigsten ist
effizienter Integer-Typ für eine gegebene Architektur ist komplizierter als
mit uintptr. Für den Benutzercode können Sie ihn wie einen magischen Typ behandeln, der
entweder 32-Bit oder 64-Bit, je nach Architektur. Wenn du schreiben kannst
Ihr Code mit uintptr (dessen Breite auch architekturabhängig ist) in einem
architekturunabhängig, dann glaube ich, dass Sie das gleiche mit Word machen können.
Und Word sind auf allen Architekturen korrekt.

Für die Verbreitung der API schlage ich vor, dass wir Funktionen für 8 . weglassen
und 16-Bit-Typen im ersten Durchgang. Sie werden selten benutzt und die meisten
Architektur wird ohnehin nur 32-Bit- und 64-Bit-Anweisungen bereitstellen.

math/big definiert und exportiert Word, aber es ist a) nicht direkt kompatibel mit uintptr (es ist ein anderer Typ) und b) Code, der Word richtig verwendet und keine Annahmen über seine Implementierungen macht, kann mit jeder Art von Word-Implementierung verwendet werden . Wir können es ändern. Ich sehe nicht, warum dies die API-Kompatibilitätsgarantie beeinträchtigen sollte (habe es jedoch nicht versucht). Lassen wir das jedenfalls beiseite. Das können wir separat handhaben.

Können wir einfach alle Uint-Typen mit expliziter Größe ausführen? Wenn wir die Größe uint/uintptr als Konstante kennen, können wir trivialerweise eine if-Anweisung schreiben, die die Funktion mit der richtigen Größe aufruft. Diese if-Anweisung wird konstant weggeklappt, und die jeweilige Wrapper-Funktion ruft einfach die size-Funktion auf, die inline eingebunden wird.

Was ist schließlich die richtige Lösung bezüglich des Worttyps (unabhängig von Mathe/Big)? Natürlich könnten wir es in Mathe/Bits definieren, aber ist das der richtige Ort? Es ist ein plattformspezifischer Typ. Sollte es ein vordeklarierter Typ 'Wort' sein? Und warum nicht?

Hier irgendwelche Funktionssignaturen (oder Namen) zu haben, die uintptr direkt erwähnen, scheint ein Fehler zu sein. Die Leute sollten nicht so ein bisschen an Go-Zeigern herumspielen.

Wenn es Funktionen für einen natürlichen Maschinenworttyp geben sollte, denke ich, dass sich diese jetzt auf int/uint beziehen können. Wir haben uint in math/big nicht verwendet, weil es 32 Bit auf 64-Bit-Systemen war, aber das haben wir inzwischen behoben. Und während math/big eine zusammenhängende Welt für sich ist, in der es sinnvoll ist, einen eigenen Typ big.Word zu haben, ist dieses Paket eher eine Sammlung von Pick-and-Choose-Dienstprogrammen. Die Definition eines neuen Typs in diesem Zusammenhang ist weniger zwingend.

Ich weiß nicht, ob wir int/uint-Varianten überhaupt brauchen. Wenn sie häufig benötigt werden, ist es sinnvoller, sie in diesem Paket zu definieren, als alle Aufrufer zu zwingen, if-Anweisungen zu schreiben. Aber ich weiß nicht, ob sie allgemein gebraucht werden.

Um die potenzielle Diskrepanz mit math/big zu beheben, gibt es mindestens zwei Lösungen, bei denen uintptr hier nicht in der API erwähnt wird.

  1. Definieren Sie die Dateien word32.go und word64.go in math/big mit build-Tags und uintptr-akzeptierenden Wrappern, die entsprechend umleiten (und sicherstellen, dass das Compiler-Inlining das Richtige tut).

  2. Ändern Sie die Definition von big.Word in uint. Die genaue Definition ist ein internes Implementierungsdetail, obwohl es in der API verfügbar gemacht wird. Das Ändern kann keinen Code außer auf amd64p32 zerstören, und selbst dort scheint es sehr unwahrscheinlich, dass es außerhalb von Mathe/Big eine Rolle spielt.

@rsc : "Wir haben uint in math/big nicht verwendet, weil es auf 64-Bit-Systemen 32 Bit war, aber das haben wir inzwischen behoben. " Ah ja, ich habe den Grund für die Wahl von uintptr völlig vergessen. Der Sinn der Go uint/int-Typen bestand darin, ganzzahlige Typen zu haben, die die natürliche Registergröße einer Maschine widerspiegeln. Ich werde versuchen, big.Word in uint zu ändern und zu sehen, was passiert.

Alle: https://go-review.googlesource.com/#/c/36315/ aktualisiert, um Versionen der uintptr-Funktion gemäß obiger Diskussion auszuschließen.

Ich habe math/big vorläufig aktualisiert, um math/bits zu verwenden, um den Effekt zu sehen (https://go-review.googlesource.com/36328).

Ein paar Kommentare:
1) Ich tendiere dazu, die Ergebnisse der Leading / TrailingZeros Funktionen uint anstatt int . Diese Ergebnisse werden in der Regel verwendet, um einen Wert entsprechend zu verschieben; und math/big beweist dies. Ich bin auch dafür, dass Ones aus Konsistenzgründen ein uint zurückgibt. Gegenargumente?

2) Ich bin kein Fan des Namens One , jedoch im Einklang mit Leading / TrailingZeros . Wie wäre es mit Population ?

3) Einige Leute haben sich beschwert, dass Log nicht in dieses Paket passt. Log zufällig dem Index der msb (mit -1 für x == 0). Wir könnten es also MsbIndex (obwohl Log schöner erscheint). Alternativ könnten wir eine Len Funktion haben, die die Länge eines x in Bits ist; dh `Log(x) == Len(x)-1).

Kommentare?

Ich bin kein Fan des Namens One, jedoch im Einklang mit Leading/TrailingZeros. Wie sieht es mit der Bevölkerung aus?

Warum nicht das traditionelle Popcount ?

Alternativ könnten wir eine Len-Funktion haben, die die Länge eines x in Bits ist; dh `Log(x) == Len(x)-1).

Len oder Length klingt nach einem guten Namen und einer guten Idee.

1.

Ich bin kein Fan des Namens One, aber im Einklang mit Leading/
Nachgestellte Nullen. Wie sieht es mit der Bevölkerung aus?

Bits.Zählen?

@aclements Was zählt?

Bevölkerung mag aus historischen Gründen der bessere Name sein, aber es sagt nicht mehr als Einsen aus, wenn Sie den Begriff nicht kennen und das gleiche Problem haben, ist nur Zählen: Bevölkerung wovon?

Es ist etwas unidiomatisch, sogar innerhalb des Pakets, aber vielleicht CountOnes, um ihm einen klaren, offensichtlichen Namen zu geben.

@aclements Was zählt?

In einem Paket namens "Bits" bin ich mir nicht sicher, was Sie außer gesetzten Bits noch zählen könnten, aber CountOnes ist auch in Ordnung und offensichtlich expliziter. Das Hinzufügen von "Einsen" entspricht auch den "Nullen" in LeadingZeros / TrailingZeros .

Das ist die offensichtlichste Interpretation, aber es gibt immer noch Unklarheiten. Es könnte sich um das Zählen von nicht gesetzten Bits oder Gesamtbits handeln (die letzte Interpretation wäre extrem unwahrscheinlich, aber immer noch eine potenzielle Falle für jemanden, der Code mit Bits liest, der mit den beteiligten Konzepten nicht vertraut ist und möglicherweise denkt, dass der suffixfreie Count wie unsicher ist.SizeOf)

Vielleicht so etwas wie AllOnes oder TotalOnes, um Trailing/LeadingZeroes zu spiegeln, aber angeben, dass im Gegensatz zu diesen die Position nicht berücksichtigt wird.

AllOnes klingt so, als würde es alle Einsen zurückgeben (in einer Bitmaske vielleicht?), oder vielleicht ein Wort, das alle Einsen ist.

CountOnes und TotalOnes scheinen ungefähr gleich zu sein, aber da der bekannteste Name dieser Operation "Bevölkerungszählung" ist, scheint CountOnes vorzuziehen.

Ich habe eine neue Version (https://go-review.googlesource.com/#/c/36315/) mit einigen Änderungen hochgeladen:

1) Ich habe Ones in PopCount : Die meisten Leute waren nicht allzu begeistert von dem einheitlichen, aber etwas tristen Namen Ones . Jeder, der dieses Paket verwendet, wird genau wissen, was PopCount tut: unter diesem Namen ist diese Funktion allgemein bekannt. Es stimmt ein wenig mit dem Rest überein, aber seine Bedeutung ist so viel klarer geworden. Ich stimme Ralph Waldo Emerson in dieser Sache zu ("Eine törichte Konsistenz ist der Hobgoblin kleiner Geister...").

2) Ich habe Log in Len wie in bits.Len . Die Bitlänge einer Zahl x ist die Anzahl der Bits, die benötigt werden, um x in binärer Darstellung darzustellen (https://en.wikipedia.org/wiki/Bit-Länge). Scheint passend und beseitigt die Notwendigkeit, hier Log haben, was eine nicht "etwas fummelige" Qualität hat. Ich glaube, dass dies auch ein Gewinn ist, weil Len(x) == Log(x) + 1 , wie wir Log definiert hatten. Es hat darüber hinaus den Vorteil, dass das Ergebnis nun immer >= 0 ist und einige +/-1 Korrekturen in der (trivialen) Implementierung wegfallen.

Insgesamt bin ich mit dieser API an dieser Stelle ziemlich zufrieden (vielleicht möchten wir später weitere Funktionen hinzufügen). Die einzige andere Sache, die wir meiner Meinung nach ernsthaft in Betracht ziehen sollten, ist, ob alle Ergebnisse ohne Vorzeichen sein sollten. Wie ich bereits erwähnt habe, sind die Ergebnisse der Funktion Trailing/Leading Zero in der Regel Eingaben für Verschiebeoperationen, die ohne Vorzeichen sein müssen. Damit bleiben nur Len und PopCount übrig, die wohl auch Werte ohne Vorzeichen zurückgeben könnten.

Kommentare?

Meine Erfahrung mit Mathe/Big war frustrierend, dass ich von Funktionen in den uint-Modus gezwungen wurde, wenn ich nicht schalte. In mathe/big/prime.go habe ich geschrieben

for i := int(s.bitLen()); i >= 0; i-- {

obwohl s.bitLen() int und nicht uint zurückgibt, weil ich mir nicht sicher war, ohne hinzusehen, und mir auch nicht sicher war, ob einige zukünftige CL es nicht ändern würden, um uint zurückzugeben, wodurch die for-Schleife in eine Endlosschleife umgewandelt würde. Die Notwendigkeit, so defensiv sein zu müssen, deutet darauf hin, dass ein Problem vorliegt.

Uints sind viel fehleranfälliger als ints, weshalb wir von ihnen in den meisten APIs raten. Ich denke, es wäre viel schöner, wenn jede Funktion in math/bits int zurückgibt und die Konvertierung in uint im Shift-Ausdruck überlässt, wie es in den meisten Shift-Ausdrücken sowieso notwendig ist.

(Ich schlage keine Änderung vor, aber ich denke, dass eine Verschiebung, die uint erfordert, im Nachhinein ein Fehler gewesen sein könnte. Vielleicht können wir es irgendwann beheben. Es wäre schön, wenn es unseren anderen APIs nicht schadet.)

Ich unterstütze die Rückgabe von int, nachdem ich meinen Code für Aufrufe der nachgestellten Nullen und der Popcount-Funktionen gelesen habe. Die meisten Aufrufe erfolgen in kurzen Variablendeklarationen und Vergleichen vom Typ int. Anrufe in Schichten erfordern natürlich den Typ uint, sind aber überraschend selten.

Kleinere Anpassungen hochgeladen. Lassen Sie uns pro +1 und Kommentar die Ergebnisse der Zählung als int .

https://go-review.googlesource.com/36315

Ich habe die Eingabezählungen für RotateLeft/Right als uint belassen, ansonsten müssen wir angeben, was passiert, wenn der Wert negativ ist oder nicht zulassen.

Schließlich könnten wir Len seit LenN(x) == N - LeadingZerosN(x) . Meinungen?

Wenn es zu diesem Zeitpunkt kein signifikantes Feedback mehr gibt, würde ich vorschlagen, dass wir mit dieser API fortfahren und nach der Überprüfung eine erste Implementierung vornehmen. Danach können wir mit der Optimierung der Implementierung beginnen.

In einem nächsten Schritt möchten wir vielleicht diskutieren, ob und welche anderen Funktionen wir möglicherweise einschließen möchten, insbesondere Add / Sub / etc., die 2 Argumente verbrauchen und übertragen und ein Ergebnis erzeugen und übertragen .

@gri Gedanken zu einer Basis 10 Len Funktion? Es wäre nur ((N - clz(x) + 1) * 1233) >> 12

Es ist weniger "etwas hacky" als Base 2, aber immer noch nützlich.
Am Freitag, 10. Februar 2017 um 17:03 Uhr Robert Griesemer [email protected]
schrieb:

Kleinere Anpassungen hochgeladen. Lassen Sie uns die Ergebnisse von +1 und Kommentaren hinterlassen
gilt als int.

https://go-review.googlesource.com/36315

Ich habe die Eingabezahlen für RotateLeft/Right als uint belassen, sonst werden wir es tun
Sie müssen angeben, was passiert, wenn der Wert negativ ist, oder ihn nicht zulassen.

Schließlich könnten wir Len sogar weglassen, da LenN(x) == N -
Führende NullenN(x). Meinungen?

Wenn es zu diesem Zeitpunkt kein signifikantes Feedback mehr gibt, würde ich vorschlagen, dass wir
Fahren Sie mit dieser API fort und führen Sie anschließend eine erste Implementierung durch
Rezension. Danach können wir mit der Optimierung der Implementierung beginnen.

In einem nächsten Schritt möchten wir vielleicht besprechen, ob und welche anderen Funktionen wir möglicherweise haben
einschließen möchten, insbesondere Add/Sub/ etc., die 2 Argumente verbrauchen und
tragen und ein Ergebnis produzieren und tragen.


Sie erhalten dies, weil Sie erwähnt wurden.
Antworten Sie direkt auf diese E-Mail und zeigen Sie sie auf GitHub an
https://github.com/golang/go/issues/18616#issuecomment-279107013 oder stumm
der Faden
https://github.com/notifications/unsubscribe-auth/AFnwZ_QMhMtBZ_mzQAb7XZDucXrpliSYks5rbQjCgaJpZM4Lg5zU
.

Ebenfalls. Verzeihen Sie, wenn ich den Rahmen dieses Problems verliere, aber ich wäre für geprüfte Arithmetik. ZB AddN(x, y T) (sum T, overflow bool)

Am Freitag, 10. Februar 2017 um 20:16 Uhr, Eric Lagergren [email protected]
schrieb:

Ebenfalls. Verzeihen Sie, wenn ich aus dem Rahmen dieses Problems herauskomme, aber ich wäre dabei
zugunsten der geprüften Arithmetik. ZB AddN(x, y T) (Summe T, Überlauf bool)

Redest du von signiertem Überlauf? oder unsigned overflow (carry/borrow)?

Außerdem ziehe ich es vor, Überlauf/Übertrag/Ausleihe als Typ T zurückzugeben, um es zu vereinfachen
Multi-Präzisions-Arithmetik.

@ericlagergren Die (Basis 2) Len Funktion, obwohl sie fast log2 ist, ist im Wesentlichen immer noch eine Bit-Zählfunktion. Eine Basis-10- Len Funktion ist in Wirklichkeit eine Protokollfunktion. Es ist nicht zu leugnen, dass es nützlich ist, aber es scheint für dieses Paket weniger geeignet zu sein.

Ja, ich denke, es wäre schön, checked Arithmetik einzubeziehen. Ich wollte nur zuerst die aktuelle API schließen, damit wir Fortschritte machen können, anstatt hin und her zu gehen. Es scheint, dass die meisten Leute, die bisher kommentiert haben, damit zufrieden sind.

Bezüglich der Add- , Sub-Funktionen: Ich stimme

Ein Mathe/Checked-Paket könnte für diese ein besseres Zuhause sein. checked.Add ist klarer als bits.Add .

@minux Ich dachte an signierte überprüfte Arithmetik, also Überlauf.

@griesemer re: Basis 10 Len : macht Sinn!

Wenn Sie möchten, kann ich eine CL für geprüfte Arithmetik einreichen. Ich mag die Idee von @jimmyfrasche , es unter einem separaten Paketnamen zu haben.

Add/sub/mul kann zu bits gehören oder auch nicht, aber geprüfte Mathematik ist nicht die einzige Verwendung für diese Operationen. Allgemeiner würde ich sagen, dass dies "breite" arithmetische Operationen sind. Für Add/Sub gibt es kaum einen Unterschied zwischen dem einen Bit des Carry/Overflow-Ergebnisses und einem booleschen Indikator für Overflow, aber wir möchten wahrscheinlich auch Add-with-Carry und Subtract-with-Borrow bereitstellen, um diese verkettebar zu machen. Und für Wide Mul gibt es im Zusatzergebnis weit mehr Informationen als Überlauf.

Es ist eine gute Idee, sich die arithmetischen Operationen mit check/wide zu merken, aber lassen wir sie für die zweite Runde.

"Jeder, der dieses Paket verwendet, wird genau wissen, was PopCount tut"

Ich habe diese Funktionalität schon früher verwendet und war irgendwie mit dem PopCount-Namen nicht vertraut (obwohl ich es hätte tun sollen, weil ich sicher bin, dass ich die Implementierung von Hacker's Delight abgeklemmt habe, die "Pop" verwendet).

Ich weiß, dass ich auf der Party bin, aber "OnesCount" erscheint mir wesentlich offensichtlicher, und wenn das Wort "PopCount" im Doc-Kommentar erwähnt wird, werden die Leute, die danach suchen, es sowieso finden.

Verwandte für karierte/breite Arithmetik: #6815. Aber ja, lass uns um eins reinkommen!

@griesemer schrieb:

Die (Basis 2) Len-Funktion ist, obwohl sie fast log2 ist, im Wesentlichen immer noch eine Bitzählfunktion. Eine Basis-10-Len-Funktion ist wirklich eine Log-Funktion. Es ist nicht zu leugnen, dass es nützlich ist, aber es scheint für dieses Paket weniger geeignet zu sein.

Ich habe letzten Oktober einen Benchmark geschrieben, um einige Ansätze für das Problem der "Dezimalstellenlänge" zu vergleichen, den ich in einer Zusammenfassung veröffentlicht habe .

Als Vorschlag angenommen; Es wird wahrscheinlich kleinere Optimierungen geben, und das ist in Ordnung.

@rogpeppe : PopCount in OnesCount geändert, da es auch 4 Daumen hoch erhielt (wie mein Vorschlag, PopCount ). Bezogen auf "Bevölkerungszahl" in der Dokumentzeichenfolge.

Per @rsc , Checked/Wide-Arithmetik-Operationen

Auch per @rsc geben alle int Werte zurück, und aus Gründen der Benutzerfreundlichkeit (und mit Blick auf #19113) verwenden Sie int Werte für Rotationszählungen.

Habe LenN Funktionen drin gelassen, obwohl sie einfach N - LeadingZerosN . Aber eine ähnliche Symmetrie existiert für RotateLeft / Right und wir haben beides.

Etwas schnellere Implementierung für TrailingZeroes hinzugefügt und Tests abgeschlossen.

An dieser Stelle glaube ich, dass wir eine brauchbare erste Implementierung haben. Bitte überprüfen Sie den Code unter https://go-review.googlesource.com/36315 , insbesondere die API. Ich möchte dies gerne eingereicht bekommen, wenn wir alle zufrieden sind.

Nächste Schritte:

  • schnellere Implementierung (primär)
  • Hinzufügen zusätzlicher Funktionen (sekundär)

Wir entwerfen ein neues Paket

@minux Du meinst neue Mathematik/groß? Kann man den Vorgang irgendwo verfolgen?

@TuomLarsen : @minux bezog sich auf Mathematik/Bits mit "neuem Paket". Er erwähnte Mathe/Big als einen Fall, in dem Mathe/Bits verwendet würden. (Bitte formulieren Sie in Zukunft Ihre Kommentare genauer, damit wir nicht suchen und erraten müssen, worauf Sie sich beziehen - danke).

CL https://golang.org/cl/37140 erwähnt dieses Problem.

Wird es Compiler-unterstützte Intrinsifizierung in Mathe/Bits für Go 1.9 geben?

@cespare Hängt davon ab, ob wir dazu kommen (@khr?). Unabhängig davon wollen wir eine ordentliche plattformunabhängige Implementierung haben. (Einer der Gründe, warum wir math/big nicht vollständig auf die Verwendung von math/bits umstellen möchten, ist, dass wir derzeit eine plattformspezifische Assemblierung in math/big haben, die schneller ist.)

Auf meinem Teller, zumindest für die Bögen, für die wir bereits Intrinsics machen
(386,amd64,arm,arm64,s390x,mips, wahrscheinlich ppc64).

Am Freitag, 17. Februar 2017 um 12:54 Uhr, Robert Griesemer < [email protected]

schrieb:

@cespare https://github.com/cespare Hängt davon ab, ob wir dazu kommen (
@khr https://github.com/khr ?). Unabhängig davon wollen wir eine
anständige plattformunabhängige Implementierung. (Einer der Gründe, warum wir das nicht tun
Mathe / Big komplett auf die Verwendung von Mathe / Bits umstellen möchten, ist das derzeit wir
haben einige plattformspezifische Assembler in Math/Big, was schneller ist.)


Sie erhalten dies, weil Sie erwähnt wurden.
Antworten Sie direkt auf diese E-Mail und zeigen Sie sie auf GitHub an
https://github.com/golang/go/issues/18616#issuecomment-280763679 oder stumm
der Faden
https://github.com/notifications/unsubscribe-auth/AGkgIIb8v1X5Cr-ljDgf8tQtT4Dg2MGiks5rdgkegaJpZM4Lg5zU
.

Dieser Artikel über den schnellsten Weg zur Implementierung der Populationszählung auf x86-64 kann hilfreich sein: Handcodierte Assemblierung schlägt intrinsische Geschwindigkeit und Einfachheit – Dan Luu, Okt 2014

CL https://golang.org/cl/38155 erwähnt dieses Problem.

CL https://golang.org/cl/38166 erwähnt dieses Problem.

CL https://golang.org/cl/38311 erwähnt dieses Problem.

CL https://golang.org/cl/38320 erwähnt dieses Problem.

CL https://golang.org/cl/38323 erwähnt dieses Problem.

Noch eine Diskussion:

Mir:

math/bits.RotateLeft ist derzeit für Panik definiert, wenn sein Argument kleiner als Null ist.
Ich möchte das ändern, um RotateLeft so zu definieren, dass eine Rechtsdrehung durchgeführt wird, wenn das Argument kleiner als Null ist.

Eine Grundroutine wie diese Panik zu haben, scheint unnötig hart zu sein. Ich würde argumentieren, dass eine Drehung um einen negativen Betrag eher einer Verschiebung um mehr als die Wortgröße (was keine Panik macht) entspricht, anstatt einer Division durch Null (was Panik auslöst). Eine Division durch Null muss wirklich in Panik geraten, da es kein vernünftiges Ergebnis gibt, das Sie stattdessen zurückgeben würden. Rotiert um negative Beträge hat ein perfekt definiertes Ergebnis, das wir zurückgeben können.

Ich denke, wir sollten RotateLeft und RotateRight als separate Funktionen beibehalten, auch wenn das eine jetzt mit dem anderen implementiert werden könnte. Die Standardverwendung wird weiterhin mit nichtnegativen Argumenten erfolgen.

Wenn wir hier etwas unternehmen, sollten wir es bis zum Einfrieren tun. Sobald go 1.9 raus ist, können wir unsere Meinung nicht mehr ändern.

Rauben:

Wenn Sie so etwas wirklich wollen, würde ich nur eine Funktion haben, Rotate, die ein positives für links und ein negatives für rechts akzeptiert.

Mir:

Das Problem ist
bits.Rotate(x, -5)

Beim Lesen dieses Codes ist nicht klar, ob wir uns am Ende nach links oder rechts drehen.
bits.RotateRight(5)
ist viel klarer, auch wenn dies bedeutet, dass es in Mathe/Bits doppelt so viele Rotate*-Funktionen gibt.

Michael Jones:

Rotieren mit Vorzeichen bedeutet Sprünge im Code, ja? Scheint schade.

Mir:

Nein, die Implementierung ist mit der vorgeschlagenen Semantik eigentlich einfacher. Maskieren Sie die niedrigen 6 Bits (für 64-Bit-Rotationen) und es funktioniert einfach.

Ich bevorzuge das einzelne Rotate mit einem vorzeichenbehafteten Argument, weil es die Hälfte der Funktionen bedeutet und in allen Fällen sehr effizient ohne Panik oder sogar eine bedingte Verzweigung durchgeführt werden kann.

Rotate ist sowieso eine Spezialfunktion, daher werden sich die Leute, die sie verwenden, sicherlich damit wohlfühlen, sich daran zu erinnern, dass ein positives Argument nach links und ein negatives Argument nach rechts verschoben wird.

Sie können die Differenz immer aufteilen und nur RotateLeft mit einem vorzeichenbehafteten Argument angeben. Das gibt eine praktische Gedächtnisstütze für die Richtung, vermeidet aber doppelte Funktionen.

@bcmills @robpike siehe auch vorherige Diskussion zu diesem genauen Thema ab https://github.com/golang/go/issues/18616#issuecomment -275598092 und weiter für ein paar Kommentare

@josharian Ich habe die Kommentare gesehen und bevorzuge immer noch meine Version. Dies kann für immer wegfallen, aber ich versuche, einfach zu implementieren, einfach zu definieren, einfach zu verstehen und einfach zu dokumentieren. Ich glaube, dass eine einzelne Rotate-Funktion mit einem vorzeichenbehafteten Argument all dies erfüllt, mit Ausnahme der Notwendigkeit, das Vorzeichen zu definieren, aber dass links positiv ist, sollte für jeden intuitiv sein, der einen Drehbefehl verwenden kann.

aber dass links positiv ist, sollte für jeden intuitiv sein, der in der Lage ist, einen Rotationsbefehl zu verwenden.

Ich halte mich gerne für in der Lage, eine Drehanweisung zu verwenden, und meine Intuition ist "Meine Güte, warum sagt das nicht, in welche Richtung es geht? Es ist wahrscheinlich links, aber ich muss mir die Dokumentation ansehen , um sicherzugehen ." Ich stimme zu, dass es intuitiv ist, dass wenn Sie eine positive Richtung wählen würden, es eine Linksdrehung wäre, aber es gibt eine viel höhere Schwelle dafür, dass etwas so offensichtlich die richtige Antwort ist, dass an jeder Anrufseite klar ist, in welche Richtung Sie drehen, ohne es zu sagen es.

Was die Lesbarkeit angeht, wie wäre es mit etwas in der Art der time.Duration API:

const RotateRight = -1

bits.Rotate(x, 5 * RotateRight)

Vielleicht eine durch Bits definierte Konstante oder vielleicht eine Übung für den Leser (Aufrufseite)?

@aclements Und so haben Sie am Ende zwei (mal N Typen) Funktionen mit identischen Fähigkeiten, deren einziger Unterschied ein Vorzeichen im Argument ist. Jetzt tolerieren wir es für Add und Sub, aber das ist das einzige Beispiel, das mir einfällt.

Auf der Zahlenachse nehmen positive Zahlen nach rechts zu, daher erwarte ich eine durch Vorzeichen definierte Drehung/Verschiebung, um die Bits für positive Zahlen nach rechts zu verschieben.

Ich habe kein Problem, wenn es das [dokumentierte] Gegenteil ist, aber ich würde das nicht als intuitiv bezeichnen.

@cznic- Bits werden jedoch von rechts nach links geschrieben

Ich bin auch für nur Rotate (https://github.com/golang/go/issues/18616#issuecomment-275016583), wenn wir es in beide Richtungen zum Laufen bringen.

Als Gegenargument zu @aclements Bedenken hinsichtlich der Richtung: Die Bereitstellung eines RotateLeft , das auch bei Rechtsdrehung funktioniert, kann auch ein falsches Sicherheitsgefühl vermitteln: "Es steht RotateLeft also dreht es sich sicherlich nicht rechts!". Mit anderen Worten, wenn es RotateLeft sagt, macht es besser nichts anderes.

Außerdem wird bits.Rotate wirklich nur im Fachcode verwendet. Dies ist keine Funktion, die von vielen Programmierern verwendet wird. Benutzer, die es wirklich brauchen, werden die Symmetrie von Rotationen verstehen.

@nathany

Bits werden jedoch von rechts nach links geschrieben

Bits sind nur binäre Ziffern. Ziffern in Zahlen in jeder Basis werden von links nach rechts geschrieben - sogar in den meisten, wenn nicht allen, von rechts nach links geschriebenen Systemen. 123 ist einhundertdreiundzwanzig, nicht dreihunderteinundzwanzig.

Dass die Potenz des Multiplikanden für die Ziffer nach rechts abnimmt, ist eine andere Sache.

Nochmals: Die Richtung ist mir egal, die intuitive ist nur eine Frage der persönlichen Vorstellungskraft.

Ich mag Rotieren. Das niedrigstwertige Bit ist meiner Ansicht nach intuitiv 0 genug.

Bitte behalten Sie sowohl RotateLeft als auch RotateRight bei, anstatt etwas zu tun, an das sich die Hälfte der Entwickler falsch erinnern wird. Es scheint jedoch in Ordnung zu sein, mit negativen Zahlen umzugehen.

99% der Entwickler werden niemals einen Drehbefehl programmieren, daher ist die Notwendigkeit einer eindeutigen Richtung bestenfalls schwach. Ein einziger Anruf genügt.

Das Problem, das diese Diskussion wiederbelebt hat, ist, dass beides erfordert, darüber zu streiten, ob negative Werte in Ordnung sind, und wenn nicht, was man dagegen tun kann. Wenn man nur einen hat, fällt dieses ganze Argument weg. Es ist ein saubereres Design.

Ich sympathisiere ein wenig mit dem Argument über sauberes Design, aber es scheint seltsam, dass Sie "Right" aus "RotateRight" entfernen müssen, während Sie die gleiche Implementierung beibehalten, um dies zu erreichen. In praktischer Hinsicht scheint es die einzige Möglichkeit zu sein, Fragen zu beantworten, indem es Leute, die es sehen, dazu zwingt, die Dokumentation zu lesen, und zwar durch die Fragen, die der Name aufwirft.
Am Ende geht es bei negativen Werten um eindeutige Richtung vs. eindeutiges Verhalten. Negative Werte sollten im allgemeinen Fall wahrscheinlich weniger besorgniserregend sein.

Was ich sagen will, ist, dass Rotate eine Frage für alle Menschen aufwirft und diese indirekt durch Dokumentation beantwortet.
RotateRight wirft eine Frage für sehr wenige Leute auf, die die Dokumentation lesen können (und sollten), wenn sie sich darum kümmern.

Auf der anderen Seite würde Rotate wahrscheinlich verhindern, dass Leute if n < 0 { RotateLeft(...) } else { RotateRight(...) } schreiben.

@golang/proposal-review hat dies diskutiert und endete damit, dass es nur eine Funktion hatte, die aber RotateLeft , nicht nur Rotate , genannt wurde, um auf Anrufseiten besonders klar zu sein. Negative Zahlen drehen sich nach rechts, und die Dokumentation wird dies deutlich machen.

CL https://golang.org/cl/40394 erwähnt dieses Problem.

CL https://golang.org/cl/41630 erwähnt dieses Problem.

Der ursprüngliche Vorschlag sowie einige zusätzliche Funktionen wurden an dieser Stelle entworfen und implementiert. Wir können diese Bibliothek im Laufe der Zeit erweitern, aber sie scheint im Moment einigermaßen "vollständig" zu sein.

Vor allem haben wir uns nicht für folgende Funktionen entschieden oder diese implementiert:

  • teste ob add/mul/etc overflow
  • Bereitstellung von Funktionen zum Implementieren von add/mul/etc, die einen eingehenden Übertrag akzeptieren und ein Ergebnis plus Übertrag (oder höheres Wort) erzeugen

Persönlich bin ich nicht überzeugt, dass diese in ein "Bits" -Paket gehören (vielleicht tun die Tests). Funktionen zum Implementieren von Multi-Precision-Add/Sub/Mul würden eine reine Go-Implementierung einiger der mathematischen/großen Kernel ermöglichen, aber ich glaube nicht, dass die Granularität stimmt: Was wir wollen, sind optimierte Kernel, die mit Vektoren arbeiten, und maximal Leistung für diese Kernel. Ich glaube nicht, dass wir dies mit Go-Code erreichen können, der allein von add/sub/mul "intrinsics" abhängt.

Daher möchte ich dieses Thema vorerst als "erledigt" schließen, es sei denn, es gibt größere Einwände. Bitte melden Sie sich in der nächsten Woche oder so, wenn Sie gegen eine Schließung sind.

Ich bin dafür, Funktionen in diese Richtung hinzuzufügen.

Ich bin fest davon überzeugt, dass sie in ihr eigenes Paket gehören, und sei es aus keinem anderen Grund, als ihr einen Namen zu geben, der ihre kollektive Funktionalität besser widerspiegelt.

:+1: zum Abschluss dieser Ausgabe und :heart: für die bisher geleistete Arbeit.

Schließung, da keine Einwände erhoben wurden.

Dies ist ein Kommentar für zukünftige Entscheidungen bezüglich API, ich verstehe, dass dieser spezielle festgelegt ist.

Rotate ist eine Spezialistenfunktion; LTR oder RTL ist nur im Kontext relevant. @aclements brachte eine gültige Frage auf, keinen gültigen, sich ständig erweiternden Punkt. Seine Frage hätte beantwortet werden können als "es ist RTL, genauso wie Integer-Inkrement"; einfach richtig?

Aber stattdessen folgt Klugheit.

Was "Spezialistenfunktion" bedeutet, ist so einfach, dass es wahrscheinlich zu schnell abgetan wurde. Wenn ein Codebeispiel gegeben ist, versteht man wahrscheinlich bereits die Rotation und die Richtung, noch bevor man auf die Codezeile stößt. Einem solchen Code geht in der Regel bereits eine anschauliche ASCII-Dokumentation voraus.

Was mental turbulent ist, ist nicht, dass Go einfach RTL als Standardmethode zur Interpretation von Bits aus einer API-Perspektive hätte wählen können, sondern ich habe zuerst die Änderungen von 1.9 hochgezogen und ein RotateLeft ohne Gegenstück gefunden, und das Dokument gibt ein Beispiel für a negativen Schritt. Dies ist eine bewusstseinserschütternde Komitee-ähnliche Entscheidung, die sehr bedauerlich ist, in 1.9 zu landen.

Ich plädiere nur dafür, mich für die Zukunft an den Nutzungskontext zu halten. All dies sollte selbstverständlich sein mit Fragen wie "Warum liefern wir kein Gegenstück zu RotateLeft, warum geraten wir bei negativen Schritten in Panik oder diskutieren int vs. uint um einen Schritt"; letztendlich, weil ich denke, was "eine Fachfunktion" bedeutet, wurde einfach zu leicht abgetan, weil man nicht klug ist.

Lassen Sie uns bitte bei unserer Rechtfertigung von APIs keine Klugheit vermeiden. Es zeigt in diesem 1.9-Update.

Änderung https://golang.org/cl/90835 erwähnt dieses Problem: cmd/compile: arm64 intrinsics for math/bits.OnesCount

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen

Verwandte Themen

michaelsafyan picture michaelsafyan  ·  3Kommentare

longzhizhi picture longzhizhi  ·  3Kommentare

bbodenmiller picture bbodenmiller  ·  3Kommentare

natefinch picture natefinch  ·  3Kommentare

mingrammer picture mingrammer  ·  3Kommentare