From 44e6f25b61d67c2beb7ad6c03323875d5c2b01ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Rainone?= Date: Sun, 30 May 2021 22:07:33 +0200 Subject: [PATCH 01/19] Add LeadingOnes and TrailingOnes --- README.md | 5 +- bitstring.go | 70 ++++++++++++- bitstring_test.go | 257 +++++++++++++++++++++++++++------------------- 3 files changed, 217 insertions(+), 115 deletions(-) diff --git a/README.md b/README.md index a9224d5..b21039b 100644 --- a/README.md +++ b/README.md @@ -19,10 +19,10 @@ Package `bitstring` implements a fixed length bit string type and bit manipulati - Gray code conversion methods: `Gray8`|`Gray16`|`Gray32`|`Gray64`|`Grayn` - Convert to/from `big.Int`: `BigInt` | `NewFromBig` - Copy/Clone methods: `Copy`|`Clone`|`CopyRange` - - Trailing/LeadingZeroes : `TrailingZeroes`|`LeadingZeroes` + - Trailing/Leading Zeroes/Ones : `TrailingZeroes`|`LeadingZeroes`|`TrailingOnes`|`LeadingOnes` -# Debug version +## Debug version By default, bit offsets arguments to `bitstring` methods are not checked. This allows not to pay the performance penalty of always checking offsets, in @@ -33,7 +33,6 @@ when building the `bitstring` package. **TODO**: - RotateLeft/Right ShiftLeft/Right - - Trailing/Leading ones - Or, And, Xor between bitstrings - Reverse - Run CI on big|little endian and 32|64 bits (for now only amd64) (see https://github.com/docker/setup-qemu-action) diff --git a/bitstring.go b/bitstring.go index 0e98506..c0e0f4d 100644 --- a/bitstring.go +++ b/bitstring.go @@ -281,8 +281,9 @@ func (bs *Bitstring) Equals(other *Bitstring) bool { return false } -// LeadingZeroes returns the number of leading 0 bits in bs. (i.e the number of -// zeroes in the leftmost side of the string representation). +// LeadingZeroes returns the number of leading bits that are set to 0 in bs. +// (i.e the number of consecutives 0's starting from the MSB (most significant +// bit). func (bs *Bitstring) LeadingZeroes() int { bitoff := int(bitoffset(uint64(bs.length))) start := len(bs.data) - 1 @@ -313,8 +314,43 @@ func (bs *Bitstring) LeadingZeroes() int { return n } -// TrailingZeroes returns the number of leading 0 bits in bs. (i.e the number of -// zeroes in the rightmost side of the string representation). +// LeadingOnes returns the number of leading bits that are set to 1 in bs. (i.e +// the number of consecutives 1's starting from the MSB (most significant bit). +func (bs *Bitstring) LeadingOnes() int { + bitoff := int(bitoffset(uint64(bs.length))) + start := len(bs.data) - 1 + + n := 0 + for i := start; i >= 0; i-- { + // We treat the first word separately if the bistring length is not a + // multiple of the wordsize because in this case we must omit the 'extra + // bits' from the count the count of leading zeroes. + if i == start && bitoff != 0 { + leading := bits.LeadingZeros64(lomask(uint64(bitoff)) ^ bs.data[i]) + + // Limit to 'off' the number of bits we count. + leading -= 64 - bitoff + n += leading + if leading != bitoff { + break // early exit if useful bits are not all 0s. + } + } else { + leading := bits.LeadingZeros64(^bs.data[i]) + + // Subsequent words + n += leading + if leading != 64 { + break + } + } + } + + return n +} + +// TrailingZeroes returns the number of trailing bits that are set to 0 in bs. +// (i.e the number of consecutives 0's starting from the LSB (least significant +// bit). func (bs *Bitstring) TrailingZeroes() int { bitoff := int(bitoffset(uint64(bs.length))) last := len(bs.data) - 1 @@ -336,7 +372,33 @@ func (bs *Bitstring) TrailingZeroes() int { break } } + return n +} +// TrailingOnes returns the number of trailing bits that are set to 1 in bs. +// (i.e the number of consecutives 1's starting from the LSB (least significant +// bit). +func (bs *Bitstring) TrailingOnes() int { + bitoff := int(bitoffset(uint64(bs.length))) + last := len(bs.data) - 1 + + n := 0 + for i := 0; i < len(bs.data); i++ { + trailing := bits.TrailingZeros64(^bs.data[i]) + + if i == last && bitoff != 0 && trailing == 64 { + // There's one specific case we need to take care of: if the last + // word if 0 and the bitstring length is not a multiple of the + // wordsize, then the effective number of trailing bits is not 64, + // we need to limit it to the number of useful bits only. + trailing = bitoff + } + + n += trailing + if trailing != 64 { + break + } + } return n } diff --git a/bitstring_test.go b/bitstring_test.go index cd2f6ff..166a238 100644 --- a/bitstring_test.go +++ b/bitstring_test.go @@ -246,179 +246,220 @@ func TestBig(t *testing.T) { } } -func TestLeadingTrailingZeroes(t *testing.T) { +func TestLeadingTrailingZeroesOnes(t *testing.T) { tests := []struct { - name string - s string - leading, trailing int + name string + s string + leadingZeroes, trailingZeroes int + leadingOnes, trailingOnes int }{ { - name: "less than 64 bits", - s: "0", - leading: 1, - trailing: 1, + name: "less than 64 bits", + s: "0", + leadingZeroes: 1, trailingZeroes: 1, + leadingOnes: 0, trailingOnes: 0, }, { - name: "less than 64 bits", - s: "1", - leading: 0, - trailing: 0, + name: "less than 64 bits", + s: "1", + leadingZeroes: 0, trailingZeroes: 0, + leadingOnes: 1, trailingOnes: 1, }, { - name: "less than 64 bits", - s: "10", - leading: 0, - trailing: 1, + name: "less than 64 bits", + s: "10", + leadingZeroes: 0, trailingZeroes: 1, + leadingOnes: 1, trailingOnes: 0, }, { - name: "less than 64 bits", - s: "01", - leading: 1, - trailing: 0, + name: "less than 64 bits", + s: "01", + leadingZeroes: 1, trailingZeroes: 0, + leadingOnes: 0, trailingOnes: 1, }, { - name: "less than 64 bits", - s: "00", - leading: 2, - trailing: 2, + name: "less than 64 bits", + s: "00", + leadingZeroes: 2, trailingZeroes: 2, + leadingOnes: 0, trailingOnes: 0, }, { - name: "less than 64 bits", - s: "11", - leading: 0, - trailing: 0, + name: "less than 64 bits", + s: "11", + leadingZeroes: 0, trailingZeroes: 0, + leadingOnes: 2, trailingOnes: 2, }, { - name: "exactly 64 bits", - s: "1111111111111111111111111111111111111111111111111111111111111111", - leading: 0, - trailing: 0, + name: "exactly 64 bits", + s: "1111111111111111111111111111111111111111111111111111111111111111", + leadingZeroes: 0, trailingZeroes: 0, + leadingOnes: 64, trailingOnes: 64, }, { - name: "exactly 64 bits", - s: "0111111111111111111111111111111111111111111111111111111111111111", - leading: 1, - trailing: 0, + name: "exactly 64 bits", + s: "0111111111111111111111111111111111111111111111111111111111111111", + leadingZeroes: 1, trailingZeroes: 0, + leadingOnes: 0, trailingOnes: 63, }, { - name: "exactly 64 bits", - s: "1111111111111111111111111111111111111111111111111111111111111110", - leading: 0, - trailing: 1, + name: "exactly 64 bits", + s: "1111111111111111111111111111111111111111111111111111111111111110", + leadingZeroes: 0, trailingZeroes: 1, + leadingOnes: 63, trailingOnes: 0, }, { - name: "exactly 64 bits", - s: "1111111111111111111111111111111111111111111111111111111111111111", - leading: 0, - trailing: 0, + name: "exactly 64 bits", + s: "1111111111111111111111111111111111111111111111111111111111111111", + leadingZeroes: 0, trailingZeroes: 0, + leadingOnes: 64, trailingOnes: 64, }, { - name: "exactly 64 bits", - s: "0000000000000000000000000000000000000000000000000000000000000000", - leading: 64, - trailing: 64, + name: "exactly 64 bits", + s: "0000000000000000000000000000000000000000000000000000000000000000", + leadingZeroes: 64, trailingZeroes: 64, + leadingOnes: 0, trailingOnes: 0, }, { - name: "128 bits", - s: "11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111", - leading: 0, - trailing: 0, + name: "128 bits", + s: "11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111", + leadingZeroes: 0, trailingZeroes: 0, + leadingOnes: 128, trailingOnes: 128, }, { - name: "128 bits", - s: "01111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111", - leading: 1, - trailing: 0, + name: "128 bits", + s: "01111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111", + leadingZeroes: 1, trailingZeroes: 0, + leadingOnes: 0, trailingOnes: 127, }, { - name: "128 bits", - s: "11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111110", - leading: 0, - trailing: 1, + name: "128 bits", + s: "11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111110", + leadingZeroes: 0, trailingZeroes: 1, + leadingOnes: 127, trailingOnes: 0, }, { - name: "128 bits", - s: "00000000000000000000000000000000000000000000000000000000000000001111111111111111111111111111111111111111111111111111111111111111", - leading: 64, - trailing: 0, + name: "128 bits", + s: "00000000000000000000000000000000000000000000000000000000000000001111111111111111111111111111111111111111111111111111111111111111", + leadingZeroes: 64, trailingZeroes: 0, + leadingOnes: 0, trailingOnes: 64, }, { - name: "128 bits", - s: "11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111", - leading: 0, - trailing: 0, + name: "128 bits", + s: "11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111", + leadingZeroes: 0, trailingZeroes: 0, + leadingOnes: 128, trailingOnes: 128, }, { - name: "128 bits", - s: "00000000000000000000000000000000000000000000000000000000000000001111111111111111111111111111111111111111111111111111111111111111", - leading: 64, - trailing: 0, + name: "128 bits", + s: "00000000000000000000000000000000000000000000000000000000000000001111111111111111111111111111111111111111111111111111111111111111", + leadingZeroes: 64, trailingZeroes: 0, + leadingOnes: 0, trailingOnes: 64, }, { - name: "128 bits", - s: "11111111111111111111111111111111111111111111111111111111111111110000000000000000000000000000000000000000000000000000000000000000", - leading: 0, - trailing: 64, + name: "128 bits", + s: "11111111111111111111111111111111111111111111111111111111111111110000000000000000000000000000000000000000000000000000000000000000", + leadingZeroes: 0, trailingZeroes: 64, + leadingOnes: 64, trailingOnes: 0, }, { - name: "129 bits", - s: "111111111111111111111111111111111111111111111111111111111111111100000000000000000000000000000000000000000000000000000000000000000", - leading: 0, - trailing: 65, + name: "129 bits", + s: "111111111111111111111111111111111111111111111111111111111111111100000000000000000000000000000000000000000000000000000000000000000", + leadingZeroes: 0, trailingZeroes: 65, + leadingOnes: 64, trailingOnes: 0, }, { - name: "129 bits", - s: "000000000000000000000000000000000000000000000000000000000000000001111111111111111111111111111111111111111111111111111111111111111", - leading: 65, - trailing: 0, + name: "129 bits", + s: "000000000000000000000000000000000000000000000000000000000000000001111111111111111111111111111111111111111111111111111111111111111", + leadingZeroes: 65, trailingZeroes: 0, + leadingOnes: 0, trailingOnes: 64, }, { - name: "129 bits", - s: "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000111000", - leading: 123, - trailing: 3, + name: "129 bits", + s: "000000000000000000000000000000000000000000000000000000000000000011111111111111111111111111111111111111111111111111111111111111111", + leadingZeroes: 64, trailingZeroes: 0, + leadingOnes: 0, trailingOnes: 65, }, { - name: "129 bits", - s: "000111000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - leading: 3, - trailing: 123, + name: "129 bits", + s: "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000111000", + leadingZeroes: 123, trailingZeroes: 3, + leadingOnes: 0, trailingOnes: 0, }, { - name: "129 bits", - s: "111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111", - leading: 0, - trailing: 0, + name: "129 bits", + s: "111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111000111", + leadingZeroes: 0, trailingZeroes: 0, + leadingOnes: 123, trailingOnes: 3, }, { - name: "259 bits", - s: "0010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - leading: 2, - trailing: 136, + name: "129 bits", + s: "000111000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + leadingZeroes: 3, trailingZeroes: 123, + leadingOnes: 0, trailingOnes: 0, + }, + { + name: "129 bits", + s: "111000111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111", + leadingZeroes: 0, trailingZeroes: 0, + leadingOnes: 3, trailingOnes: 123, + }, + { + name: "129 bits", + s: "111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111", + leadingZeroes: 0, trailingZeroes: 0, + leadingOnes: 129, trailingOnes: 129, + }, + { + name: "259 bits", + s: "0010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + leadingZeroes: 2, trailingZeroes: 136, + leadingOnes: 0, trailingOnes: 0, + }, + { + name: "259 bits", + s: "1101111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111101111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111", + leadingZeroes: 0, trailingZeroes: 0, + leadingOnes: 2, trailingOnes: 136, }, } for _, tt := range tests { bs, _ := NewFromString(tt.s) t.Run(tt.name, func(t *testing.T) { - if got := bs.LeadingZeroes(); got != tt.leading { - t.Errorf("%q leading zeroes = %d, want %d", tt.s, got, tt.leading) + if got := bs.LeadingZeroes(); got != tt.leadingZeroes { + t.Errorf("%q leading zeroes = %d, want %d", tt.s, got, tt.leadingZeroes) } - if got := bs.TrailingZeroes(); got != tt.trailing { - t.Errorf("%q leading zeroes = %d, want %d", tt.s, got, tt.trailing) + if got := bs.TrailingZeroes(); got != tt.trailingZeroes { + t.Errorf("%q trailing zeroes = %d, want %d", tt.s, got, tt.trailingZeroes) + } + + if got := bs.LeadingOnes(); got != tt.leadingOnes { + t.Errorf("%q leading ones = %d, want %d", tt.s, got, tt.leadingOnes) + } + + if got := bs.TrailingOnes(); got != tt.trailingOnes { + t.Errorf("%q trailing ones = %d, want %d", tt.s, got, tt.trailingOnes) } // After reversing the bit string, leading and trailing zeroes must be swapped. bs.Reverse() - if got := bs.LeadingZeroes(); got != tt.trailing { - t.Errorf("reversed %q leading zeroes = %d, want %d", tt.s, got, tt.trailing) + if got := bs.LeadingZeroes(); got != tt.trailingZeroes { + t.Errorf("reversed %q leading zeroes = %d, want %d", tt.s, got, tt.trailingZeroes) + } + + if got := bs.TrailingZeroes(); got != tt.leadingZeroes { + t.Errorf("reversed %q trailing zeroes = %d, want %d", tt.s, got, tt.leadingZeroes) + } + + if got := bs.LeadingOnes(); got != tt.trailingOnes { + t.Errorf("reversed %q leading ones = %d, want %d", tt.s, got, tt.trailingOnes) } - if got := bs.TrailingZeroes(); got != tt.leading { - t.Errorf("reversed %q leading zeroes = %d, want %d", tt.s, got, tt.leading) + if got := bs.TrailingOnes(); got != tt.leadingOnes { + t.Errorf("reversed %q trailing ones = %d, want %d", tt.s, got, tt.leadingOnes) } }) } From db044914feacddae979c70cfbded60a650823a96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Rainone?= Date: Sun, 30 May 2021 22:14:23 +0200 Subject: [PATCH 02/19] Update README --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index b21039b..4296211 100644 --- a/README.md +++ b/README.md @@ -34,5 +34,4 @@ when building the `bitstring` package. **TODO**: - RotateLeft/Right ShiftLeft/Right - Or, And, Xor between bitstrings - - Reverse - - Run CI on big|little endian and 32|64 bits (for now only amd64) (see https://github.com/docker/setup-qemu-action) + - Run CI on big|little endian and 32|64 bits (with qemu) (see https://github.com/docker/setup-qemu-action) From 5cdc3f2782729b5f618baa9ac9dcc6f7caa6c5dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Rainone?= Date: Tue, 1 Jun 2021 22:28:51 +0200 Subject: [PATCH 03/19] .github: add dependabot --- .github/workflows/{test.yml => ci.yml} | 10 +++------- .github/workflows/dependabot.yml | 7 +++++++ 2 files changed, 10 insertions(+), 7 deletions(-) rename .github/workflows/{test.yml => ci.yml} (64%) create mode 100644 .github/workflows/dependabot.yml diff --git a/.github/workflows/test.yml b/.github/workflows/ci.yml similarity index 64% rename from .github/workflows/test.yml rename to .github/workflows/ci.yml index 504c5d9..672fa63 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/ci.yml @@ -2,19 +2,15 @@ on: [push, pull_request] name: Test jobs: test: - strategy: - matrix: - go-version: [1.15.x, 1.16.x] - os: [ubuntu-latest] - runs-on: ${{ matrix.os }} + runs-on: ubuntu-latest steps: - name: Install Go uses: actions/setup-go@v2 with: - go-version: ${{ matrix.go-version }} + go-version: 1.16.x - name: Checkout code uses: actions/checkout@v2 - - name: Test and coverage + - name: Test run: | go test -coverprofile coverage.txt go test -tags bitstring_debug . diff --git a/.github/workflows/dependabot.yml b/.github/workflows/dependabot.yml new file mode 100644 index 0000000..2c7d170 --- /dev/null +++ b/.github/workflows/dependabot.yml @@ -0,0 +1,7 @@ +version: 2 +updates: + # Maintain dependencies for GitHub Actions + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "daily" From 29bbb86bcf875e5892f8e7ac60041903b7563eb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Rainone?= Date: Wed, 2 Jun 2021 15:49:30 +0200 Subject: [PATCH 04/19] Add Bitstring.Flip() --- bitstring.go | 5 +++++ example_test.go | 9 +++++++++ 2 files changed, 14 insertions(+) diff --git a/bitstring.go b/bitstring.go index c0e0f4d..bb03ca9 100644 --- a/bitstring.go +++ b/bitstring.go @@ -177,6 +177,11 @@ func (bs *Bitstring) Reverse() { rightShiftBits(bs.data, bitoffset(uint64(64-bs.length))) } +// Flip flips all bits (replaces ones with zeroes and zeroes with ones). +func (bs *Bitstring) Flip() { + bs.FlipRange(0, bs.length) +} + // NewFromBig creates a new Bitstring using the absolute value of the big.Int // bi. // diff --git a/example_test.go b/example_test.go index 057cb09..b5919ee 100644 --- a/example_test.go +++ b/example_test.go @@ -126,3 +126,12 @@ func ExampleBitstring_FlipRange() { fmt.Println(bs) // Output: 11001100 } + +func ExampleBitstring_Flip() { + bs, _ := NewFromString("11110000") + + bs.Flip() + + fmt.Println(bs) + // Output: 00001111 +} From 38ec1ba983c1521f237e8f7257672e9f49784709 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Rainone?= Date: Wed, 2 Jun 2021 16:07:23 +0200 Subject: [PATCH 05/19] Cosmetics --- bitstring.go | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/bitstring.go b/bitstring.go index bb03ca9..7bf8bac 100644 --- a/bitstring.go +++ b/bitstring.go @@ -79,12 +79,16 @@ func NewFromString(s string) (*Bitstring, error) { return bs, nil } -// Len returns the length if bs, that is the number of bits it contains. +// Len returns the length of bs in bits. func (bs *Bitstring) Len() int { return int(bs.length) } -// Data returns the bitstring underlying slice. +// Bits returns raw access to the bitstring underlying uint64 slice. The result +// and bs share the same underlying array. +// +// Bits is intended to support implementation of missing low-level Bitstring +// functionality outside this package; it should be avoided otherwise. func (bs *Bitstring) Data() []uint64 { return bs.data } @@ -366,9 +370,9 @@ func (bs *Bitstring) TrailingZeroes() int { if i == last && bitoff != 0 && trailing == 64 { // There's one specific case we need to take care of: if the last - // word if 0 and the bitstring length is not a multiple of the - // wordsize, then the effective number of trailing bits is not 64, - // we need to limit it to the number of useful bits only. + // word is 0 and the bitstring length is not a multiple of 64 then + // the actual number of trailing bits is not 64, we need to limit it + // to the number of useful bits only. trailing = bitoff } From ccd1d58393d147e21e789d467827856291a43381 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Rainone?= Date: Wed, 2 Jun 2021 16:08:14 +0200 Subject: [PATCH 06/19] Improve Trailing/Leading Zeroes/Ones --- bitstring.go | 11 -- bitstring_test.go | 268 +++++++++------------------------------------- 2 files changed, 51 insertions(+), 228 deletions(-) diff --git a/bitstring.go b/bitstring.go index 7bf8bac..a747f0c 100644 --- a/bitstring.go +++ b/bitstring.go @@ -388,21 +388,10 @@ func (bs *Bitstring) TrailingZeroes() int { // (i.e the number of consecutives 1's starting from the LSB (least significant // bit). func (bs *Bitstring) TrailingOnes() int { - bitoff := int(bitoffset(uint64(bs.length))) - last := len(bs.data) - 1 - n := 0 for i := 0; i < len(bs.data); i++ { trailing := bits.TrailingZeros64(^bs.data[i]) - if i == last && bitoff != 0 && trailing == 64 { - // There's one specific case we need to take care of: if the last - // word if 0 and the bitstring length is not a multiple of the - // wordsize, then the effective number of trailing bits is not 64, - // we need to limit it to the number of useful bits only. - trailing = bitoff - } - n += trailing if trailing != 64 { break diff --git a/bitstring_test.go b/bitstring_test.go index 166a238..bfd0b20 100644 --- a/bitstring_test.go +++ b/bitstring_test.go @@ -245,222 +245,56 @@ func TestBig(t *testing.T) { }) } } - -func TestLeadingTrailingZeroesOnes(t *testing.T) { - tests := []struct { - name string - s string - leadingZeroes, trailingZeroes int - leadingOnes, trailingOnes int - }{ - { - name: "less than 64 bits", - s: "0", - leadingZeroes: 1, trailingZeroes: 1, - leadingOnes: 0, trailingOnes: 0, - }, - { - name: "less than 64 bits", - s: "1", - leadingZeroes: 0, trailingZeroes: 0, - leadingOnes: 1, trailingOnes: 1, - }, - { - name: "less than 64 bits", - s: "10", - leadingZeroes: 0, trailingZeroes: 1, - leadingOnes: 1, trailingOnes: 0, - }, - { - name: "less than 64 bits", - s: "01", - leadingZeroes: 1, trailingZeroes: 0, - leadingOnes: 0, trailingOnes: 1, - }, - { - name: "less than 64 bits", - s: "00", - leadingZeroes: 2, trailingZeroes: 2, - leadingOnes: 0, trailingOnes: 0, - }, - { - name: "less than 64 bits", - s: "11", - leadingZeroes: 0, trailingZeroes: 0, - leadingOnes: 2, trailingOnes: 2, - }, - { - name: "exactly 64 bits", - s: "1111111111111111111111111111111111111111111111111111111111111111", - leadingZeroes: 0, trailingZeroes: 0, - leadingOnes: 64, trailingOnes: 64, - }, - { - name: "exactly 64 bits", - s: "0111111111111111111111111111111111111111111111111111111111111111", - leadingZeroes: 1, trailingZeroes: 0, - leadingOnes: 0, trailingOnes: 63, - }, - { - name: "exactly 64 bits", - s: "1111111111111111111111111111111111111111111111111111111111111110", - leadingZeroes: 0, trailingZeroes: 1, - leadingOnes: 63, trailingOnes: 0, - }, - { - name: "exactly 64 bits", - s: "1111111111111111111111111111111111111111111111111111111111111111", - leadingZeroes: 0, trailingZeroes: 0, - leadingOnes: 64, trailingOnes: 64, - }, - { - name: "exactly 64 bits", - s: "0000000000000000000000000000000000000000000000000000000000000000", - leadingZeroes: 64, trailingZeroes: 64, - leadingOnes: 0, trailingOnes: 0, - }, - { - name: "128 bits", - s: "11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111", - leadingZeroes: 0, trailingZeroes: 0, - leadingOnes: 128, trailingOnes: 128, - }, - { - name: "128 bits", - s: "01111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111", - leadingZeroes: 1, trailingZeroes: 0, - leadingOnes: 0, trailingOnes: 127, - }, - { - name: "128 bits", - s: "11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111110", - leadingZeroes: 0, trailingZeroes: 1, - leadingOnes: 127, trailingOnes: 0, - }, - { - name: "128 bits", - s: "00000000000000000000000000000000000000000000000000000000000000001111111111111111111111111111111111111111111111111111111111111111", - leadingZeroes: 64, trailingZeroes: 0, - leadingOnes: 0, trailingOnes: 64, - }, - { - name: "128 bits", - s: "11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111", - leadingZeroes: 0, trailingZeroes: 0, - leadingOnes: 128, trailingOnes: 128, - }, - { - name: "128 bits", - s: "00000000000000000000000000000000000000000000000000000000000000001111111111111111111111111111111111111111111111111111111111111111", - leadingZeroes: 64, trailingZeroes: 0, - leadingOnes: 0, trailingOnes: 64, - }, - { - name: "128 bits", - s: "11111111111111111111111111111111111111111111111111111111111111110000000000000000000000000000000000000000000000000000000000000000", - leadingZeroes: 0, trailingZeroes: 64, - leadingOnes: 64, trailingOnes: 0, - }, - - { - name: "129 bits", - s: "111111111111111111111111111111111111111111111111111111111111111100000000000000000000000000000000000000000000000000000000000000000", - leadingZeroes: 0, trailingZeroes: 65, - leadingOnes: 64, trailingOnes: 0, - }, - { - name: "129 bits", - s: "000000000000000000000000000000000000000000000000000000000000000001111111111111111111111111111111111111111111111111111111111111111", - leadingZeroes: 65, trailingZeroes: 0, - leadingOnes: 0, trailingOnes: 64, - }, - { - name: "129 bits", - s: "000000000000000000000000000000000000000000000000000000000000000011111111111111111111111111111111111111111111111111111111111111111", - leadingZeroes: 64, trailingZeroes: 0, - leadingOnes: 0, trailingOnes: 65, - }, - { - name: "129 bits", - s: "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000111000", - leadingZeroes: 123, trailingZeroes: 3, - leadingOnes: 0, trailingOnes: 0, - }, - { - name: "129 bits", - s: "111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111000111", - leadingZeroes: 0, trailingZeroes: 0, - leadingOnes: 123, trailingOnes: 3, - }, - { - name: "129 bits", - s: "000111000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - leadingZeroes: 3, trailingZeroes: 123, - leadingOnes: 0, trailingOnes: 0, - }, - { - name: "129 bits", - s: "111000111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111", - leadingZeroes: 0, trailingZeroes: 0, - leadingOnes: 3, trailingOnes: 123, - }, - { - name: "129 bits", - s: "111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111", - leadingZeroes: 0, trailingZeroes: 0, - leadingOnes: 129, trailingOnes: 129, - }, - { - name: "259 bits", - s: "0010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - leadingZeroes: 2, trailingZeroes: 136, - leadingOnes: 0, trailingOnes: 0, - }, - { - name: "259 bits", - s: "1101111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111101111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111", - leadingZeroes: 0, trailingZeroes: 0, - leadingOnes: 2, trailingOnes: 136, - }, - } - - for _, tt := range tests { - bs, _ := NewFromString(tt.s) - t.Run(tt.name, func(t *testing.T) { - if got := bs.LeadingZeroes(); got != tt.leadingZeroes { - t.Errorf("%q leading zeroes = %d, want %d", tt.s, got, tt.leadingZeroes) - } - - if got := bs.TrailingZeroes(); got != tt.trailingZeroes { - t.Errorf("%q trailing zeroes = %d, want %d", tt.s, got, tt.trailingZeroes) - } - - if got := bs.LeadingOnes(); got != tt.leadingOnes { - t.Errorf("%q leading ones = %d, want %d", tt.s, got, tt.leadingOnes) - } - - if got := bs.TrailingOnes(); got != tt.trailingOnes { - t.Errorf("%q trailing ones = %d, want %d", tt.s, got, tt.trailingOnes) - } - - // After reversing the bit string, leading and trailing zeroes must be swapped. - bs.Reverse() - - if got := bs.LeadingZeroes(); got != tt.trailingZeroes { - t.Errorf("reversed %q leading zeroes = %d, want %d", tt.s, got, tt.trailingZeroes) - } - - if got := bs.TrailingZeroes(); got != tt.leadingZeroes { - t.Errorf("reversed %q trailing zeroes = %d, want %d", tt.s, got, tt.leadingZeroes) - } - - if got := bs.LeadingOnes(); got != tt.trailingOnes { - t.Errorf("reversed %q leading ones = %d, want %d", tt.s, got, tt.trailingOnes) - } - - if got := bs.TrailingOnes(); got != tt.leadingOnes { - t.Errorf("reversed %q trailing ones = %d, want %d", tt.s, got, tt.leadingOnes) - } - }) +func TestLeadingTrailing(t *testing.T) { + maxbits := 193 + + for nbits := 1; nbits < maxbits; nbits++ { + for i := 0; i < nbits; i++ { + bs := New(nbits) + + t.Run("only zeroes/"+bs.String(), func(t *testing.T) { + if bs.LeadingZeroes() != nbits { + t.Fatalf("%v leading zeroes = %d, want %d", bs, bs.LeadingZeroes(), nbits) + } + if bs.TrailingZeroes() != nbits { + t.Fatalf("%v trailing zeroes = %d, want %d", bs, bs.TrailingZeroes(), nbits) + } + }) + + bs.Flip() + + t.Run("only ones/"+bs.String(), func(t *testing.T) { + if bs.LeadingOnes() != nbits { + t.Fatalf("%v leading ones = %d, want %d", bs, bs.LeadingOnes(), nbits) + } + if bs.TrailingOnes() != nbits { + t.Fatalf("%v trailing ones = %d, want %d", bs, bs.TrailingOnes(), nbits) + } + }) + + bs.Flip() + + bs.SetBit(i) + + t.Run(bs.String(), func(t *testing.T) { + if bs.LeadingZeroes() != nbits-i-1 { + t.Fatalf("%v leading zeroes = %d, want %d", bs, bs.LeadingZeroes(), nbits-i-1) + } + if bs.TrailingZeroes() != i { + t.Fatalf("%v trailing zeroes = %d, want %d", bs, bs.TrailingZeroes(), i) + } + }) + + bs.Flip() + + t.Run(bs.String(), func(t *testing.T) { + if bs.LeadingOnes() != nbits-i-1 { + t.Fatalf("%v leading ones = %d, want %d", bs, bs.LeadingOnes(), nbits-i-1) + } + if bs.TrailingOnes() != i { + t.Fatalf("%v trailing ones = %d, want %d", bs, bs.TrailingOnes(), i) + } + }) + } } } From 5bb4bc62bf0865f894c11f2325048a1235ec38be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Rainone?= Date: Wed, 2 Jun 2021 16:38:33 +0200 Subject: [PATCH 07/19] Simplify msb/lsb --- bench_test.go | 28 --------------------- bitops.go | 67 ++++++-------------------------------------------- bitops_test.go | 4 +-- 3 files changed, 9 insertions(+), 90 deletions(-) diff --git a/bench_test.go b/bench_test.go index 91714be..67426b9 100644 --- a/bench_test.go +++ b/bench_test.go @@ -193,31 +193,3 @@ func BenchmarkOnesCount(b *testing.B) { b.Run("", func(b *testing.B) { benchmarkOnesCount(b, tt) }) } } - -func Benchmark_msb(b *testing.B) { - nums := []uint64{ - atobin("1100001000000000000000000000000100000000000000000000000000000000"), - atobin("01"), - atobin("10"), - atobin("10000000000100000000000000000001"), - atobin("110000000000100000000000000000001"), - atobin("00000000010001111111000000000100"), - atobin("00000001000001111111000000000000"), - atobin("01000000000000000000000000000000"), - atobin("1000000000000000000000000000000000000000000000000000000000000000"), - atobin("0100000000000000000000000000000000000000000000000000000000000000"), - atobin("1111111111111111111111111111111111111111111111111111111111111111"), - atobin("1011111111111111111111111111111111111111111111111111111111111111"), - atobin("1111111111111111111111111111111111111111111111111111111111111110"), - } - - b.ResetTimer() - var val uint64 - for i := 0; i < b.N; i++ { - for _, num := range nums { - val = msb(num) - } - } - - sink = val -} diff --git a/bitops.go b/bitops.go index 0727155..6966c2f 100644 --- a/bitops.go +++ b/bitops.go @@ -2,6 +2,7 @@ package bitstring import ( "math" + "math/bits" "unsafe" ) @@ -37,73 +38,19 @@ func transferbits(dst, src, mask uint64) uint64 { } // lsb returns the offset of the lowest significant set bit in v. That is, the -// index of the rightmost 1. +// index of the rightmost 1 in the bitstring string representation. // -// Note: lsb(0) is meaningless, it's the caller responsibility to not use the -// result of lsb(0). +// Warning: msb(0) = 64 but calling msb(0) makes no sense anyway. func lsb(v uint64) uint64 { - var num uint64 - - if (v & 0xffffffff) == 0 { - num += 32 - v >>= 32 - } - if (v & 0xffff) == 0 { - num += 16 - v >>= 16 - } - if (v & 0xff) == 0 { - num += 8 - v >>= 8 - } - if (v & 0xf) == 0 { - num += 4 - v >>= 4 - } - if (v & 0x3) == 0 { - num += 2 - v >>= 2 - } - if (v & 0x1) == 0 { - num++ - } - return num + return uint64(bits.TrailingZeros64(v)) } // msb returns the offset of the most significant set bit in v. That is, the -// index of the leftmost 1. +// index of the leftmost 1 in the bitstring string representation. // -// Note: msb(0) is meaningless, it's the caller responsibility to not use the -// result of msb(0). +// Warning: msb(0) = math.MaxUint64 but calling msb(0) makes no sense anyway. func msb(v uint64) uint64 { - var num uint64 - - if (v & 0xffffffff00000000) != 0 { - num += 32 - v >>= 32 - } - if (v & 0xffff0000) != 0 { - num += 16 - v >>= 16 - } - if (v & 0xff00) != 0 { - num += 8 - v >>= 8 - } - if (v & 0xf0) != 0 { - num += 4 - v >>= 4 - } - if (v & 0xc) != 0 { - num += 2 - v >>= 2 - } - if (v & 0x2) != 0 { - num++ - v >>= 1 - } - - return num + return uint64(63 - bits.LeadingZeros64(v)) } // fastmsbLittleEndian is faster version of msb that only works on little endian diff --git a/bitops_test.go b/bitops_test.go index d2a4c9c..3b8ead1 100644 --- a/bitops_test.go +++ b/bitops_test.go @@ -73,10 +73,10 @@ func Test_lsb(t *testing.T) { bits string want uint64 }{ - {bits: "11000010000000000000000000000001", want: 0}, // ok + {bits: "11000010000000000000000000000001", want: 0}, {bits: "01000001000000000000000000000010", want: 1}, {bits: "10000000000100000000000000000001", want: 0}, - {bits: "00000000010001111111000000000100", want: 2}, // ok + {bits: "00000000010001111111000000000100", want: 2}, {bits: "00000001000001111111000000000000", want: 12}, {bits: "10000000000000000000000000000000", want: 31}, {bits: "1000000000000000000000000000000000000000000000000000000000000000", want: 63}, From c7bdd1beb9b838dfbe905840a0ed7fec3663079b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Rainone?= Date: Wed, 2 Jun 2021 19:25:09 +0200 Subject: [PATCH 08/19] Remove unused code --- bitops.go | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/bitops.go b/bitops.go index 6966c2f..a159762 100644 --- a/bitops.go +++ b/bitops.go @@ -3,7 +3,6 @@ package bitstring import ( "math" "math/bits" - "unsafe" ) const wordsize = 32 << (^uint(0) >> 63) // 32 or 64 @@ -53,20 +52,6 @@ func msb(v uint64) uint64 { return uint64(63 - bits.LeadingZeros64(v)) } -// fastmsbLittleEndian is faster version of msb that only works on little endian -// architectures. About 50% faster than msb on amd64. Rely on the fact that Go -// uses IEEE 754 floating point representation. Converts v to float64, then -// extracts the exponent bits of the IEEE754 representation. -func fastmsbLittleEndian(v uint64) uint64 { - if v == math.MaxUint64 { - return 63 - } - - f := float64(v) - arr := *(*[2]uint32)(unsafe.Pointer(&f)) - return uint64(arr[1]>>20 - 1023) -} - func reverseBytes(buf []byte) []byte { for i := 0; i < len(buf)/2; i++ { buf[i], buf[len(buf)-i-1] = buf[len(buf)-i-1], buf[i] From 2b5a19278bcd7bdffead95ed5809c51b8abda7e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Rainone?= Date: Wed, 2 Jun 2021 19:25:30 +0200 Subject: [PATCH 09/19] Move all test helpers in helper_test.go --- helper_test.go | 51 ++++++++++++++++++++++++++++++++++++++++++++++++++ helpers.go | 41 ---------------------------------------- 2 files changed, 51 insertions(+), 41 deletions(-) delete mode 100644 helpers.go diff --git a/helper_test.go b/helper_test.go index 4e9ff73..3c2eeea 100644 --- a/helper_test.go +++ b/helper_test.go @@ -1,9 +1,12 @@ package bitstring import ( + "encoding/binary" "fmt" "strconv" + "strings" "testing" + "unsafe" ) func atobin(s string) uint64 { @@ -35,3 +38,51 @@ func equalbits(tb testing.TB, got, want *Bitstring) { } } } + +func sprintbuf(b []byte) string { + var sb strings.Builder + for i := range b { + fmt.Fprintf(&sb, "%08b ", b[i]) + } + + return sb.String() +} + +func printbuf(b []byte) { + fmt.Println(sprintbuf(b)) +} + +// returns a string representing the first n bits of the base-2 representation +// of val (unsigned). +func sprintubits(val uint64, nbits int) string { + return fmt.Sprintf(fmt.Sprintf("%%0%db", nbits), val) +} + +// returns a string representing the first n bits of the base-2 representation +// of val (signed). +func sprintsbits(val int64, nbits int) string { + if val < 0 { + // casting to uint will show us the 2's complement + return sprintubits(uint64(val), nbits) + } + return fmt.Sprintf(fmt.Sprintf("%%0%db", nbits), val) +} + +// prints a string representing the first n bits of the base-2 representation of val. +//lint:ignore U1000 (unused but useful for debugging) +func printbits(val, n int) { + fmt.Printf(fmt.Sprintf("%%0%db\n", n), val) +} + +var nativeEndian binary.ByteOrder + +func init() { + i := uint32(1) + b := (*[4]byte)(unsafe.Pointer(&i)) + if b[0] == 1 { + nativeEndian = binary.LittleEndian + } else { + nativeEndian = binary.BigEndian + } + fmt.Println(nativeEndian, wordsize) +} diff --git a/helpers.go b/helpers.go deleted file mode 100644 index 41c6360..0000000 --- a/helpers.go +++ /dev/null @@ -1,41 +0,0 @@ -package bitstring - -import ( - "fmt" - "strings" -) - -func sprintbuf(b []byte) string { - var sb strings.Builder - for i := range b { - fmt.Fprintf(&sb, "%08b ", b[i]) - } - - return sb.String() -} - -func printbuf(b []byte) { - fmt.Println(sprintbuf(b)) -} - -// returns a string representing the first n bits of the base-2 representation -// of val (unsigned). -func sprintubits(val uint64, nbits int) string { - return fmt.Sprintf(fmt.Sprintf("%%0%db", nbits), val) -} - -// returns a string representing the first n bits of the base-2 representation -// of val (signed). -func sprintsbits(val int64, nbits int) string { - if val < 0 { - // casting to uint will show us the 2's complement - return sprintubits(uint64(val), nbits) - } - return fmt.Sprintf(fmt.Sprintf("%%0%db", nbits), val) -} - -// prints a string representing the first n bits of the base-2 representation of val. -//lint:ignore U1000 (unused but useful for debugging) -func printbits(val, n int) { - fmt.Printf(fmt.Sprintf("%%0%db\n", n), val) -} From d985f5826e1a46d3dcc9d6dc1dda8f1e04b914a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Rainone?= Date: Wed, 2 Jun 2021 19:56:15 +0200 Subject: [PATCH 10/19] Drop testify in bistring_test.go --- bitstring_test.go | 138 ++++++++++++++++++++++++++++++---------------- helper_test.go | 10 ++++ 2 files changed, 102 insertions(+), 46 deletions(-) diff --git a/bitstring_test.go b/bitstring_test.go index bfd0b20..fc754c1 100644 --- a/bitstring_test.go +++ b/bitstring_test.go @@ -5,55 +5,68 @@ import ( "math/rand" "strings" "testing" - - "github.com/stretchr/testify/assert" ) func TestNew(t *testing.T) { bs := New(100) - assert.Equal(t, 100, bs.Len()) + if bs.Len() != 100 { + t.Errorf("len = %d, want 100", bs.Len()) + } + for i := 0; i < bs.Len(); i++ { - assert.False(t, bs.Bit(i)) + if bs.Bit(i) { + t.Error("bit i = 1, want 0") + } } } func TestRandom(t *testing.T) { rng := rand.New(rand.NewSource(99)) - - assert.Equal(t, Random(100, rng).Len(), 100) + bs := Random(100, rng) + if bs.Len() != 100 { + t.Errorf("len = %d, want 100", bs.Len()) + } } func TestSetBit(t *testing.T) { bs := New(69) + bits := make([]bool, 69) + bs.SetBit(1) bs.SetBit(4) - - assert.False(t, bs.Bit(0)) - assert.True(t, bs.Bit(1)) - assert.False(t, bs.Bit(2)) - assert.False(t, bs.Bit(3)) - assert.True(t, bs.Bit(4)) - - bs.ClearBit(4) - assert.False(t, bs.Bit(4)) + bs.SetBit(65) + bits[1] = true + bits[4] = true + bits[65] = true + checkBits(t, bits, bs) + + bs.ClearBit(65) + bits[65] = false + checkBits(t, bits, bs) } func TestFlipBit(t *testing.T) { bs := New(69) + bits := make([]bool, 69) + bs.FlipBit(2) - assert.True(t, bs.Bit(2)) + bits[2] = !bits[2] + checkBits(t, bits, bs) bs.FlipBit(2) - assert.False(t, bs.Bit(2)) + bits[2] = !bits[2] + checkBits(t, bits, bs) bs.FlipBit(67) - assert.True(t, bs.Bit(67)) + bits[67] = !bits[67] + checkBits(t, bits, bs) bs.FlipBit(67) - assert.False(t, bs.Bit(67)) + bits[67] = !bits[67] + checkBits(t, bits, bs) } func TestString(t *testing.T) { @@ -77,17 +90,16 @@ func TestString(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := NewFromString(tt.str) + bs, err := NewFromString(tt.str) if !tt.valid { - assert.Nil(t, got) - assert.Error(t, err) + if bs != nil || err == nil { + t.Errorf("invalid string, got (%s, %v) want (nil, error)", bs, err) + } return } - assert.NotNil(t, got) - assert.NoError(t, err) - - // Convert back to string - assert.Equal(t, tt.str, got.String()) + if bs == nil || err != nil || bs.String() != tt.str { + t.Errorf("valid string, got (%s, %v) want (%s, nil)", bs, err, tt.str) + } }) } } @@ -100,7 +112,6 @@ func TestReverse(t *testing.T) { "00000001", "11110000000000000000000000000000000000000000000000000000000000000111", "10000000000000000000000000000000000000000000000000000000000000011111", - fmt.Sprintf("%s000000000000000000", strings.Repeat("1", 1029)), "000000000000000000111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111", } for _, str := range tests { @@ -110,15 +121,22 @@ func TestReverse(t *testing.T) { bs.Reverse() - assert.Equal(t, string(reverseBytes([]byte(str))), bs.String()) - assert.Equal(t, ones, bs.OnesCount()) + revstr := string(reverseBytes([]byte(str))) + if bs.String() != revstr { + t.Errorf("reversed string = %s, want %s", bs.String(), revstr) + } + if bs.OnesCount() != ones { + t.Errorf("reversed string got %d ones, want %d", bs.OnesCount(), ones) + } }) } } func TestOnesCount(t *testing.T) { bs := New(65) - assert.Zero(t, bs.OnesCount()) + if bs.OnesCount() != 0 { + t.Fatalf("defaut constructed Bitstring shouldn't have any bit set, got %d", bs.OnesCount()) + } bs.SetBit(0) bs.SetBit(31) @@ -126,21 +144,30 @@ func TestOnesCount(t *testing.T) { bs.SetBit(33) bs.SetBit(63) bs.SetBit(64) - setBits := bs.OnesCount() - assert.EqualValues(t, 6, setBits) + + want := 6 + if got := bs.OnesCount(); got != want { + t.Errorf("%s got %d ones, want %d", bs, got, want) + } } func TestZeroesCount(t *testing.T) { bs := New(12) - assert.EqualValues(t, 12, bs.ZeroesCount()) + want := 12 + if got := bs.ZeroesCount(); got != want { + t.Errorf("%s got %d zeroes, want %d", bs, got, want) + } bs.SetBit(0) bs.SetBit(5) bs.SetBit(6) bs.SetBit(9) bs.SetBit(10) - setBits := bs.ZeroesCount() - assert.EqualValues(t, 7, setBits) + + want = 7 + if got := bs.ZeroesCount(); got != want { + t.Errorf("%s got %d zeroes, want %d", bs, got, want) + } } func TestClone(t *testing.T) { @@ -150,8 +177,7 @@ func TestClone(t *testing.T) { bs.SetBit(7) bs.SetBit(8) - cpy := bs.Clone() - equalbits(t, cpy, bs) + equalbits(t, bs.Clone(), bs) } func TestCopy(t *testing.T) { @@ -188,22 +214,36 @@ func TestEquals(t *testing.T) { org.SetBit(5) org.SetBit(8) - assert.True(t, org.Equals(org)) - assert.False(t, org.Equals(nil)) - assert.False(t, org.Equals(&Bitstring{})) + if !org.Equals(org) { + t.Errorf("org != org, want org == org") + } + if org.Equals(nil) { + t.Errorf("org == nil, want org != nil") + } + + if org.Equals(&Bitstring{}) { + t.Errorf("org == default constructed Bitstring, want !=") + } clone := org.Clone() - assert.Truef(t, clone.Equals(org), "different bitstrings, clone=%s, org=%s", clone, org) + if !clone.Equals(org) { + t.Errorf("different bitstrings, clone=%s, org=%s", clone, org) + } clone.FlipBit(0) - assert.Falsef(t, clone.Equals(org), "same bitstrings, clone=%s, org=%s", clone, org) + if clone.Equals(org) { + t.Errorf("same bitstrings, clone=%s, org=%s", clone, org) + } // Bitstrings of different lengths but with the same bits set should not be equal. bs2 := New(9) bs2.SetBit(2) bs2.SetBit(5) bs2.SetBit(8) - assert.False(t, bs2.Equals(org)) + + if bs2.Equals(org) { + t.Errorf("same bitstrings, bs2=%s, org=%s", bs2, org) + } } func trimLeadingZeroes(s string) string { @@ -233,7 +273,10 @@ func TestBig(t *testing.T) { bs, _ := NewFromString(num) bi := bs.BigInt() sbig := fmt.Sprintf("%b", bi) - assert.Equal(t, trimLeadingZeroes(num), sbig) + want := trimLeadingZeroes(num) + if sbig != want { + t.Errorf("got big = %s, want %s", sbig, want) + } // Create a bitstring from the binary string representation, create // a Bitstring from it and compare the significant bits. @@ -241,7 +284,10 @@ func TestBig(t *testing.T) { // Remove leading zeroes since a big int doesn't have leading zeroes. got := bs.String()[bs.LeadingZeroes():] - assert.Equal(t, got, bs2.String()) + want = bs2.String() + if got != want { + t.Errorf("got big = %s, want %s", got, want) + } }) } } diff --git a/helper_test.go b/helper_test.go index 3c2eeea..341c40b 100644 --- a/helper_test.go +++ b/helper_test.go @@ -86,3 +86,13 @@ func init() { } fmt.Println(nativeEndian, wordsize) } + +func checkBits(t *testing.T, bits []bool, bs *Bitstring) { + t.Helper() + + for i, bit := range bits { + if bs.Bit(i) != bit { + t.Errorf("bit %d = %t, want %t", i, bs.Bit(i), bit) + } + } +} From 30b7518987a972346ee5915fbb2bf413928de0e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Rainone?= Date: Wed, 2 Jun 2021 22:41:26 +0200 Subject: [PATCH 11/19] Simplify tests --- bitops_test.go | 80 +++++++++++++++++++++++------------------------ bitstring_test.go | 2 +- go.mod | 2 +- helper_test.go | 67 +++++++++++++++++---------------------- 4 files changed, 71 insertions(+), 80 deletions(-) diff --git a/bitops_test.go b/bitops_test.go index 3b8ead1..51340ee 100644 --- a/bitops_test.go +++ b/bitops_test.go @@ -13,9 +13,9 @@ func Test_lomask(t *testing.T) { n uint64 want uint64 }{ - {n: 0, want: atobin("00000000000000000000000000000000")}, - {n: 1, want: atobin("00000000000000000000000000000001")}, - {n: 2, want: atobin("00000000000000000000000000000011")}, + {n: 0, want: 0b00000000000000000000000000000000}, + {n: 1, want: 0b00000000000000000000000000000001}, + {n: 2, want: 0b00000000000000000000000000000011}, {n: 64 - 2, want: math.MaxUint64 >> 2}, {n: 64 - 1, want: math.MaxUint64 >> 1}, {n: 64, want: math.MaxUint64}, @@ -49,44 +49,44 @@ func Test_himask(t *testing.T) { func Test_genmask(t *testing.T) { tests := []struct { l, h uint64 - want string + want uint64 }{ - {l: 0, h: 0, want: "00000000000000000000000000000000"}, - {l: 0, h: 1, want: "00000000000000000000000000000001"}, - {l: 0, h: 2, want: "00000000000000000000000000000011"}, - {l: 1, h: 1, want: "00000000000000000000000000000000"}, - {l: 1, h: 2, want: "00000000000000000000000000000010"}, - {l: 0, h: 31, want: "01111111111111111111111111111111"}, - {l: 1, h: 31, want: "01111111111111111111111111111110"}, - {l: 0, h: 30, want: "00111111111111111111111111111111"}, + {l: 0, h: 0, want: 0b00000000000000000000000000000000}, + {l: 0, h: 1, want: 0b00000000000000000000000000000001}, + {l: 0, h: 2, want: 0b00000000000000000000000000000011}, + {l: 1, h: 1, want: 0b00000000000000000000000000000000}, + {l: 1, h: 2, want: 0b00000000000000000000000000000010}, + {l: 0, h: 31, want: 0b01111111111111111111111111111111}, + {l: 1, h: 31, want: 0b01111111111111111111111111111110}, + {l: 0, h: 30, want: 0b00111111111111111111111111111111}, } for _, tt := range tests { - if got := mask(tt.l, tt.h); got != atobin(tt.want) { - t.Errorf("mask(%d, %d) got %s, want %s", tt.l, tt.h, - sprintubits(got, 32), tt.want) + if got := mask(tt.l, tt.h); got != tt.want { + t.Errorf("mask(%d, %d) got %02b, want %02b", tt.l, tt.h, + got, tt.want) } } } func Test_lsb(t *testing.T) { tests := []struct { - bits string + bits uint64 want uint64 }{ - {bits: "11000010000000000000000000000001", want: 0}, - {bits: "01000001000000000000000000000010", want: 1}, - {bits: "10000000000100000000000000000001", want: 0}, - {bits: "00000000010001111111000000000100", want: 2}, - {bits: "00000001000001111111000000000000", want: 12}, - {bits: "10000000000000000000000000000000", want: 31}, - {bits: "1000000000000000000000000000000000000000000000000000000000000000", want: 63}, - {bits: "11111111111111111111111111111111", want: 0}, + {bits: 0b11000010000000000000000000000001, want: 0}, + {bits: 0b01000001000000000000000000000010, want: 1}, + {bits: 0b10000000000100000000000000000001, want: 0}, + {bits: 0b00000000010001111111000000000100, want: 2}, + {bits: 0b00000001000001111111000000000000, want: 12}, + {bits: 0b10000000000000000000000000000000, want: 31}, + {bits: 0b1000000000000000000000000000000000000000000000000000000000000000, want: 63}, + {bits: 0b11111111111111111111111111111111, want: 0}, } for _, tt := range tests { t.Run("", func(t *testing.T) { - got := lsb(atobin(tt.bits)) + got := lsb(tt.bits) if tt.want != got { - t.Errorf("lsb(%v) = %d, want %d", tt.bits, got, tt.want) + t.Errorf("lsb(%02b) = %d, want %d", tt.bits, got, tt.want) } }) } @@ -94,26 +94,26 @@ func Test_lsb(t *testing.T) { func Test_msb(t *testing.T) { tests := []struct { - bits string + bits uint64 want uint64 }{ - {bits: "1100001000000000000000000000000100000000000000000000000000000000", want: 63}, - {bits: "01", want: 0}, - {bits: "10", want: 1}, - {bits: "10000000000100000000000000000001", want: 31}, - {bits: "110000000000100000000000000000001", want: 32}, - {bits: "00000000010001111111000000000100", want: 22}, - {bits: "00000001000001111111000000000000", want: 24}, - {bits: "01000000000000000000000000000000", want: 30}, - {bits: "1000000000000000000000000000000000000000000000000000000000000000", want: 63}, - {bits: "0100000000000000000000000000000000000000000000000000000000000000", want: 62}, - {bits: "1111111111111111111111111111111111111111111111111111111111111111", want: 63}, + {bits: 0b1100001000000000000000000000000100000000000000000000000000000000, want: 63}, + {bits: 0b01, want: 0}, + {bits: 0b10, want: 1}, + {bits: 0b10000000000100000000000000000001, want: 31}, + {bits: 0b110000000000100000000000000000001, want: 32}, + {bits: 0b00000000010001111111000000000100, want: 22}, + {bits: 0b00000001000001111111000000000000, want: 24}, + {bits: 0b01000000000000000000000000000000, want: 30}, + {bits: 0b1000000000000000000000000000000000000000000000000000000000000000, want: 63}, + {bits: 0b0100000000000000000000000000000000000000000000000000000000000000, want: 62}, + {bits: 0b1111111111111111111111111111111111111111111111111111111111111111, want: 63}, } for _, tt := range tests { t.Run("", func(t *testing.T) { - got := msb(atobin(tt.bits)) + got := msb(tt.bits) if tt.want != got { - t.Errorf("msb(%v) = %d, want %d", tt.bits, got, tt.want) + t.Errorf("msb(%02b) = %d, want %d", tt.bits, got, tt.want) } }) } diff --git a/bitstring_test.go b/bitstring_test.go index fc754c1..abfbbbe 100644 --- a/bitstring_test.go +++ b/bitstring_test.go @@ -135,7 +135,7 @@ func TestReverse(t *testing.T) { func TestOnesCount(t *testing.T) { bs := New(65) if bs.OnesCount() != 0 { - t.Fatalf("defaut constructed Bitstring shouldn't have any bit set, got %d", bs.OnesCount()) + t.Fatalf("default constructed Bitstring shouldn't have any bit set, got %d", bs.OnesCount()) } bs.SetBit(0) diff --git a/go.mod b/go.mod index d37e08a..77a0aea 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,5 @@ module github.com/arl/bitstring -go 1.0 +go 1.13 require github.com/stretchr/testify v1.7.0 diff --git a/helper_test.go b/helper_test.go index 341c40b..e4ac39f 100644 --- a/helper_test.go +++ b/helper_test.go @@ -3,20 +3,13 @@ package bitstring import ( "encoding/binary" "fmt" - "strconv" + "os" + "runtime" "strings" "testing" "unsafe" ) -func atobin(s string) uint64 { - i, err := strconv.ParseUint(s, 2, 64) - if err != nil { - panic(fmt.Sprintf("Can't convert %s to base 2: %s", s, err)) - } - return i -} - func equalbits(tb testing.TB, got, want *Bitstring) { tb.Helper() @@ -39,6 +32,31 @@ func equalbits(tb testing.TB, got, want *Bitstring) { } } +func checkBits(tb testing.TB, bits []bool, bs *Bitstring) { + tb.Helper() + + for i, bit := range bits { + if bs.Bit(i) != bit { + tb.Errorf("bit %d = %t, want %t", i, bs.Bit(i), bit) + } + } +} + +func nativeEndian() binary.ByteOrder { + i := uint32(1) + b := (*[4]byte)(unsafe.Pointer(&i)) + if b[0] == 1 { + return binary.LittleEndian + } + + return binary.BigEndian +} + +func TestMain(m *testing.M) { + fmt.Printf("%s: %s %d-bit\n", runtime.GOARCH, nativeEndian(), wordsize) + os.Exit(m.Run()) +} + func sprintbuf(b []byte) string { var sb strings.Builder for i := range b { @@ -48,6 +66,7 @@ func sprintbuf(b []byte) string { return sb.String() } +//lint:ignore U1000 useful for debugging func printbuf(b []byte) { fmt.Println(sprintbuf(b)) } @@ -60,6 +79,7 @@ func sprintubits(val uint64, nbits int) string { // returns a string representing the first n bits of the base-2 representation // of val (signed). +//lint:ignore U1000 useful for debugging func sprintsbits(val int64, nbits int) string { if val < 0 { // casting to uint will show us the 2's complement @@ -67,32 +87,3 @@ func sprintsbits(val int64, nbits int) string { } return fmt.Sprintf(fmt.Sprintf("%%0%db", nbits), val) } - -// prints a string representing the first n bits of the base-2 representation of val. -//lint:ignore U1000 (unused but useful for debugging) -func printbits(val, n int) { - fmt.Printf(fmt.Sprintf("%%0%db\n", n), val) -} - -var nativeEndian binary.ByteOrder - -func init() { - i := uint32(1) - b := (*[4]byte)(unsafe.Pointer(&i)) - if b[0] == 1 { - nativeEndian = binary.LittleEndian - } else { - nativeEndian = binary.BigEndian - } - fmt.Println(nativeEndian, wordsize) -} - -func checkBits(t *testing.T, bits []bool, bs *Bitstring) { - t.Helper() - - for i, bit := range bits { - if bs.Bit(i) != bit { - t.Errorf("bit %d = %t, want %t", i, bs.Bit(i), bit) - } - } -} From d4ff23627422e35f323355c388e447e93d5e19bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Rainone?= Date: Wed, 2 Jun 2021 22:46:17 +0200 Subject: [PATCH 12/19] Cosmetics --- bitops.go | 7 ++++--- bitops_test.go | 4 ++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/bitops.go b/bitops.go index a159762..4f4e705 100644 --- a/bitops.go +++ b/bitops.go @@ -73,11 +73,12 @@ func rightShiftBits(words []uint64, n uint64) { } } -// if n is a power of 2, ispow2 returns (v, true) such that (1< Date: Wed, 2 Jun 2021 22:46:25 +0200 Subject: [PATCH 13/19] Update README --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 4296211..ca16681 100644 --- a/README.md +++ b/README.md @@ -34,4 +34,3 @@ when building the `bitstring` package. **TODO**: - RotateLeft/Right ShiftLeft/Right - Or, And, Xor between bitstrings - - Run CI on big|little endian and 32|64 bits (with qemu) (see https://github.com/docker/setup-qemu-action) From 37d992df3d74d98a94d40b4353b467dfd3b17c63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Rainone?= Date: Thu, 3 Jun 2021 12:50:12 +0200 Subject: [PATCH 14/19] Move reverse lut generator in internal/reverse_lut --- reverse_lut_generate.go => internal/reverse_lut/generate.go | 4 ++-- reverse_lut_gen.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) rename reverse_lut_generate.go => internal/reverse_lut/generate.go (90%) diff --git a/reverse_lut_generate.go b/internal/reverse_lut/generate.go similarity index 90% rename from reverse_lut_generate.go rename to internal/reverse_lut/generate.go index ca91644..d7c8b95 100644 --- a/reverse_lut_generate.go +++ b/internal/reverse_lut/generate.go @@ -10,8 +10,8 @@ import ( "strings" ) -// This program generates the lookup table used to reverse the bits in a byte, -// based on https://graphics.stanford.edu/~seander/bithacks.html#BitReverseTable +// This program generates the lookup table used to reverse bits in a byte in O(1). +// Based on https://graphics.stanford.edu/~seander/bithacks.html#BitReverseTable func main() { var sb strings.Builder diff --git a/reverse_lut_gen.go b/reverse_lut_gen.go index c2f0100..de82e19 100644 --- a/reverse_lut_gen.go +++ b/reverse_lut_gen.go @@ -1,3 +1,3 @@ package bitstring -//go:generate go run reverse_lut_generate.go +//go:generate go run internal/reverse_lut/generate.go From 985ef6e11926716f5e09a42449bc441c6ca30be4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Rainone?= Date: Thu, 17 Jun 2021 14:15:52 +0200 Subject: [PATCH 15/19] bitops: cosmetics --- bitops.go | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/bitops.go b/bitops.go index 4f4e705..fbcad83 100644 --- a/bitops.go +++ b/bitops.go @@ -8,27 +8,39 @@ import ( const wordsize = 32 << (^uint(0) >> 63) // 32 or 64 // bitmask returns a mask where only the nth bit of a word is set. -func bitmask(n uint64) uint64 { return 1 << n } +func bitmask(n uint64) uint64 { + return 1 << n +} // wordoffset returns, for a given bit n of a bit string, the offset // of the word that contains bit n. -func wordoffset(n uint64) uint64 { return n / 64 } +func wordoffset(n uint64) uint64 { + return n / 64 +} // bitoffset returns, for a given bit n of a bit string, the offset of that bit // with respect to the first bit of the word that contains it. -func bitoffset(n uint64) uint64 { return n & (64 - 1) } +func bitoffset(n uint64) uint64 { + return n & (64 - 1) +} // mask returns a mask that keeps the bits in the range [l, h) behavior // undefined if any argument is greater than the size of a machine word. -func mask(l, h uint64) uint64 { return lomask(h) & himask(l) } +func mask(l, h uint64) uint64 { + return lomask(h) & himask(l) +} -// lomask returns a mask to keep the n LSB (least significant bits). Undefined +// lomask returns a mask where the n least significant bits are set. Undefined // behavior if n is greater than 64. -func lomask(n uint64) uint64 { return math.MaxUint64 >> (64 - n) } +func lomask(n uint64) uint64 { + return math.MaxUint64 >> (64 - n) +} -// himask returns a mask to keep the n MSB (most significant bits). Undefined -// behavior if n is greater than 64. -func himask(n uint64) uint64 { return math.MaxUint64 << n } +// himask returns a mask where the most significant bits are set, starting from +// offset n. Undefined behavior if n is greater than 64. +func himask(n uint64) uint64 { + return math.MaxUint64 << n +} // transferbits returns the word that results from transferring some bits from // src to dst, where set bits in mask specify the bits to transfer. From e23b8f44f68caa4573aed74244749ecc2947c04a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Rainone?= Date: Thu, 17 Jun 2021 14:16:13 +0200 Subject: [PATCH 16/19] RotateLeft + tests [WIP] --- bitstring.go | 171 ++++++++++++++++++++++++++++-- bitstring_test.go | 258 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 423 insertions(+), 6 deletions(-) diff --git a/bitstring.go b/bitstring.go index a747f0c..5247b69 100644 --- a/bitstring.go +++ b/bitstring.go @@ -400,14 +400,173 @@ func (bs *Bitstring) TrailingOnes() int { return n } -/* -// RotateLeft rotates the bitstring by (k mod len) bits. -func (bs *Bitstring) RotateLeft(k int) { - panic("unimplemented") +func reverse(buf []uint64) []uint64 { + for i := 0; i < len(buf)/2; i++ { + buf[i], buf[len(buf)-i-1] = buf[len(buf)-i-1], buf[i] + } + return buf +} + +func rotate3(nums []uint64, k int) { + k = k % len(nums) + if k < 0 { + panic(fmt.Sprintf("rotate3 with k negative (%d)", k)) + } + if k == 0 { + return + } + + // fmt.Printf("rotating left(%d) -> %v\n", k, nums) + reverse(nums) + // fmt.Println("reverse -> ", nums) + reverse(nums[:k]) + // fmt.Println("reverse -> ", nums) + reverse(nums[k:]) + // fmt.Println("reverse -> ", nums) } -// RotateRight rotates the bitstring by (k mod len) bits. +// RotateRight rotates the bitstring by k bits to the right. func (bs *Bitstring) RotateRight(k int) { - panic("unimplemented") + bs.RotateLeft(bs.length - k%bs.length) +} + +// RotateLeft rotates the bitstring by k bits to the left. +// TODO: document whether k can be negative +func (bs *Bitstring) rotateLeft(k int) { + // Remove full circles; reduce k to its smallest equivalent value. + k %= bs.length + + // Before digging into bit twiddling, we first rotate bs by the largest + // multiple of 64 we can, we do a rotation of the slice elements. + kwords := k / 64 + if kwords != 0 { + rotate3(bs.data, kwords) + } + + // XXXX XXXX XXXX + + // kbits is the number of bits we must rotate bs to the left in order to + // complete the full rotation. Rotate each element of the bs.data slice of + // kbits to the left, and carry the shifted kbits to the next element. + kbits := k % 64 + if kbits == 0 { + return + } + + carry := uint64(0) + for i := 0; i < len(bs.data); i++ { + w := bits.RotateLeft64(bs.data[i], kbits) + tmp := w & lomask(uint64(kbits)) // extract the range of bits to carry over to next word. + w &= ^lomask(uint64(kbits)) // clear the range of bits of w before applying carry from previous word. + w |= carry + bs.data[i], carry = w, tmp + } + + // look for interesting stuff in /usr/local/go/src/runtime/mpallocbits.go + + // Report last word carry onto the first word. + bs.data[0] |= carry + + // nlastbits := bs.length % 64 // number of bits in the last word. + + // carry |= mask(uint64(nlastbits), uint64(nlastbits+kbits)) >> nlastbits + // bs.data[0] |= carry + // bs.data[len(bs.data)-1] &= lomask((uint64(kbits))) + // nlastbits = 4 + // k = 10 + // 0000000000000000000000000000000000000000000000000000000000001111 + + // 0000000000000000000000000000000000000000000000000011110000000000 + +} +func (bs *Bitstring) RotateLeft(k int) { + bs.rotateLeft(k) +} + +/* +// RotateLeft rotates the bitstring by k bits to the left. +func (bs *Bitstring) RotateLeft(k int) { + fmt.Println("RotateLeft(", k, ")") + // Reduce k to its smallest value. + k %= bs.length + + // First move words. + nwords := k / 64 + if nwords != 0 { + rotate3(bs.data, nwords) + } + + // Remaining bits to move (i.e always less than 64). + lastbits := uint64(bs.length % 64) + if lastbits == 0 { + return + } + + kbits := uint64(k - nwords*64) + // if nbits == 0 { + // return + // } + + fmt.Println("k bits to move", kbits, "nbits in last word ", lastbits) + if uint64(bs.length%64) == 0 { + panic("impossible") + } + + var carry uint64 + for i := 0; i < len(bs.data); i++ { + w := bs.data[i] + hmask := himask(64 - kbits) + // fmt.Printf("%2b\n", ) + tmp := w & hmask + tmp >>= (64 - kbits) + w <<= kbits + w |= carry + carry = tmp + bs.data[i] = w + } + + if lastbits+kbits > 64 { + } else { + // all the bits of the + } + + // use circular shift for the last word? + // https://en.wikipedia.org/wiki/Circular_shift + + // var carry uint64 + // for i := 0; i < len(bs.data); i++ { + // bs.data[i], carry = bs.data[i]<>(64-nbits) + // } + fmt.Printf("before last bs.data[len(bs.data)-1] %v\n", bs.data) + + // 10000 + // ____ + + nbitslastw := uint64(bs.length % 64) // number of useful bits of last word + bs.data[0] |= (carry >> (64 - nbitslastw)) + + carry = bs.data[len(bs.data)-1] & mask(nbitslastw, nbitslastw+nbits) + carry = carry >> nbitslastw + bs.data[0] |= carry + + + // // work but not for 37 + // nbitslastw := uint64(bs.length % 64) // number of useful bits of last word + // bs.data[0] |= (carry >> (nbitslastw)) + + // carry = bs.data[len(bs.data)-1] & mask(nbitslastw, nbitslastw+nbits) + // carry = carry >> nbitslastw + // bs.data[0] |= carry + + + // carry = bs.data[len(bs.data)-1] & mask(nbitslastw, nbitslastw+nbits) + // bs.data[0] |= (carry >> (nbitslastw)) + // bs.data[len(bs.data)-1] &= lomask(nbitslastw) + + // lastbits := uint64(bs.length % 64) // number of useful bits of last word + // bs.data[0] |= (bs.data[len(bs.data)-1] & mask(lastbits, lastbits+nbits)) >> lastbits + // bs.data[len(bs.data)-1] &= lomask(lastbits) + / + } */ diff --git a/bitstring_test.go b/bitstring_test.go index abfbbbe..91addf6 100644 --- a/bitstring_test.go +++ b/bitstring_test.go @@ -344,3 +344,261 @@ func TestLeadingTrailing(t *testing.T) { } } } + +func TestRotate(t *testing.T) { + tests := []struct { + num string + k int + want string + }{ + // k is a multiple of 64 bits + // { + // num: "0000000000000000000000000000000000000000000000000000000000000111" + "0000000000000000000000000000000000000000000000000000000000000011" + "0000000000000000000000000000000000000000000000000000000000000001" + "0000000000000000000000000000000000000000000000000000000000000000", + // k: 0, + // want: "0000000000000000000000000000000000000000000000000000000000000111" + "0000000000000000000000000000000000000000000000000000000000000011" + "0000000000000000000000000000000000000000000000000000000000000001" + "0000000000000000000000000000000000000000000000000000000000000000", + // }, + // { + // num:/* 3 */ "0000000000000000000000000000000000000000000000000000000000000111" + /* 2 */ "0000000000000000000000000000000000000000000000000000000000000011" + /* 1 */ "0000000000000000000000000000000000000000000000000000000000000001" + /* 0 */ "0000000000000000000000000000000000000000000000000000000000000000", + // k: 64, + // want:/* 2 */ "0000000000000000000000000000000000000000000000000000000000000011" + /* 1 */ "0000000000000000000000000000000000000000000000000000000000000001" + /* 0 */ "0000000000000000000000000000000000000000000000000000000000000000" + /* 3 */ "0000000000000000000000000000000000000000000000000000000000000111", + // }, + // { + // num:/* 3 */ "0000000000000000000000000000000000000000000000000000000000000111" + /* 2 */ "0000000000000000000000000000000000000000000000000000000000000011" + /* 1 */ "0000000000000000000000000000000000000000000000000000000000000001" + /* 0 */ "0000000000000000000000000000000000000000000000000000000000000000", + // k: 128, + // want:/* 1 */ "0000000000000000000000000000000000000000000000000000000000000001" + /* 0 */ "0000000000000000000000000000000000000000000000000000000000000000" + /* 3 */ "0000000000000000000000000000000000000000000000000000000000000111" + /* 2 */ "0000000000000000000000000000000000000000000000000000000000000011", + // }, + // { + // num:/* 3 */ "0000000000000000000000000000000000000000000000000000000000000111" + /* 2 */ "0000000000000000000000000000000000000000000000000000000000000011" + /* 1 */ "0000000000000000000000000000000000000000000000000000000000000001" + /* 0 */ "0000000000000000000000000000000000000000000000000000000000000000", + // k: 192, + // want:/* 0 */ "0000000000000000000000000000000000000000000000000000000000000000" + /* 3 */ "0000000000000000000000000000000000000000000000000000000000000111" + /* 2 */ "0000000000000000000000000000000000000000000000000000000000000011" + /* 1 */ "0000000000000000000000000000000000000000000000000000000000000001", + // }, + // { + // num:/* 3 */ "0000000000000000000000000000000000000000000000000000000000000111" + /* 2 */ "0000000000000000000000000000000000000000000000000000000000000011" + /* 1 */ "0000000000000000000000000000000000000000000000000000000000000001" + /* 0 */ "0000000000000000000000000000000000000000000000000000000000000000", + // k: 256, + // want:/* 3 */ "0000000000000000000000000000000000000000000000000000000000000111" + /* 2 */ "0000000000000000000000000000000000000000000000000000000000000011" + /* 1 */ "0000000000000000000000000000000000000000000000000000000000000001" + /* 0 */ "0000000000000000000000000000000000000000000000000000000000000000", + // }, + + // // shorter than 64 bits + // { + // num: "0", + // k: 1, + // want: "0", + // }, + // { + // num: "1", + // k: 63, + // want: "1", + // }, + // { + // num: "01010101", + // k: 0, + // want: "01010101", + // }, + // { + // num: "1000", + // k: 1, + // want: "0001", + // }, + // { + // num: "1001", + // k: 2, + // want: "0110", + // }, + // { + // num: "1001", + // k: 2, + // want: "0110", + // }, + + { + num: "111111111111111111111111111111111111100000000000000000000000000", + k: 37, + want: "000000000000000000000000001111111111111111111111111111111111111", + }, + + // longer than 64 bits + { + num: "0000000000000000000000000000000000000000000000000000000000001100", + k: 62, + want: "0000000000000000000000000000000000000000000000000000000000000011", + }, + { + num: "10000000000000000000000000000000000000000000000000000000000000001", + k: 3, + want: "00000000000000000000000000000000000000000000000000000000000001100", + }, + { + num: "10000000000000000000000000000000000000000000000000000000000000001", + k: 3, + want: "00000000000000000000000000000000000000000000000000000000000001100", + }, + { + num: "00000000000000000000000000000000000000000000000000000000000001100", + k: 62, + want: "10000000000000000000000000000000000000000000000000000000000000001", + }, + } + + for _, tt := range tests { + t.Run(fmt.Sprintf("%d/%s", tt.k, tt.num), func(t *testing.T) { + bs, err := NewFromString(tt.num) + if err != nil { + t.Fatal(err) + } + + bs.RotateLeft(tt.k) + if bs.String() != tt.want { + t.Fatalf("RotateLeft wrong value\n\tgot:\n%s\n\n\twant:\n%s", groupBy64(bs.String()), groupBy64(tt.want)) + } + + bs.RotateRight(tt.k) + if bs.String() != tt.num { + t.Fatalf("RotateRight wrong value\n\tgot:\n%s\n\n\twant:\n%s", groupBy64(bs.String()), groupBy64(tt.num)) + } + // ensure than number of 1s and 0 has remained the same through the operations or this would mess other functions + }) + } +} + +func Test_rotateLeft(t *testing.T) { + tests := []struct { + num string + k int + want string + }{ + // len < 64. + // { + // num: "1", + // k: 1, + // want: "1", + // }, + // { + // num: "1", + // k: 3, + // want: "1", + // }, + { + num: "101", + k: 1, + want: "011", + }, + + // { + // num: "1001", + // k: 2, + // want: "0110", + // }, + // { + // num: "111111111111111111111111111111111111100000000000000000000000000", + // k: 37, + // want: "000000000000000000000000001111111111111111111111111111111111111", + // }, + + // len and k are both multiples of 64. + { + num: "1111111100000000000000000000000000000000000000000000000000000111" + "1111100000000000000000000000000000000000000000000000000000000011" + "1111000000000000000000000000000000000000000000000000000000000001" + "1110000000000000000000000000000000000000000000000000000000000000", + k: 0, + want: "1111111100000000000000000000000000000000000000000000000000000111" + "1111100000000000000000000000000000000000000000000000000000000011" + "1111000000000000000000000000000000000000000000000000000000000001" + "1110000000000000000000000000000000000000000000000000000000000000", + }, + { + num:/* 3 */ "1111111100000000000000000000000000000000000000000000000000000111" + /* 2 */ "1111100000000000000000000000000000000000000000000000000000000011" + /* 1 */ "1111000000000000000000000000000000000000000000000000000000000001" + /* 0 */ "1110000000000000000000000000000000000000000000000000000000000000", + k: 64, + want:/* 2 */ "1111100000000000000000000000000000000000000000000000000000000011" + /* 1 */ "1111000000000000000000000000000000000000000000000000000000000001" + /* 0 */ "1110000000000000000000000000000000000000000000000000000000000000" + /* 3 */ "1111111100000000000000000000000000000000000000000000000000000111", + }, + { + num:/* 3 */ "1111111100000000000000000000000000000000000000000000000000000111" + /* 2 */ "1111100000000000000000000000000000000000000000000000000000000011" + /* 1 */ "1111000000000000000000000000000000000000000000000000000000000001" + /* 0 */ "1110000000000000000000000000000000000000000000000000000000000000", + k: 128, + want:/* 1 */ "1111000000000000000000000000000000000000000000000000000000000001" + /* 0 */ "1110000000000000000000000000000000000000000000000000000000000000" + /* 3 */ "1111111100000000000000000000000000000000000000000000000000000111" + /* 2 */ "1111100000000000000000000000000000000000000000000000000000000011", + }, + { + num:/* 3 */ "1111111100000000000000000000000000000000000000000000000000000111" + /* 2 */ "1111100000000000000000000000000000000000000000000000000000000011" + /* 1 */ "1111000000000000000000000000000000000000000000000000000000000001" + /* 0 */ "1110000000000000000000000000000000000000000000000000000000000000", + k: 192, + want:/* 0 */ "1110000000000000000000000000000000000000000000000000000000000000" + /* 3 */ "1111111100000000000000000000000000000000000000000000000000000111" + /* 2 */ "1111100000000000000000000000000000000000000000000000000000000011" + /* 1 */ "1111000000000000000000000000000000000000000000000000000000000001", + }, + { + num:/* 3 */ "1111111100000000000000000000000000000000000000000000000000000111" + /* 2 */ "1111100000000000000000000000000000000000000000000000000000000011" + /* 1 */ "1111000000000000000000000000000000000000000000000000000000000001" + /* 0 */ "1110000000000000000000000000000000000000000000000000000000000000", + k: 256, + want:/* 3 */ "1111111100000000000000000000000000000000000000000000000000000111" + /* 2 */ "1111100000000000000000000000000000000000000000000000000000000011" + /* 1 */ "1111000000000000000000000000000000000000000000000000000000000001" + /* 0 */ "1110000000000000000000000000000000000000000000000000000000000000", + }, + { + num:/* 3 */ "1111111100000000000000000000000000000000000000000000000000000111" + /* 2 */ "1111100000000000000000000000000000000000000000000000000000000011" + /* 1 */ "1111000000000000000000000000000000000000000000000000000000000001" + /* 0 */ "1110000000000000000000000000000000000000000000000000000000000000", + k: 320, + want:/* 2 */ "1111100000000000000000000000000000000000000000000000000000000011" + /* 1 */ "1111000000000000000000000000000000000000000000000000000000000001" + /* 0 */ "1110000000000000000000000000000000000000000000000000000000000000" + /* 3 */ "1111111100000000000000000000000000000000000000000000000000000111", + }, + + // len is a multiple of 64, k is not. + { + num: "1111111100000000000000000000000000000000000000000000000000000111" + "1111100000000000000000000000000000000000000000000000000000000011" + "1111000000000000000000000000000000000000000000000000000000000001" + "1110000000000000000000000000000000000000000000000000000000000000", + k: 1, + want: "1111111000000000000000000000000000000000000000000000000000001111" + "1111000000000000000000000000000000000000000000000000000000000111" + "1110000000000000000000000000000000000000000000000000000000000011" + "1100000000000000000000000000000000000000000000000000000000000001", + }, + { + num: "1111111100000000000000000000000000000000000000000000000000000111" + "1111100000000000000000000000000000000000000000000000000000000011" + "1111000000000000000000000000000000000000000000000000000000000001" + "1110000000000000000000000000000000000000000000000000000000000000", + k: 34, + want: "0000000000000000000000000001111111100000000000000000000000000000" + "0000000000000000000000000000111111000000000000000000000000000000" + "0000000000000000000000000000011110000000000000000000000000000000" + "0000000000000000000000000000001111111100000000000000000000000000", + }, + { + num: "1111111100000000000000000000000000000000000000000000000000000111" + "1111100000000000000000000000000000000000000000000000000000000011" + "1111000000000000000000000000000000000000000000000000000000000001" + "1110000000000000000000000000000000000000000000000000000000000000", + k: 63, + want: "1111110000000000000000000000000000000000000000000000000000000001" + "1111100000000000000000000000000000000000000000000000000000000000" + "1111000000000000000000000000000000000000000000000000000000000000" + "0111111110000000000000000000000000000000000000000000000000000011", + }, + { + num: "1111111100000000000000000000000000000000000000000000000000000111" + "1111100000000000000000000000000000000000000000000000000000000011" + "1111000000000000000000000000000000000000000000000000000000000001" + "1110000000000000000000000000000000000000000000000000000000000000", + k: 127, + want: "1111100000000000000000000000000000000000000000000000000000000000" + "1111000000000000000000000000000000000000000000000000000000000000" + "0111111110000000000000000000000000000000000000000000000000000011" + "1111110000000000000000000000000000000000000000000000000000000001", + }, + } + + for _, tt := range tests { + t.Run(fmt.Sprintf("len=%d/k=%d", len(tt.num), tt.k), func(t *testing.T) { + bs, err := NewFromString(tt.num) + if err != nil { + t.Fatal(err) + } + ones := bs.OnesCount() + + bs.rotateLeft(tt.k) + + if bs.String() != tt.want { + t.Fatalf("RotateLeft\noriginal:\n\t%s\ngot:\n\t%s\nwant:\n\t%s", groupBy64(tt.num), groupBy64(bs.String()), groupBy64(tt.want)) + } + + if bs.OnesCount() != ones { + t.Fatalf("Rotation changed the number of 0's, got %d want %d", bs.OnesCount(), ones) + } + + // ensure than number of 1s and 0 has remained the same through the operations or this would mess other functions + }) + } + +} + +func groupBy64(s string) string { + ret := "" + for len(s) >= 64 { + v := s[len(s)-64:] + ret = ret + v + s = s[:len(s)-64] + if len(s) < 64 { + break + } + ret = " " + ret + } + ret = s + " " + ret + return ret +} + +/* +func groupBy64(s string) string { + res := "" + for i := len(s) - 1; i >= 0; i-- { + bit := string(s[i]) + res = bit + res + if i%64 == 0 { + res = " " + res + } + } + + return res +} +*/ From 8f92694d3009dd82879921bf8678e4896ead4220 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Rainone?= Date: Thu, 17 Jun 2021 14:22:30 +0200 Subject: [PATCH 17/19] RotateLeft: handle last word _manually_ --- bitstring.go | 13 +++++++++++-- bitstring_test.go | 1 + 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/bitstring.go b/bitstring.go index 5247b69..d5e6efb 100644 --- a/bitstring.go +++ b/bitstring.go @@ -454,7 +454,8 @@ func (bs *Bitstring) rotateLeft(k int) { } carry := uint64(0) - for i := 0; i < len(bs.data); i++ { + i := 0 + for ; i < len(bs.data)-1; i++ { w := bits.RotateLeft64(bs.data[i], kbits) tmp := w & lomask(uint64(kbits)) // extract the range of bits to carry over to next word. w &= ^lomask(uint64(kbits)) // clear the range of bits of w before applying carry from previous word. @@ -462,11 +463,19 @@ func (bs *Bitstring) rotateLeft(k int) { bs.data[i], carry = w, tmp } - // look for interesting stuff in /usr/local/go/src/runtime/mpallocbits.go + // _Manually_ handle the last word since the number of bits to rotate might + // not be 64 (if the bitstring length is not a multiple of 64). + w := bits.RotateLeft64(bs.data[i], kbits) + tmp := w & lomask(uint64(kbits)) // extract the range of bits to carry over to next word. + w &= ^lomask(uint64(kbits)) // clear the range of bits of w before applying carry from previous word. + w |= carry + bs.data[i], carry = w, tmp // Report last word carry onto the first word. bs.data[0] |= carry + // look for interesting stuff in /usr/local/go/src/runtime/mpallocbits.go + // nlastbits := bs.length % 64 // number of bits in the last word. // carry |= mask(uint64(nlastbits), uint64(nlastbits+kbits)) >> nlastbits diff --git a/bitstring_test.go b/bitstring_test.go index 91addf6..3a34d3a 100644 --- a/bitstring_test.go +++ b/bitstring_test.go @@ -346,6 +346,7 @@ func TestLeadingTrailing(t *testing.T) { } func TestRotate(t *testing.T) { + t.Skip("working on Test_rotateLeft") tests := []struct { num string k int From 1ba7346fdf6612e219ff3fcb4545cd3679c4cc05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Rainone?= Date: Thu, 17 Jun 2021 21:14:25 +0200 Subject: [PATCH 18/19] WIP --- bitstring.go | 147 ++++++++----------------------- bitstring_test.go | 215 ++++++++++------------------------------------ 2 files changed, 82 insertions(+), 280 deletions(-) diff --git a/bitstring.go b/bitstring.go index d5e6efb..a02e9b3 100644 --- a/bitstring.go +++ b/bitstring.go @@ -416,13 +416,9 @@ func rotate3(nums []uint64, k int) { return } - // fmt.Printf("rotating left(%d) -> %v\n", k, nums) reverse(nums) - // fmt.Println("reverse -> ", nums) reverse(nums[:k]) - // fmt.Println("reverse -> ", nums) reverse(nums[k:]) - // fmt.Println("reverse -> ", nums) } // RotateRight rotates the bitstring by k bits to the right. @@ -432,7 +428,7 @@ func (bs *Bitstring) RotateRight(k int) { // RotateLeft rotates the bitstring by k bits to the left. // TODO: document whether k can be negative -func (bs *Bitstring) rotateLeft(k int) { +func (bs *Bitstring) RotateLeft(k int) { // Remove full circles; reduce k to its smallest equivalent value. k %= bs.length @@ -447,7 +443,8 @@ func (bs *Bitstring) rotateLeft(k int) { // kbits is the number of bits we must rotate bs to the left in order to // complete the full rotation. Rotate each element of the bs.data slice of - // kbits to the left, and carry the shifted kbits to the next element. + // kbits to the left, and carry the k leftmost shifted bits to the next + // element. kbits := k % 64 if kbits == 0 { return @@ -463,119 +460,47 @@ func (bs *Bitstring) rotateLeft(k int) { bs.data[i], carry = w, tmp } - // _Manually_ handle the last word since the number of bits to rotate might - // not be 64 (if the bitstring length is not a multiple of 64). - w := bits.RotateLeft64(bs.data[i], kbits) - tmp := w & lomask(uint64(kbits)) // extract the range of bits to carry over to next word. - w &= ^lomask(uint64(kbits)) // clear the range of bits of w before applying carry from previous word. - w |= carry - bs.data[i], carry = w, tmp - - // Report last word carry onto the first word. - bs.data[0] |= carry - - // look for interesting stuff in /usr/local/go/src/runtime/mpallocbits.go - - // nlastbits := bs.length % 64 // number of bits in the last word. - - // carry |= mask(uint64(nlastbits), uint64(nlastbits+kbits)) >> nlastbits - // bs.data[0] |= carry - // bs.data[len(bs.data)-1] &= lomask((uint64(kbits))) - // nlastbits = 4 - // k = 10 - // 0000000000000000000000000000000000000000000000000000000000001111 - - // 0000000000000000000000000000000000000000000000000011110000000000 - -} -func (bs *Bitstring) RotateLeft(k int) { - bs.rotateLeft(k) -} - -/* -// RotateLeft rotates the bitstring by k bits to the left. -func (bs *Bitstring) RotateLeft(k int) { - fmt.Println("RotateLeft(", k, ")") - // Reduce k to its smallest value. - k %= bs.length - - // First move words. - nwords := k / 64 - if nwords != 0 { - rotate3(bs.data, nwords) - } - - // Remaining bits to move (i.e always less than 64). - lastbits := uint64(bs.length % 64) + // _Manually_ handle the last word since we may not have to consider all + // bits, in case the bitstring length is not a multiple of 64. + // w := bits.RotateLeft64(bs.data[i], kbits) + // tmp := w & lomask(uint64(kbits)) // extract the range of bits to carry over to next word. + // w &= ^lomask(uint64(kbits)) // clear the range of bits of w before applying carry from previous word. + // w |= carry + // bs.data[i], carry = w, tmp + + // _Manually_ handle the last word since we may not have to consider all + // bits, in case the bitstring length is not a multiple of 64. + lastbits := bs.length % 64 if lastbits == 0 { - return - } - - kbits := uint64(k - nwords*64) - // if nbits == 0 { - // return - // } - - fmt.Println("k bits to move", kbits, "nbits in last word ", lastbits) - if uint64(bs.length%64) == 0 { - panic("impossible") + lastbits = 64 } - - var carry uint64 - for i := 0; i < len(bs.data); i++ { + if kbits+lastbits > 64 { + // Rotation will move the leftmost bits to the right. + // lastbits = 60 + // ----111010101010010111010101000101101011011010001010101001011001 << 10 -> 101010010111010101000101101011011010001010101001011001----111010 w := bs.data[i] - hmask := himask(64 - kbits) - // fmt.Printf("%2b\n", ) - tmp := w & hmask - tmp >>= (64 - kbits) + tmp := w & mask(uint64(lastbits-kbits), uint64(lastbits)) + tmp >>= (lastbits - kbits) w <<= kbits w |= carry - carry = tmp + w &= lomask(uint64(lastbits)) // that's strange that we don't need it bs.data[i] = w - } - - if lastbits+kbits > 64 { + carry = tmp } else { - // all the bits of the + // No bits will fall off at the end, same as shift. + // ------------------------------------------------------------1001 << 58 -> --1001---------------------------------------------------------- + // ------1010101010010111010101000101101011011010001010101001011001 << 4 -> --1010101010010111010101000101101011011010001010101001011001---- + // TODO (we can use a simple shift here rather than a rotation) + w := bits.RotateLeft64(bs.data[i], kbits) + // apply carry + w |= carry + // extract the range of bits to carry over to next word. + carry = w & mask(uint64(lastbits), uint64(lastbits+kbits)) >> lastbits + // reset extra bits + w &= lomask(uint64(lastbits)) + bs.data[i] = w } - // use circular shift for the last word? - // https://en.wikipedia.org/wiki/Circular_shift - - // var carry uint64 - // for i := 0; i < len(bs.data); i++ { - // bs.data[i], carry = bs.data[i]<>(64-nbits) - // } - fmt.Printf("before last bs.data[len(bs.data)-1] %v\n", bs.data) - - // 10000 - // ____ - - nbitslastw := uint64(bs.length % 64) // number of useful bits of last word - bs.data[0] |= (carry >> (64 - nbitslastw)) - - carry = bs.data[len(bs.data)-1] & mask(nbitslastw, nbitslastw+nbits) - carry = carry >> nbitslastw + // Report last word carry onto the first word. bs.data[0] |= carry - - - // // work but not for 37 - // nbitslastw := uint64(bs.length % 64) // number of useful bits of last word - // bs.data[0] |= (carry >> (nbitslastw)) - - // carry = bs.data[len(bs.data)-1] & mask(nbitslastw, nbitslastw+nbits) - // carry = carry >> nbitslastw - // bs.data[0] |= carry - - - // carry = bs.data[len(bs.data)-1] & mask(nbitslastw, nbitslastw+nbits) - // bs.data[0] |= (carry >> (nbitslastw)) - // bs.data[len(bs.data)-1] &= lomask(nbitslastw) - - // lastbits := uint64(bs.length % 64) // number of useful bits of last word - // bs.data[0] |= (bs.data[len(bs.data)-1] & mask(lastbits, lastbits+nbits)) >> lastbits - // bs.data[len(bs.data)-1] &= lomask(lastbits) - / - } -*/ diff --git a/bitstring_test.go b/bitstring_test.go index 3a34d3a..c2d1cb8 100644 --- a/bitstring_test.go +++ b/bitstring_test.go @@ -345,155 +345,33 @@ func TestLeadingTrailing(t *testing.T) { } } -func TestRotate(t *testing.T) { - t.Skip("working on Test_rotateLeft") +func Test_RotateLeftRight(t *testing.T) { tests := []struct { num string k int want string }{ - // k is a multiple of 64 bits - // { - // num: "0000000000000000000000000000000000000000000000000000000000000111" + "0000000000000000000000000000000000000000000000000000000000000011" + "0000000000000000000000000000000000000000000000000000000000000001" + "0000000000000000000000000000000000000000000000000000000000000000", - // k: 0, - // want: "0000000000000000000000000000000000000000000000000000000000000111" + "0000000000000000000000000000000000000000000000000000000000000011" + "0000000000000000000000000000000000000000000000000000000000000001" + "0000000000000000000000000000000000000000000000000000000000000000", - // }, - // { - // num:/* 3 */ "0000000000000000000000000000000000000000000000000000000000000111" + /* 2 */ "0000000000000000000000000000000000000000000000000000000000000011" + /* 1 */ "0000000000000000000000000000000000000000000000000000000000000001" + /* 0 */ "0000000000000000000000000000000000000000000000000000000000000000", - // k: 64, - // want:/* 2 */ "0000000000000000000000000000000000000000000000000000000000000011" + /* 1 */ "0000000000000000000000000000000000000000000000000000000000000001" + /* 0 */ "0000000000000000000000000000000000000000000000000000000000000000" + /* 3 */ "0000000000000000000000000000000000000000000000000000000000000111", - // }, - // { - // num:/* 3 */ "0000000000000000000000000000000000000000000000000000000000000111" + /* 2 */ "0000000000000000000000000000000000000000000000000000000000000011" + /* 1 */ "0000000000000000000000000000000000000000000000000000000000000001" + /* 0 */ "0000000000000000000000000000000000000000000000000000000000000000", - // k: 128, - // want:/* 1 */ "0000000000000000000000000000000000000000000000000000000000000001" + /* 0 */ "0000000000000000000000000000000000000000000000000000000000000000" + /* 3 */ "0000000000000000000000000000000000000000000000000000000000000111" + /* 2 */ "0000000000000000000000000000000000000000000000000000000000000011", - // }, - // { - // num:/* 3 */ "0000000000000000000000000000000000000000000000000000000000000111" + /* 2 */ "0000000000000000000000000000000000000000000000000000000000000011" + /* 1 */ "0000000000000000000000000000000000000000000000000000000000000001" + /* 0 */ "0000000000000000000000000000000000000000000000000000000000000000", - // k: 192, - // want:/* 0 */ "0000000000000000000000000000000000000000000000000000000000000000" + /* 3 */ "0000000000000000000000000000000000000000000000000000000000000111" + /* 2 */ "0000000000000000000000000000000000000000000000000000000000000011" + /* 1 */ "0000000000000000000000000000000000000000000000000000000000000001", - // }, - // { - // num:/* 3 */ "0000000000000000000000000000000000000000000000000000000000000111" + /* 2 */ "0000000000000000000000000000000000000000000000000000000000000011" + /* 1 */ "0000000000000000000000000000000000000000000000000000000000000001" + /* 0 */ "0000000000000000000000000000000000000000000000000000000000000000", - // k: 256, - // want:/* 3 */ "0000000000000000000000000000000000000000000000000000000000000111" + /* 2 */ "0000000000000000000000000000000000000000000000000000000000000011" + /* 1 */ "0000000000000000000000000000000000000000000000000000000000000001" + /* 0 */ "0000000000000000000000000000000000000000000000000000000000000000", - // }, - - // // shorter than 64 bits - // { - // num: "0", - // k: 1, - // want: "0", - // }, - // { - // num: "1", - // k: 63, - // want: "1", - // }, - // { - // num: "01010101", - // k: 0, - // want: "01010101", - // }, - // { - // num: "1000", - // k: 1, - // want: "0001", - // }, - // { - // num: "1001", - // k: 2, - // want: "0110", - // }, - // { - // num: "1001", - // k: 2, - // want: "0110", - // }, - - { - num: "111111111111111111111111111111111111100000000000000000000000000", - k: 37, - want: "000000000000000000000000001111111111111111111111111111111111111", - }, - - // longer than 64 bits - { - num: "0000000000000000000000000000000000000000000000000000000000001100", - k: 62, - want: "0000000000000000000000000000000000000000000000000000000000000011", - }, + // len < 64. { - num: "10000000000000000000000000000000000000000000000000000000000000001", - k: 3, - want: "00000000000000000000000000000000000000000000000000000000000001100", + num: "1", + k: 1, + want: "1", }, { - num: "10000000000000000000000000000000000000000000000000000000000000001", + num: "1", k: 3, - want: "00000000000000000000000000000000000000000000000000000000000001100", + want: "1", }, - { - num: "00000000000000000000000000000000000000000000000000000000000001100", - k: 62, - want: "10000000000000000000000000000000000000000000000000000000000000001", - }, - } - - for _, tt := range tests { - t.Run(fmt.Sprintf("%d/%s", tt.k, tt.num), func(t *testing.T) { - bs, err := NewFromString(tt.num) - if err != nil { - t.Fatal(err) - } - - bs.RotateLeft(tt.k) - if bs.String() != tt.want { - t.Fatalf("RotateLeft wrong value\n\tgot:\n%s\n\n\twant:\n%s", groupBy64(bs.String()), groupBy64(tt.want)) - } - - bs.RotateRight(tt.k) - if bs.String() != tt.num { - t.Fatalf("RotateRight wrong value\n\tgot:\n%s\n\n\twant:\n%s", groupBy64(bs.String()), groupBy64(tt.num)) - } - // ensure than number of 1s and 0 has remained the same through the operations or this would mess other functions - }) - } -} - -func Test_rotateLeft(t *testing.T) { - tests := []struct { - num string - k int - want string - }{ - // len < 64. - // { - // num: "1", - // k: 1, - // want: "1", - // }, - // { - // num: "1", - // k: 3, - // want: "1", - // }, { num: "101", k: 1, want: "011", }, - - // { - // num: "1001", - // k: 2, - // want: "0110", - // }, - // { - // num: "111111111111111111111111111111111111100000000000000000000000000", - // k: 37, - // want: "000000000000000000000000001111111111111111111111111111111111111", - // }, + { + num: "1001", + k: 2, + want: "0110", + }, // len and k are both multiples of 64. { @@ -548,6 +426,28 @@ func Test_rotateLeft(t *testing.T) { k: 127, want: "1111100000000000000000000000000000000000000000000000000000000000" + "1111000000000000000000000000000000000000000000000000000000000000" + "0111111110000000000000000000000000000000000000000000000000000011" + "1111110000000000000000000000000000000000000000000000000000000001", }, + + // neither len nor k are multiples of 64 + { + num: "111111100000000000000000000000000000000000000000000000000000111" + "1111100000000000000000000000000000000000000000000000000000000011" + "1111000000000000000000000000000000000000000000000000000000000001" + "1110000000000000000000000000000000000000000000000000000000000000", + k: 1, + want: "111111000000000000000000000000000000000000000000000000000001111" + "1111000000000000000000000000000000000000000000000000000000000111" + "1110000000000000000000000000000000000000000000000000000000000011" + "1100000000000000000000000000000000000000000000000000000000000001", + }, + { + num: "00000111" + "1111100000000000000000000000000000000000000000000000000000000011" + "1111000000000000000000000000000000000000000000000000000000000001" + "1110000000000000000000000000000000000000000000000000000000000000", + k: 34, + want: "00000000000000000000000000000000000011" + "1111000000000000000000000000000000000000000000000000000000000001" + "1110000000000000000000000000000000000000000000000000000000000000" + "00000111" + "11111000000000000000000000", + }, + { + num: "1010111100000000010000100001000000001100000000000000000000000111" + "1111100000000000000000000000000000000000000000000000000000000011" + "1111000000000000000000000000000000000000000000000000000000000001" + "1110000000000000000000000000000000000000000000000000000000111001", + k: 63, + want: "1" + "1111100000000000000000000000000000000000000000000000000000000011" + "1111000000000000000000000000000000000000000000000000000000000001" + "1110000000000000000000000000000000000000000000000000000000111001" + "101011110000000001000010000100000000110000000000000000000000011", + }, + { + num: "11" + "0011100000000000000000000000000000000000000000000000000000000011" + "1111000000000000000000000000000000000000000000000000000000000001" + "1110000000000000000000000000000000000000000000000000000000111001", + k: 13, + want: "00000000000000000000000000000000000000000000000000011" + "1111000000000000000000000000000000000000000000000000000000000001" + "1110000000000000000000000000000000000000000000000000000000111001" + "1100111000000", + }, } for _, tt := range tests { @@ -558,48 +458,25 @@ func Test_rotateLeft(t *testing.T) { } ones := bs.OnesCount() - bs.rotateLeft(tt.k) + bs.RotateLeft(tt.k) if bs.String() != tt.want { - t.Fatalf("RotateLeft\noriginal:\n\t%s\ngot:\n\t%s\nwant:\n\t%s", groupBy64(tt.num), groupBy64(bs.String()), groupBy64(tt.want)) + t.Fatalf("RotateLeft\noriginal:\n\t%s\ngot:\n\t%s\nwant:\n\t%s", tt.num, bs.String(), tt.want) } + // Ensure that we don't lose any ones during the trip... if bs.OnesCount() != ones { - t.Fatalf("Rotation changed the number of 0's, got %d want %d", bs.OnesCount(), ones) + t.Fatalf("RotateLeft changed the number of 1's, got %d want %d", bs.OnesCount(), ones) } - // ensure than number of 1s and 0 has remained the same through the operations or this would mess other functions - }) - } - -} - -func groupBy64(s string) string { - ret := "" - for len(s) >= 64 { - v := s[len(s)-64:] - ret = ret + v - s = s[:len(s)-64] - if len(s) < 64 { - break - } - ret = " " + ret - } - ret = s + " " + ret - return ret -} + bs.RotateRight(tt.k) + if bs.String() != tt.num { + t.Fatalf("RotateRight\noriginal:\n\t%s\ngot:\n\t%s\nwant:\n\t%s", tt.want, bs.String(), tt.num) + } -/* -func groupBy64(s string) string { - res := "" - for i := len(s) - 1; i >= 0; i-- { - bit := string(s[i]) - res = bit + res - if i%64 == 0 { - res = " " + res - } + if bs.OnesCount() != ones { + t.Fatalf("RotateRight changed the number of 1's, got %d want %d", bs.OnesCount(), ones) + } + }) } - - return res } -*/ From ca75af0a5e13f5b4d833c657541193c75bd5498f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Rainone?= <476650+arl@users.noreply.github.com> Date: Fri, 18 Jun 2021 23:01:11 +0000 Subject: [PATCH 19/19] commit from codespace --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ca16681..08b239a 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ Package `bitstring` implements a fixed length bit string type and bit manipulati - Trailing/Leading Zeroes/Ones : `TrailingZeroes`|`LeadingZeroes`|`TrailingOnes`|`LeadingOnes` -## Debug version +## Debug versionhttps://bitstring.readthedocs.io/en/latest/ By default, bit offsets arguments to `bitstring` methods are not checked. This allows not to pay the performance penalty of always checking offsets, in @@ -34,3 +34,5 @@ when building the `bitstring` package. **TODO**: - RotateLeft/Right ShiftLeft/Right - Or, And, Xor between bitstrings + +have a look at https://bitstring.readthedocs.io/en/latest/