Skip to content

Commit 82ff14c

Browse files
YajuneshYajunesh M R
andauthored
feat: Add BitRotate utility for circular bit rotations (#7011)
* feat: Add BitRotate utility for circular bit rotations * feat: Add BitRotate utility for circular bit rotations * feat: Add BitRotate utility for circular bit rotations * fix: Remove trailing spaces and add newline at EOF --------- Co-authored-by: Yajunesh M R <[email protected]>
1 parent d717ca4 commit 82ff14c

File tree

2 files changed

+288
-0
lines changed

2 files changed

+288
-0
lines changed
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package com.thealgorithms.bitmanipulation;
2+
3+
/**
4+
* Utility class for performing circular bit rotations on 32-bit integers.
5+
* Bit rotation is a circular shift operation where bits shifted out on one end
6+
* are reinserted on the opposite end.
7+
*
8+
* <p>This class provides methods for both left and right circular rotations,
9+
* supporting only 32-bit integer operations with proper shift normalization
10+
* and error handling.</p>
11+
*
12+
* @see <a href="https://en.wikipedia.org/wiki/Bit_rotation">Bit Rotation</a>
13+
*/
14+
public final class BitRotate {
15+
16+
/**
17+
* Private constructor to prevent instantiation.
18+
* This is a utility class with only static methods.
19+
*/
20+
private BitRotate() {
21+
throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
22+
}
23+
24+
/**
25+
* Performs a circular left rotation (left shift) on a 32-bit integer.
26+
* Bits shifted out from the left side are inserted on the right side.
27+
*
28+
* @param value the 32-bit integer value to rotate
29+
* @param shift the number of positions to rotate left (must be non-negative)
30+
* @return the result of left rotating the value by the specified shift amount
31+
* @throws IllegalArgumentException if shift is negative
32+
*
33+
* @example
34+
* // Binary: 10000000 00000000 00000000 00000001
35+
* rotateLeft(0x80000001, 1)
36+
* // Returns: 3 (binary: 00000000 00000000 00000000 00000011)
37+
*/
38+
public static int rotateLeft(int value, int shift) {
39+
if (shift < 0) {
40+
throw new IllegalArgumentException("Shift amount cannot be negative: " + shift);
41+
}
42+
43+
// Normalize shift to the range [0, 31] using modulo 32
44+
shift = shift % 32;
45+
46+
if (shift == 0) {
47+
return value;
48+
}
49+
50+
// Left rotation: (value << shift) | (value >>> (32 - shift))
51+
return (value << shift) | (value >>> (32 - shift));
52+
}
53+
54+
/**
55+
* Performs a circular right rotation (right shift) on a 32-bit integer.
56+
* Bits shifted out from the right side are inserted on the left side.
57+
*
58+
* @param value the 32-bit integer value to rotate
59+
* @param shift the number of positions to rotate right (must be non-negative)
60+
* @return the result of right rotating the value by the specified shift amount
61+
* @throws IllegalArgumentException if shift is negative
62+
*
63+
* @example
64+
* // Binary: 00000000 00000000 00000000 00000011
65+
* rotateRight(3, 1)
66+
* // Returns: -2147483647 (binary: 10000000 00000000 00000000 00000001)
67+
*/
68+
public static int rotateRight(int value, int shift) {
69+
if (shift < 0) {
70+
throw new IllegalArgumentException("Shift amount cannot be negative: " + shift);
71+
}
72+
73+
// Normalize shift to the range [0, 31] using modulo 32
74+
shift = shift % 32;
75+
76+
if (shift == 0) {
77+
return value;
78+
}
79+
80+
// Right rotation: (value >>> shift) | (value << (32 - shift))
81+
return (value >>> shift) | (value << (32 - shift));
82+
}
83+
}
Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
package com.thealgorithms.bitmanipulation;
2+
3+
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
4+
import static org.junit.jupiter.api.Assertions.assertEquals;
5+
import static org.junit.jupiter.api.Assertions.assertThrows;
6+
import static org.junit.jupiter.api.Assertions.assertTrue;
7+
8+
import org.junit.jupiter.api.Test;
9+
10+
/**
11+
* Unit tests for BitRotate class covering typical, boundary, and edge cases.
12+
* Tests verify correct behavior for 32-bit circular bit rotations.
13+
*
14+
* @author Yajunesh
15+
*/
16+
public class BitRotateTest {
17+
18+
// ===== rotateLeft Tests =====
19+
20+
@Test
21+
public void testRotateLeftBasic() {
22+
// Basic left rotation
23+
assertEquals(0b00000000_00000000_00000000_00000010, BitRotate.rotateLeft(1, 1));
24+
assertEquals(0b00000000_00000000_00000000_00000100, BitRotate.rotateLeft(1, 2));
25+
assertEquals(0b00000000_00000000_00000000_00001000, BitRotate.rotateLeft(1, 3));
26+
}
27+
28+
@Test
29+
public void testRotateLeftWithCarry() {
30+
// Test bits carrying from left to right
31+
// Binary: 10000000_00000000_00000000_00000001
32+
int value = 0x80000001;
33+
// After left rotate by 1: 00000000_00000000_00000000_00000011
34+
assertEquals(3, BitRotate.rotateLeft(value, 1));
35+
36+
// Binary: 11000000_00000000_00000000_00000000
37+
value = 0xC0000000;
38+
// After left rotate by 1: 10000000_00000000_00000000_00000001
39+
assertEquals(0x80000001, BitRotate.rotateLeft(value, 1));
40+
}
41+
42+
@Test
43+
public void testRotateLeftShift32() {
44+
// Shift of 32 should be same as shift of 0 (modulo behavior)
45+
int value = 0x12345678;
46+
assertEquals(value, BitRotate.rotateLeft(value, 32));
47+
assertEquals(value, BitRotate.rotateLeft(value, 64));
48+
assertEquals(value, BitRotate.rotateLeft(value, 96));
49+
}
50+
51+
@Test
52+
public void testRotateLeftShiftNormalization() {
53+
// Test that shifts > 32 are properly normalized
54+
int value = 1;
55+
assertEquals(BitRotate.rotateLeft(value, 1), BitRotate.rotateLeft(value, 33));
56+
assertEquals(BitRotate.rotateLeft(value, 5), BitRotate.rotateLeft(value, 37));
57+
}
58+
59+
@Test
60+
public void testRotateLeftZeroShift() {
61+
// Zero shift should return original value
62+
int value = 0xABCD1234;
63+
assertEquals(value, BitRotate.rotateLeft(value, 0));
64+
}
65+
66+
// ===== rotateRight Tests =====
67+
68+
@Test
69+
public void testRotateRightBasic() {
70+
// Basic right rotation
71+
assertEquals(0b10000000_00000000_00000000_00000000, BitRotate.rotateRight(1, 1));
72+
assertEquals(0b01000000_00000000_00000000_00000000, BitRotate.rotateRight(1, 2));
73+
assertEquals(0b00100000_00000000_00000000_00000000, BitRotate.rotateRight(1, 3));
74+
}
75+
76+
@Test
77+
public void testRotateRightWithCarry() {
78+
// Test bits carrying from right to left
79+
// Binary: 00000000_00000000_00000000_00000011
80+
int value = 3;
81+
// After right rotate by 1: 10000000_00000000_00000000_00000001
82+
assertEquals(0x80000001, BitRotate.rotateRight(value, 1));
83+
84+
// Binary: 00000000_00000000_00000000_00000001
85+
value = 1;
86+
// After right rotate by 1: 10000000_00000000_00000000_00000000
87+
assertEquals(0x80000000, BitRotate.rotateRight(value, 1));
88+
}
89+
90+
@Test
91+
public void testRotateRightShift32() {
92+
// Shift of 32 should be same as shift of 0 (modulo behavior)
93+
int value = 0x9ABCDEF0;
94+
assertEquals(value, BitRotate.rotateRight(value, 32));
95+
assertEquals(value, BitRotate.rotateRight(value, 64));
96+
assertEquals(value, BitRotate.rotateRight(value, 96));
97+
}
98+
99+
@Test
100+
public void testRotateRightShiftNormalization() {
101+
// Test that shifts > 32 are properly normalized
102+
int value = 1;
103+
assertEquals(BitRotate.rotateRight(value, 1), BitRotate.rotateRight(value, 33));
104+
assertEquals(BitRotate.rotateRight(value, 7), BitRotate.rotateRight(value, 39));
105+
}
106+
107+
@Test
108+
public void testRotateRightZeroShift() {
109+
// Zero shift should return original value
110+
int value = 0xDEADBEEF;
111+
assertEquals(value, BitRotate.rotateRight(value, 0));
112+
}
113+
114+
// ===== Edge Case Tests =====
115+
116+
@Test
117+
public void testRotateLeftMaxValue() {
118+
// Test with maximum integer value
119+
int value = Integer.MAX_VALUE; // 0x7FFFFFFF
120+
int rotated = BitRotate.rotateLeft(value, 1);
121+
// MAX_VALUE << 1 should become 0xFFFFFFFE, but with rotation it becomes different
122+
assertEquals(0xFFFFFFFE, rotated);
123+
}
124+
125+
@Test
126+
public void testRotateRightMinValue() {
127+
// Test with minimum integer value (treated as unsigned)
128+
int value = Integer.MIN_VALUE; // 0x80000000
129+
int rotated = BitRotate.rotateRight(value, 1);
130+
// MIN_VALUE >>> 1 should become 0x40000000, but with rotation from left
131+
assertEquals(0x40000000, rotated);
132+
}
133+
134+
@Test
135+
public void testRotateAllOnes() {
136+
// Test with all bits set
137+
int value = 0xFFFFFFFF; // All ones
138+
assertEquals(value, BitRotate.rotateLeft(value, 13));
139+
assertEquals(value, BitRotate.rotateRight(value, 27));
140+
}
141+
142+
@Test
143+
public void testRotateAllZeros() {
144+
// Test with all bits zero
145+
int value = 0x00000000;
146+
assertEquals(value, BitRotate.rotateLeft(value, 15));
147+
assertEquals(value, BitRotate.rotateRight(value, 19));
148+
}
149+
150+
// ===== Exception Tests =====
151+
152+
@Test
153+
public void testRotateLeftNegativeShift() {
154+
// Negative shifts should throw IllegalArgumentException
155+
Exception exception = assertThrows(IllegalArgumentException.class, () -> BitRotate.rotateLeft(42, -1));
156+
assertTrue(exception.getMessage().contains("negative"));
157+
}
158+
159+
@Test
160+
public void testRotateRightNegativeShift() {
161+
// Negative shifts should throw IllegalArgumentException
162+
Exception exception = assertThrows(IllegalArgumentException.class, () -> BitRotate.rotateRight(42, -5));
163+
assertTrue(exception.getMessage().contains("negative"));
164+
}
165+
166+
// ===== Complementary Operations Test =====
167+
168+
@Test
169+
public void testRotateLeftRightComposition() {
170+
// Rotating left then right by same amount should return original value
171+
int original = 0x12345678;
172+
int shift = 7;
173+
174+
int leftRotated = BitRotate.rotateLeft(original, shift);
175+
int restored = BitRotate.rotateRight(leftRotated, shift);
176+
177+
assertEquals(original, restored);
178+
}
179+
180+
@Test
181+
public void testRotateRightLeftComposition() {
182+
// Rotating right then left by same amount should return original value
183+
int original = 0x9ABCDEF0;
184+
int shift = 13;
185+
186+
int rightRotated = BitRotate.rotateRight(original, shift);
187+
int restored = BitRotate.rotateLeft(rightRotated, shift);
188+
189+
assertEquals(original, restored);
190+
}
191+
192+
@Test
193+
public void testRotateLeft31IsSameAsRotateRight1() {
194+
// Rotating left by 31 should be same as rotating right by 1
195+
int value = 0x55555555;
196+
assertEquals(BitRotate.rotateLeft(value, 31), BitRotate.rotateRight(value, 1));
197+
}
198+
199+
@Test
200+
public void testTraversals() {
201+
// Test that methods don't throw exceptions
202+
assertDoesNotThrow(() -> BitRotate.rotateLeft(1, 1));
203+
assertDoesNotThrow(() -> BitRotate.rotateRight(1, 1));
204+
}
205+
}

0 commit comments

Comments
 (0)