diff --git a/vlib/rand/mt19937/mt19937.v b/vlib/rand/mt19937/mt19937.v
index bb6d3dfe45..43a80e7455 100644
--- a/vlib/rand/mt19937/mt19937.v
+++ b/vlib/rand/mt19937/mt19937.v
@@ -44,6 +44,8 @@ C++ functions for MT19937, with initialization improved 2002/2/10.
    http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/emt.html
    email: m-mat @ math.sci.hiroshima-u.ac.jp (remove space)
 */
+pub const seed_len = 2
+
 const (
 	nn            = 312
 	mm            = 156
@@ -57,10 +59,10 @@ const (
 // **NOTE**: The RNG is not seeded when instantiated so remember to seed it before use.
 pub struct MT19937RNG {
 mut:
-	state    []u64 = []u64{len: mt19937.nn}
-	mti      int   = mt19937.nn
-	next_rnd u32
-	has_next bool
+	state      []u64 = []u64{len: mt19937.nn}
+	mti        int   = mt19937.nn
+	bytes_left int
+	buffer     u64
 }
 
 // calculate_state returns a random state array calculated from the `seed_data`.
@@ -85,21 +87,55 @@ pub fn (mut rng MT19937RNG) seed(seed_data []u32) {
 	rng.state = calculate_state(seed_data, mut rng.state)
 	rng.state = calculate_state(seed_data, mut rng.state)
 	rng.mti = mt19937.nn
-	rng.next_rnd = 0
-	rng.has_next = false
+	rng.bytes_left = 0
+	rng.buffer = 0
+}
+
+// byte returns a uniformly distributed pseudorandom 8-bit unsigned positive `byte`.
+[inline]
+pub fn (mut rng MT19937RNG) byte() byte {
+	if rng.bytes_left >= 1 {
+		rng.bytes_left -= 1
+		value := byte(rng.buffer)
+		rng.buffer >>= 8
+		return value
+	}
+	rng.buffer = rng.u64()
+	rng.bytes_left = 7
+	value := byte(rng.buffer)
+	rng.buffer >>= 8
+	return value
+}
+
+// u16 returns a pseudorandom 16bit int in range `[0, 2¹⁶)`.
+[inline]
+pub fn (mut rng MT19937RNG) u16() u16 {
+	if rng.bytes_left >= 2 {
+		rng.bytes_left -= 2
+		value := u16(rng.buffer)
+		rng.buffer >>= 16
+		return value
+	}
+	ans := rng.u64()
+	rng.buffer = ans >> 16
+	rng.bytes_left = 6
+	return u16(ans)
 }
 
 // u32 returns a pseudorandom 32bit int in range `[0, 2³²)`.
 [inline]
 pub fn (mut rng MT19937RNG) u32() u32 {
-	if rng.has_next {
-		rng.has_next = false
-		return rng.next_rnd
+	// Can we take a whole u32 out of the buffer?
+	if rng.bytes_left >= 4 {
+		rng.bytes_left -= 4
+		value := u32(rng.buffer)
+		rng.buffer >>= 32
+		return value
 	}
 	ans := rng.u64()
-	rng.next_rnd = u32(ans >> 32)
-	rng.has_next = true
-	return u32(ans & 0xffffffff)
+	rng.buffer = ans >> 32
+	rng.bytes_left = 4
+	return u32(ans)
 }
 
 // u64 returns a pseudorandom 64bit int in range `[0, 2⁶⁴)`.
@@ -131,6 +167,12 @@ pub fn (mut rng MT19937RNG) u64() u64 {
 	return x
 }
 
+// block_size returns the number of bits that the RNG can produce in a single iteration.
+[inline]
+pub fn (mut rng MT19937RNG) block_size() int {
+	return 64
+}
+
 // free should be called when the generator is no longer needed
 [unsafe]
 pub fn (mut rng MT19937RNG) free() {
diff --git a/vlib/rand/musl/musl_rng.v b/vlib/rand/musl/musl_rng.v
index ff008308d7..5ae644ae8f 100644
--- a/vlib/rand/musl/musl_rng.v
+++ b/vlib/rand/musl/musl_rng.v
@@ -5,10 +5,14 @@ module musl
 
 import rand.seed
 
+pub const seed_len = 1
+
 // MuslRNG ported from https://git.musl-libc.org/cgit/musl/tree/src/prng/rand_r.c
 pub struct MuslRNG {
 mut:
-	state u32 = seed.time_seed_32()
+	state      u32 = seed.time_seed_32()
+	bytes_left int
+	buffer     u32
 }
 
 // seed sets the current random state based on `seed_data`.
@@ -19,6 +23,39 @@ pub fn (mut rng MuslRNG) seed(seed_data []u32) {
 		exit(1)
 	}
 	rng.state = seed_data[0]
+	rng.bytes_left = 0
+	rng.buffer = 0
+}
+
+// byte returns a uniformly distributed pseudorandom 8-bit unsigned positive `byte`.
+[inline]
+fn (mut rng MuslRNG) byte() byte {
+	if rng.bytes_left >= 1 {
+		rng.bytes_left -= 1
+		value := byte(rng.buffer)
+		rng.buffer >>= 8
+		return value
+	}
+	rng.buffer = rng.u32()
+	rng.bytes_left = 3
+	value := byte(rng.buffer)
+	rng.buffer >>= 8
+	return value
+}
+
+// u16 returns a pseudorandom 16-bit unsigned integer (`u16`).
+[inline]
+pub fn (mut rng MuslRNG) u16() u16 {
+	if rng.bytes_left >= 2 {
+		rng.bytes_left -= 2
+		value := u16(rng.buffer)
+		rng.buffer >>= 16
+		return value
+	}
+	ans := rng.u32()
+	rng.buffer = ans >> 16
+	rng.bytes_left = 2
+	return u16(ans)
 }
 
 // temper returns a tempered value based on `prev` value.
@@ -33,8 +70,7 @@ fn temper(prev u32) u32 {
 }
 
 // u32 returns a pseudorandom 32-bit unsigned integer (`u32`).
-[inline]
-pub fn (mut rng MuslRNG) u32() u32 {
+fn (mut rng MuslRNG) u32() u32 {
 	rng.state = rng.state * 1103515245 + 12345
 	// We are not dividing by 2 (or shifting right by 1)
 	// because we want all 32-bits of random data
@@ -47,6 +83,12 @@ pub fn (mut rng MuslRNG) u64() u64 {
 	return u64(rng.u32()) | (u64(rng.u32()) << 32)
 }
 
+// block_size returns the number of bits that the RNG can produce in a single iteration.
+[inline]
+pub fn (mut rng MuslRNG) block_size() int {
+	return 32
+}
+
 // free should be called when the generator is no longer needed
 [unsafe]
 pub fn (mut rng MuslRNG) free() {
diff --git a/vlib/rand/pcg32/pcg32.v b/vlib/rand/pcg32/pcg32.v
index 0a49d9a92f..4df3ff7b8a 100644
--- a/vlib/rand/pcg32/pcg32.v
+++ b/vlib/rand/pcg32/pcg32.v
@@ -4,15 +4,18 @@
 module pcg32
 
 import rand.seed
-import rand.constants
+
+pub const seed_len = 4
 
 // PCG32RNG ported from http://www.pcg-random.org/download.html,
 // https://github.com/imneme/pcg-c-basic/blob/master/pcg_basic.c, and
 // https://github.com/imneme/pcg-c-basic/blob/master/pcg_basic.h
 pub struct PCG32RNG {
 mut:
-	state u64 = u64(0x853c49e6748fea9b) ^ seed.time_seed_64()
-	inc   u64 = u64(0xda3e39cb94b95bdb) ^ seed.time_seed_64()
+	state      u64 = u64(0x853c49e6748fea9b) ^ seed.time_seed_64()
+	inc        u64 = u64(0xda3e39cb94b95bdb) ^ seed.time_seed_64()
+	bytes_left int
+	buffer     u32
 }
 
 // seed seeds the PCG32RNG with 4 `u32` values.
@@ -30,11 +33,44 @@ pub fn (mut rng PCG32RNG) seed(seed_data []u32) {
 	rng.u32()
 	rng.state += init_state
 	rng.u32()
+	rng.bytes_left = 0
+	rng.buffer = 0
+}
+
+// byte returns a uniformly distributed pseudorandom 8-bit unsigned positive `byte`.
+[inline]
+fn (mut rng PCG32RNG) byte() byte {
+	if rng.bytes_left >= 1 {
+		rng.bytes_left -= 1
+		value := byte(rng.buffer)
+		rng.buffer >>= 8
+		return value
+	}
+	rng.buffer = rng.u32()
+	rng.bytes_left = 3
+	value := byte(rng.buffer)
+	rng.buffer >>= 8
+	return value
+}
+
+// u16 returns a pseudorandom 16-bit unsigned integer (`u16`).
+[inline]
+pub fn (mut rng PCG32RNG) u16() u16 {
+	if rng.bytes_left >= 2 {
+		rng.bytes_left -= 2
+		value := u16(rng.buffer)
+		rng.buffer >>= 16
+		return value
+	}
+	ans := rng.u32()
+	rng.buffer = ans >> 16
+	rng.bytes_left = 2
+	return u16(ans)
 }
 
 // u32 returns a pseudorandom unsigned `u32`.
 [inline]
-pub fn (mut rng PCG32RNG) u32() u32 {
+fn (mut rng PCG32RNG) u32() u32 {
 	oldstate := rng.state
 	rng.state = oldstate * (6364136223846793005) + rng.inc
 	xorshifted := u32(((oldstate >> u64(18)) ^ oldstate) >> u64(27))
@@ -48,6 +84,12 @@ pub fn (mut rng PCG32RNG) u64() u64 {
 	return u64(rng.u32()) | (u64(rng.u32()) << 32)
 }
 
+// block_size returns the number of bits that the RNG can produce in a single iteration.
+[inline]
+pub fn (mut rng PCG32RNG) block_size() int {
+	return 32
+}
+
 // free should be called when the generator is no longer needed
 [unsafe]
 pub fn (mut rng PCG32RNG) free() {
diff --git a/vlib/rand/rand.c.v b/vlib/rand/rand.c.v
index d7ab386f99..2ebb64c83d 100644
--- a/vlib/rand/rand.c.v
+++ b/vlib/rand/rand.c.v
@@ -147,16 +147,44 @@ fn init() {
 	C.atexit(deinit)
 }
 
-// read fills in `buf` a maximum of `buf.len` random bytes
-pub fn read(mut buf []byte) {
+fn read_32(mut rng PRNG, mut buf []byte) {
+	p32 := unsafe { &u32(buf.data) }
+	u32s := buf.len / 4
+	for i in 0 .. u32s {
+		unsafe {
+			*(p32 + i) = rng.u32()
+		}
+	}
+	for i in u32s * 4 .. buf.len {
+		buf[i] = rng.byte()
+	}
+}
+
+fn read_64(mut rng PRNG, mut buf []byte) {
 	p64 := unsafe { &u64(buf.data) }
 	u64s := buf.len / 8
 	for i in 0 .. u64s {
 		unsafe {
-			*(p64 + i) = default_rng.u64()
+			*(p64 + i) = rng.u64()
 		}
 	}
 	for i in u64s * 8 .. buf.len {
-		buf[i] = byte(default_rng.u32())
+		buf[i] = rng.byte()
+	}
+}
+
+fn read_internal(mut rng PRNG, mut buf []byte) {
+	match rng.block_size() {
+		32 {
+			read_32(mut rng, mut buf)
+		}
+		64 {
+			read_64(mut rng, mut buf)
+		}
+		else {
+			for i in 0 .. buf.len {
+				buf[i] = rng.byte()
+			}
+		}
 	}
 }
diff --git a/vlib/rand/rand.js.v b/vlib/rand/rand.js.v
index 1403c5f1df..28f452593e 100644
--- a/vlib/rand/rand.js.v
+++ b/vlib/rand/rand.js.v
@@ -66,9 +66,8 @@ pub fn ulid_at_millisecond(unix_time_milli u64) string {
 	return res
 }
 
-// read fills in `buf` a maximum of `buf.len` random bytes
-pub fn read(mut buf []byte) {
+fn read_internal(mut rng PRNG, mut buf []byte) {
 	for i in 0 .. buf.len {
-		buf[i] = byte(default_rng.u32())
+		buf[i] = rng.byte()
 	}
 }
diff --git a/vlib/rand/rand.v b/vlib/rand/rand.v
index 400a69928d..17cc705e87 100644
--- a/vlib/rand/rand.v
+++ b/vlib/rand/rand.v
@@ -15,53 +15,25 @@ import rand.wyrand
 pub interface PRNG {
 mut:
 	seed(seed_data []u32)
-	// TODO: Support buffering for bytes
-	// byte() byte
-	// bytes(bytes_needed int) ?[]byte
-	// u16() u16
+	byte() byte
+	u16() u16
 	u32() u32
 	u64() u64
+	block_size() int
 	free()
 }
 
-// byte returns a uniformly distributed pseudorandom 8-bit unsigned positive `byte`.
-[inline]
-pub fn (mut rng PRNG) byte() byte {
-	// TODO: Reimplement for all PRNGs efficiently
-	return byte(rng.u32() & 0xff)
-}
-
-// bytes returns a buffer of `bytes_needed` random bytes.
+// bytes returns a buffer of `bytes_needed` random bytes
 [inline]
 pub fn (mut rng PRNG) bytes(bytes_needed int) ?[]byte {
-	// TODO: Reimplement for all PRNGs efficiently
 	if bytes_needed < 0 {
 		return error('can not read < 0 random bytes')
 	}
-	mut res := []byte{cap: bytes_needed}
-	mut remaining := bytes_needed
 
-	for remaining > 8 {
-		mut value := rng.u64()
-		for _ in 0 .. 8 {
-			res << byte(value & 0xff)
-			value >>= 8
-		}
-		remaining -= 8
-	}
-	for remaining > 4 {
-		mut value := rng.u32()
-		for _ in 0 .. 4 {
-			res << byte(value & 0xff)
-			value >>= 8
-		}
-		remaining -= 4
-	}
-	for remaining > 0 {
-		res << rng.byte()
-		remaining -= 1
-	}
-	return res
+	mut buffer := []byte{len: bytes_needed}
+	read_internal(mut rng, mut buffer)
+
+	return buffer
 }
 
 // u32n returns a uniformly distributed pseudorandom 32-bit signed positive `u32` in range `[0, max)`.
@@ -140,6 +112,18 @@ pub fn (mut rng PRNG) u64_in_range(min u64, max u64) ?u64 {
 	return min + rng.u64n(max - min) ?
 }
 
+// i8 returns a (possibly negative) pseudorandom 8-bit `i8`.
+[inline]
+pub fn (mut rng PRNG) i8() i8 {
+	return i8(rng.byte())
+}
+
+// i16 returns a (possibly negative) pseudorandom 16-bit `i16`.
+[inline]
+pub fn (mut rng PRNG) i16() i16 {
+	return i16(rng.u16())
+}
+
 // int returns a (possibly negative) pseudorandom 32-bit `int`.
 [inline]
 pub fn (mut rng PRNG) int() int {
@@ -213,38 +197,38 @@ pub fn (mut rng PRNG) f64() f64 {
 	return f64(rng.u64()) / constants.max_u64_as_f64
 }
 
-// f32n returns a pseudorandom `f32` value in range `[0, max)`.
+// f32n returns a pseudorandom `f32` value in range `[0, max]`.
 [inline]
 pub fn (mut rng PRNG) f32n(max f32) ?f32 {
-	if max <= 0 {
-		return error('max has to be positive.')
+	if max < 0 {
+		return error('max has to be non-negative.')
 	}
 	return rng.f32() * max
 }
 
-// f64n returns a pseudorandom `f64` value in range `[0, max)`.
+// f64n returns a pseudorandom `f64` value in range `[0, max]`.
 [inline]
 pub fn (mut rng PRNG) f64n(max f64) ?f64 {
-	if max <= 0 {
-		return error('max has to be positive.')
+	if max < 0 {
+		return error('max has to be non-negative.')
 	}
 	return rng.f64() * max
 }
 
-// f32_in_range returns a pseudorandom `f32` in range `[min, max)`.
+// f32_in_range returns a pseudorandom `f32` in range `[min, max]`.
 [inline]
 pub fn (mut rng PRNG) f32_in_range(min f32, max f32) ?f32 {
-	if max <= min {
-		return error('max must be greater than min')
+	if max < min {
+		return error('max must be greater than or equal to min')
 	}
 	return min + rng.f32n(max - min) ?
 }
 
-// i64_in_range returns a pseudorandom `i64` in range `[min, max)`.
+// i64_in_range returns a pseudorandom `i64` in range `[min, max]`.
 [inline]
 pub fn (mut rng PRNG) f64_in_range(min f64, max f64) ?f64 {
-	if max <= min {
-		return error('max must be greater than min')
+	if max < min {
+		return error('max must be greater than or equal to min')
 	}
 	return min + rng.f64n(max - min) ?
 }
@@ -311,6 +295,11 @@ pub fn u64_in_range(min u64, max u64) ?u64 {
 	return default_rng.u64_in_range(min, max)
 }
 
+// i16 returns a uniformly distributed pseudorandom 16-bit signed (possibly negative) `i16`.
+pub fn i16() i16 {
+	return default_rng.i16()
+}
+
 // int returns a uniformly distributed pseudorandom 32-bit signed (possibly negative) `int`.
 pub fn int() int {
 	return default_rng.int()
@@ -392,6 +381,11 @@ pub fn bytes(bytes_needed int) ?[]byte {
 	return default_rng.bytes(bytes_needed)
 }
 
+// read fills in `buf` a maximum of `buf.len` random bytes
+pub fn read(mut buf []byte) {
+	read_internal(mut default_rng, mut buf)
+}
+
 const (
 	english_letters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
 	hex_chars       = 'abcdef0123456789'
diff --git a/vlib/rand/splitmix64/splitmix64.v b/vlib/rand/splitmix64/splitmix64.v
index b2660b202a..e38716e4c6 100644
--- a/vlib/rand/splitmix64/splitmix64.v
+++ b/vlib/rand/splitmix64/splitmix64.v
@@ -4,14 +4,15 @@
 module splitmix64
 
 import rand.seed
-import rand.constants
+
+pub const seed_len = 2
 
 // SplitMix64RNG ported from http://xoshiro.di.unimi.it/splitmix64.c
 pub struct SplitMix64RNG {
 mut:
-	state     u64 = seed.time_seed_64()
-	has_extra bool
-	extra     u32
+	state      u64 = seed.time_seed_64()
+	bytes_left int
+	buffer     u64
 }
 
 // seed sets the seed of the accepting SplitMix64RNG to the given data
@@ -22,25 +23,57 @@ pub fn (mut rng SplitMix64RNG) seed(seed_data []u32) {
 		exit(1)
 	}
 	rng.state = seed_data[0] | (u64(seed_data[1]) << 32)
-	rng.has_extra = false
+	rng.bytes_left = 0
+	rng.buffer = 0
 }
 
-// u32 updates the PRNG state and returns the next pseudorandom `u32`.
+// byte returns a uniformly distributed pseudorandom 8-bit unsigned positive `byte`.
+[inline]
+pub fn (mut rng SplitMix64RNG) byte() byte {
+	if rng.bytes_left >= 1 {
+		rng.bytes_left -= 1
+		value := byte(rng.buffer)
+		rng.buffer >>= 8
+		return value
+	}
+	rng.buffer = rng.u64()
+	rng.bytes_left = 7
+	value := byte(rng.buffer)
+	rng.buffer >>= 8
+	return value
+}
+
+// u16 returns a pseudorandom 16bit int in range `[0, 2¹⁶)`.
+[inline]
+pub fn (mut rng SplitMix64RNG) u16() u16 {
+	if rng.bytes_left >= 2 {
+		rng.bytes_left -= 2
+		value := u16(rng.buffer)
+		rng.buffer >>= 16
+		return value
+	}
+	ans := rng.u64()
+	rng.buffer = ans >> 16
+	rng.bytes_left = 6
+	return u16(ans)
+}
+
+// u32 returns a pseudorandom 32bit int in range `[0, 2³²)`.
 [inline]
 pub fn (mut rng SplitMix64RNG) u32() u32 {
-	if rng.has_extra {
-		rng.has_extra = false
-		return rng.extra
+	if rng.bytes_left >= 4 {
+		rng.bytes_left -= 4
+		value := u32(rng.buffer)
+		rng.buffer >>= 32
+		return value
 	}
-	full_value := rng.u64()
-	lower := u32(full_value & constants.lower_mask)
-	upper := u32(full_value >> 32)
-	rng.extra = upper
-	rng.has_extra = true
-	return lower
+	ans := rng.u64()
+	rng.buffer = ans >> 32
+	rng.bytes_left = 4
+	return u32(ans)
 }
 
-// u64 updates the PRNG state and returns the next pseudorandom `u64`.
+// u64 returns a pseudorandom 64bit int in range `[0, 2⁶⁴)`.
 [inline]
 pub fn (mut rng SplitMix64RNG) u64() u64 {
 	rng.state += (0x9e3779b97f4a7c15)
@@ -50,6 +83,12 @@ pub fn (mut rng SplitMix64RNG) u64() u64 {
 	return z ^ (z >> (31))
 }
 
+// block_size returns the number of bits that the RNG can produce in a single iteration.
+[inline]
+pub fn (mut rng SplitMix64RNG) block_size() int {
+	return 64
+}
+
 // free should be called when the generator is no longer needed
 [unsafe]
 pub fn (mut rng SplitMix64RNG) free() {
diff --git a/vlib/rand/sys/system_rng.c.v b/vlib/rand/sys/system_rng.c.v
index 42a3bdf835..b431d58fbb 100644
--- a/vlib/rand/sys/system_rng.c.v
+++ b/vlib/rand/sys/system_rng.c.v
@@ -16,9 +16,14 @@ import rand.seed
 // 2147483647. The repetition period also varies wildly. In order to provide more entropy
 // without altering the underlying algorithm too much, this implementation simply
 // requests for more random bits until the necessary width for the integers is achieved.
+
+pub const seed_len = 1
+
 const (
 	rand_limit     = u64(C.RAND_MAX)
 	rand_bitsize   = bits.len_64(rand_limit)
+	rand_bytesize  = rand_bitsize / 8
+	u16_iter_count = calculate_iterations_for(16)
 	u32_iter_count = calculate_iterations_for(32)
 	u64_iter_count = calculate_iterations_for(64)
 )
@@ -32,7 +37,9 @@ fn calculate_iterations_for(bits int) int {
 // SysRNG is the PRNG provided by default in the libc implementiation that V uses.
 pub struct SysRNG {
 mut:
-	seed u32 = seed.time_seed_32()
+	seed       u32 = seed.time_seed_32()
+	buffer     int
+	bytes_left int
 }
 
 // r.seed() sets the seed of the accepting SysRNG to the given data.
@@ -55,7 +62,39 @@ pub fn (r SysRNG) default_rand() int {
 	return C.rand()
 }
 
-// r.u32() returns a pseudorandom u32 value less than 2^32
+// byte returns a uniformly distributed pseudorandom 8-bit unsigned positive `byte`.
+[inline]
+pub fn (mut r SysRNG) byte() byte {
+	if r.bytes_left >= 1 {
+		r.bytes_left -= 1
+		value := byte(r.buffer)
+		r.buffer >>= 8
+		return value
+	}
+	r.buffer = r.default_rand()
+	r.bytes_left = sys.rand_bytesize - 1
+	value := byte(r.buffer)
+	r.buffer >>= 8
+	return value
+}
+
+// u16 returns a uniformly distributed pseudorandom 16-bit unsigned positive `u16`.
+[inline]
+pub fn (mut r SysRNG) u16() u16 {
+	if r.bytes_left >= 2 {
+		r.bytes_left -= 2
+		value := u16(r.buffer)
+		r.buffer >>= 16
+		return value
+	}
+	mut result := u16(C.rand())
+	for i in 1 .. sys.u16_iter_count {
+		result = result ^ (u16(C.rand()) << (sys.rand_bitsize * i))
+	}
+	return result
+}
+
+// u32 returns a uniformly distributed pseudorandom 32-bit unsigned positive `u32`.
 [inline]
 pub fn (r SysRNG) u32() u32 {
 	mut result := u32(C.rand())
@@ -65,7 +104,7 @@ pub fn (r SysRNG) u32() u32 {
 	return result
 }
 
-// r.u64() returns a pseudorandom u64 value less than 2^64
+// u64 returns a uniformly distributed pseudorandom 64-bit unsigned positive `u64`.
 [inline]
 pub fn (r SysRNG) u64() u64 {
 	mut result := u64(C.rand())
@@ -75,6 +114,12 @@ pub fn (r SysRNG) u64() u64 {
 	return result
 }
 
+// block_size returns the number of bits that the RNG can produce in a single iteration.
+[inline]
+pub fn (r SysRNG) block_size() int {
+	return sys.rand_bitsize
+}
+
 // free should be called when the generator is no longer needed
 [unsafe]
 pub fn (mut rng SysRNG) free() {
diff --git a/vlib/rand/wyrand/wyrand.v b/vlib/rand/wyrand/wyrand.v
index 34037429be..52e4e06f84 100644
--- a/vlib/rand/wyrand/wyrand.v
+++ b/vlib/rand/wyrand/wyrand.v
@@ -3,9 +3,8 @@
 // that can be found in the LICENSE file.
 module wyrand
 
-import rand.seed
-import rand.constants
 import hash
+import rand.seed
 
 // Redefinition of some constants that we will need for pseudorandom number generation.
 const (
@@ -16,9 +15,9 @@ const (
 // WyRandRNG is a RNG based on the WyHash hashing algorithm.
 pub struct WyRandRNG {
 mut:
-	state     u64 = seed.time_seed_64()
-	has_extra bool
-	extra     u32
+	state      u64 = seed.time_seed_64()
+	bytes_left int
+	buffer     u64
 }
 
 // seed sets the seed, needs only two `u32`s in little-endian format as [lower, higher].
@@ -28,25 +27,59 @@ pub fn (mut rng WyRandRNG) seed(seed_data []u32) {
 		exit(1)
 	}
 	rng.state = seed_data[0] | (u64(seed_data[1]) << 32)
-	rng.has_extra = false
+	rng.bytes_left = 0
+	rng.buffer = 0
 }
 
-// u32 updates the PRNG state and returns the next pseudorandom `u32`.
+// byte returns a uniformly distributed pseudorandom 8-bit unsigned positive `byte`.
+[inline]
+pub fn (mut rng WyRandRNG) byte() byte {
+	// Can we extract a value from the buffer?
+	if rng.bytes_left >= 1 {
+		rng.bytes_left -= 1
+		value := byte(rng.buffer)
+		rng.buffer >>= 8
+		return value
+	}
+	// Add a new value to the buffer
+	rng.buffer = rng.u64()
+	rng.bytes_left = 7
+	value := byte(rng.buffer)
+	rng.buffer >>= 8
+	return value
+}
+
+// u16 returns a pseudorandom 16bit int in range `[0, 2¹⁶)`.
+[inline]
+pub fn (mut rng WyRandRNG) u16() u16 {
+	if rng.bytes_left >= 2 {
+		rng.bytes_left -= 2
+		value := u16(rng.buffer)
+		rng.buffer >>= 16
+		return value
+	}
+	ans := rng.u64()
+	rng.buffer = ans >> 16
+	rng.bytes_left = 6
+	return u16(ans)
+}
+
+// u32 returns a pseudorandom 32bit int in range `[0, 2³²)`.
 [inline]
 pub fn (mut rng WyRandRNG) u32() u32 {
-	if rng.has_extra {
-		rng.has_extra = false
-		return rng.extra
+	if rng.bytes_left >= 4 {
+		rng.bytes_left -= 4
+		value := u32(rng.buffer)
+		rng.buffer >>= 32
+		return value
 	}
-	full_value := rng.u64()
-	lower := u32(full_value & constants.lower_mask)
-	upper := u32(full_value >> 32)
-	rng.extra = upper
-	rng.has_extra = true
-	return lower
+	ans := rng.u64()
+	rng.buffer = ans >> 32
+	rng.bytes_left = 4
+	return u32(ans)
 }
 
-// u64 updates the PRNG state and returns the next pseudorandom `u64`.
+// u64 returns a pseudorandom 64bit int in range `[0, 2⁶⁴)`.
 [inline]
 pub fn (mut rng WyRandRNG) u64() u64 {
 	unsafe {
@@ -57,3 +90,9 @@ pub fn (mut rng WyRandRNG) u64() u64 {
 	}
 	return 0
 }
+
+// block_size returns the number of bits that the RNG can produce in a single iteration.
+[inline]
+pub fn (mut rng WyRandRNG) block_size() int {
+	return 64
+}