-
-
Notifications
You must be signed in to change notification settings - Fork 202
Add Bitwise Operations concept #716
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
colinleach
wants to merge
7
commits into
exercism:main
Choose a base branch
from
colinleach:bits
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
57fd9a8
Add Bitwise Operations concept
039b421
Update concepts/bitwise-operations/introduction.md
colinleach 1c9d2e2
Add section title for bit-shifting negative integers
colinleach 54046fd
negative numbers fix
b4f4c06
Update concepts/bitwise-operations/about.md
colinleach 00566fd
Update concepts/bitwise-operations/about.md
colinleach 6cd516d
Update concepts/bitwise-operations/about.md
colinleach File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
{ | ||
"authors": [ | ||
"colinleach" | ||
], | ||
"contributors": [], | ||
"blurb": "Kotlin provides a set of bitwise operations that allow you to manipulate the individual bits of integers." | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
# About Bits | ||
|
||
Binary digits ultimately map directly to the transistors in your CPU or RAM, and whether each is "on" or "off". | ||
|
||
Low-level manipulation, informally called "bit-twiddling", is particularly important in system languages. | ||
|
||
Higher-level languages like Kotlin usually abstract away most of this detail. | ||
However, a basic range of bit-level operations [is available][ref-bitwise]. | ||
|
||
~~~~exercism/note | ||
To see human-readable binary output, nearly all the examples below need to be wrapped in an [`Integer.toBinaryString()`][web-binstring] function, or converted with `toString(radix = 2)`. | ||
|
||
This is visually distracting, so most occurrences of this function have been edited out, and the results are represented with `0b` notation. | ||
|
||
[web-binstring]: https://www.baeldung.com/kotlin/int-binary-representation | ||
~~~~ | ||
|
||
## Bit-shift operations | ||
|
||
`Int` or `Long` types, both signed and unsigned, can be represented as a String of 1's and 0's, and manipulated as a sequence of bits. | ||
|
||
Perhaps surprisingly, `Byte` or `Short` types are not compatible with most bitwise operations in Kotlin. | ||
All the examples below use 32-bit integers (`Int` or `UInt`). | ||
|
||
```kotlin | ||
val ns = 0b111 // 7 decimal | ||
Integer.toBinaryString(ns) // => "111" | ||
``` | ||
|
||
Bit-shifts move everything to the left or right by a specified number of positions. | ||
Some bits drop off one end, and the other end is padded with zeros or ones. | ||
|
||
- Left shift with `shl`: zero-padding. | ||
- Right shift with `shr` or `ushr`: zero padding for positive numbers (see later section for negative numbers). | ||
|
||
```kotlin | ||
val ns = 0b111 // 7 decimal | ||
ns shl 1 // => 0b1110, decimal 14 | ||
ns shr 1 // => 0b11, decimal 3 | ||
ns ushr 1 // => 0b11 | ||
|
||
val nu: UInt = 0b111u // 7 unsigned | ||
nu shr 1 // => 0b11 | ||
``` | ||
|
||
Each left-shift doubles the value, and each right-shift halves it (subject to truncation). | ||
This is more obvious in decimal representation: | ||
|
||
```kotlin | ||
3 shl 2 // => 12 | ||
24 shr 3 // => 3 | ||
``` | ||
|
||
Such bit-shifting is much faster than "proper" arithmetic, making the technique very popular in low-level coding. | ||
|
||
### Bit-shifting negative integers | ||
|
||
With _negative integers_, we need to be a bit more careful. | ||
|
||
Negative values are stored in [two's complement][wiki-2complement] form, which means that the left-most bit is 1. | ||
No problem for a left-shift, but when right-shifting how do we pad the left-most bits? | ||
|
||
```kotlin | ||
val ns = -0b111 // => 0b11111111111111111111111111111001 | ||
|
||
// shift left: simple | ||
ns shl 1 // => 0b11111111111111111111111111110010, decimal -14 | ||
|
||
// shift right: preserves sign bit | ||
ns shr 1 // => 0b11111111111111111111111111111100, decimal -4 | ||
|
||
// unsigned shift right: left-pads with zeros | ||
ns ushr 1 // => 0b01111111111111111111111111111100, decimal 2147483644 | ||
``` | ||
|
||
The [`shr`][ref-shr] operator performs [arithmetic shift][wiki-arithmetic], preserving the sign bit. | ||
|
||
The [`ushr`][ref-ushr] operator performs [logical shift][wiki-logical], padding with zeros as if the number was unsigned. | ||
|
||
## Bitwise logic | ||
|
||
We saw in the [Booleans Concept][concept-booleans] that the operators `&&` (and), `||` (or) and `!` (not) are used with boolean values. | ||
|
||
There are equivalent operators `and`, `or`, `xor` ([exclusive-or][wiki-xor]) to compare the bits in two integers, and an `inv` (inversion) function to flip all the bits. | ||
|
||
```kotlin | ||
0b1011 and 0b0010 // bit is 1 in both numbers | ||
// => 0b0010 | ||
|
||
0b1011 or 0b0010 // bit is 1 in at least one number | ||
// => 0b1011 | ||
|
||
0b1011 xor 0b0010 // bit is 1 in exactly one number, not both | ||
// => 0b1001 | ||
|
||
0b1011.inv() // flip all bits (in a 32-bit signed Int) | ||
// => 0b11111111111111111111111111110100 | ||
``` | ||
|
||
|
||
[ref-bitwise]: https://kotlinlang.org/docs/numbers.html#bitwise-operations | ||
[wiki-xor]: https://en.wikipedia.org/wiki/Exclusive_or | ||
[wiki-2complement]: https://en.wikipedia.org/wiki/Two%27s_complement | ||
[wiki-arithmetic]: https://en.wikipedia.org/wiki/Arithmetic_shift | ||
[wiki-logical]: https://en.wikipedia.org/wiki/Logical_shift | ||
[web-binstring]: https://www.baeldung.com/kotlin/int-binary-representation | ||
[ref-shr]: https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-int/shr.html | ||
[ref-ushr]: https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-int/ushr.html | ||
[concept-booleans]: https://exercism.org/tracks/kotlin/concepts/booleans |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
# Introduction | ||
|
||
Binary digits ultimately map directly to the transistors in your CPU or RAM, and whether each is "on" or "off". | ||
|
||
Low-level manipulation, informally called "bit-twiddling", is particularly important in system languages. | ||
|
||
Higher-level languages like Kotlin usually abstract away most of this detail. | ||
However, a basic range of bit-level operations [is available][ref-bitwise]. | ||
|
||
## Bit-shift operations | ||
|
||
`Int` or `Long` types, signed or unsigned, can be represented as a string of 1's and 0's and manipulated as a sequence of bits. | ||
|
||
Perhaps surprisingly, `Byte` or `Short` types are not compatible with most bitwise operations in Kotlin. | ||
All the examples below use 32-bit integers (`Int` or `UInt`). | ||
|
||
```kotlin | ||
val ns = 0b111 // 7 decimal | ||
Integer.toBinaryString(ns) // => "111" | ||
``` | ||
|
||
Bit-shifts just move everything to the left or right by a specified number of positions. | ||
Some bits drop off one end, and the other end is padded with zeros or ones. | ||
|
||
- Left shift with `shl`: zero-padding. | ||
- Right shift with `shr` or `ushr`: zero padding for positive numbers (see later for negative numbers). | ||
|
||
```kotlin | ||
val ns = 0b111 // 7 decimal | ||
ns shl 1 // => 0b1110, decimal 14 | ||
ns shr 1 // => 0b11, decimal 3 | ||
ns ushr 1 // => 0b11 | ||
|
||
val nu: UInt = 0b111u // 7 unsigned | ||
nu shr 1 // => 0b11 | ||
``` | ||
|
||
Each left-shift doubles the value, and each right-shift halves it (subject to truncation). | ||
This is more obvious in decimal representation: | ||
|
||
```kotlin | ||
3 shl 2 // => 12 | ||
24 shr 3 // => 3 | ||
``` | ||
|
||
Such bit-shifting is much faster than "proper" arithmetic, making the technique very popular in low-level coding. | ||
|
||
With _negative integers_, we need to be a bit more careful. | ||
|
||
Negative values are stored in [two's complement][wiki-2complement] form, which means that the left-most bit is 1. | ||
No problem for a left-shift, but when right-shifting how do we pad the left-most bits? | ||
|
||
```kotlin | ||
val ns = -0b111 // => 0b11111111111111111111111111111001 | ||
|
||
// shift left: simple | ||
ns shl 1 // => 0b11111111111111111111111111110010, decimal -14 | ||
|
||
// shift right: preserves sign bit | ||
ns shr 1 // => 0b11111111111111111111111111111100, decimal -4 | ||
|
||
// unsigned shift right: left-pads with zeros | ||
ns ushr 1 // => 0b01111111111111111111111111111100, decimal 2147483644 | ||
``` | ||
|
||
The `shr` operator performs [arithmetic shift][wiki-arithmetic], preserving the sign bit. | ||
|
||
The `ushr` operator performs [logical shift][wiki-logical], padding with zeros as if the number was unsigned. | ||
|
||
## Bitwise logic | ||
|
||
We saw in the [Booleans Concept][concept-booleans] that the operators `&&` (and), `||` (or) and `!` (not) are used with boolean values. | ||
|
||
There are equivalent operators `and`, `or`, `xor` ([exclusive-or][wiki-xor]) to compare the bits in two integers, and an `inv` (inversion) function to flip all the bits. | ||
|
||
```kotlin | ||
0b1011 and 0b0010 // bit is 1 in both numbers | ||
// => 0b0010 | ||
|
||
0b1011 or 0b0010 // bit is 1 in at least one number | ||
// => 0b1011 | ||
|
||
0b1011 xor 0b0010 // bit is 1 in exactly one number, not both | ||
// => 0b1001 | ||
|
||
0b1011.inv() // flip all bits (in a 32-bit signed Int) | ||
// => 0b11111111111111111111111111110100 | ||
``` | ||
|
||
|
||
[ref-bitwise]: https://kotlinlang.org/docs/numbers.html#bitwise-operations | ||
[wiki-xor]: https://en.wikipedia.org/wiki/Exclusive_or | ||
[wiki-2complement]: https://en.wikipedia.org/wiki/Two%27s_complement | ||
[wiki-arithmetic]: https://en.wikipedia.org/wiki/Arithmetic_shift | ||
[wiki-logical]: https://en.wikipedia.org/wiki/Logical_shift | ||
[concept-booleans]: https://exercism.org/tracks/kotlin/concepts/booleans |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
[ | ||
{ | ||
"url": "https://kotlinlang.org/docs/numbers.html#bitwise-operations", | ||
"description": "Kotlin manual: bitwise operations" | ||
} | ||
] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't know this!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah. Since 1.5, UByte has
or
andxor
andand
andinv
, same for UShort.Byte has full support since 1.2, same for Short.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's strange. The 2.2 docs say:
I tried Byte and UByte in Kotlin Notebook, and couldn't get them to work with
shl
: "Unresolved reference. None of the following candidates is applicable because of receiver type mismatch".I'm not getting much done today (reasons including all-day headache and brain fog), but I don't think I messed that up.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yoooo my
Short
link actually linked to BigInteger which is also new to me....we now need to update the numbers concept XD. BigDecimal also existsnvm, only JVM!Anyway, yeah, it works for UShort and UByte. Not Short and Byte. And not all operations are supported for UShort and UByte! Only
and
,or
,xor
, andinv
. Playground. This playground also compiles in JS and Wasm :)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmmm, producing a clear, concise Concept for a poorly documented mess could be ... challenging.
Still, we're all better informed now, I suppose.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also: feel free to disagree, but I have some doubts about whether experimental and undocumented features belong in an Exercism syllabus. Though maybe I'm just old and unimaginative...
(I'm definitely old)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Experimental: maybe but I don't do that for the other tracks because it's a lot of work to keep track of.
Undocumented: sometimes, but in this case it's not undocumented. The Kotlin docs are out of date, the kotlin reference is not! That's how I found these: https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-u-byte/#-1940364782%2FFunctions%2F-1430298843