diff --git a/include/QF/Makemodule.am b/include/QF/Makemodule.am
index 3892a430e..d36dd7ead 100644
--- a/include/QF/Makemodule.am
+++ b/include/QF/Makemodule.am
@@ -115,6 +115,7 @@ include_qf_input = \
 	include/QF/input/imt.h
 
 include_qf_math = \
+	include/QF/math/bitop.h \
 	include/QF/math/dual.h \
 	include/QF/math/half.h \
 	include/QF/math/matrix3.h \
diff --git a/include/QF/math/bitop.h b/include/QF/math/bitop.h
new file mode 100644
index 000000000..ee00ac6ca
--- /dev/null
+++ b/include/QF/math/bitop.h
@@ -0,0 +1,73 @@
+/*
+	bitop.h
+
+	bit-op functions
+
+	Copyright (C) 2022 Bill Currie <bill@taniwha.org>
+
+	Author: Bill Currie <bill@taniwha.org>
+	Date: 2022/1/23
+
+	This program is free software; you can redistribute it and/or
+	modify it under the terms of the GNU General Public License
+	as published by the Free Software Foundation; either version 2
+	of the License, or (at your option) any later version.
+
+	This program is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+	See the GNU General Public License for more details.
+
+	You should have received a copy of the GNU General Public License
+	along with this program; if not, write to:
+
+		Free Software Foundation, Inc.
+		59 Temple Place - Suite 330
+		Boston, MA  02111-1307, USA
+
+*/
+
+#ifndef __QF_math_bitop_h
+#define __QF_math_bitop_h
+
+/** \defgroup mathlib_bitop Bit-op functions
+	\ingroup utils
+*/
+///@{
+
+#include "QF/qtypes.h"
+
+#define BITOP_RUP1__(x)  (            (x) | (            (x) >>  1))
+#define BITOP_RUP2__(x)  (BITOP_RUP1__(x) | (BITOP_RUP1__(x) >>  2))
+#define BITOP_RUP4__(x)  (BITOP_RUP2__(x) | (BITOP_RUP2__(x) >>  4))
+#define BITOP_RUP8__(x)  (BITOP_RUP4__(x) | (BITOP_RUP4__(x) >>  8))
+#define BITOP_RUP16__(x) (BITOP_RUP8__(x) | (BITOP_RUP8__(x) >> 16))
+/** Round x up to the next power of two.
+
+	Rounds x up to the next power of two leaving exact powers of two
+	untouched.
+
+	\param x	The value to round
+	\return		The next higher power of two or x if it already is a power
+				of two.
+*/
+#define BITOP_RUP(x) (BITOP_RUP16__((uint32_t)(x) - 1) + 1)
+
+#define BITOP_LOG2__(x) (((((x) & 0xffff0000) != 0) << 4) \
+						|((((x) & 0xff00ff00) != 0) << 3) \
+						|((((x) & 0xf0f0f0f0) != 0) << 2) \
+						|((((x) & 0xcccccccc) != 0) << 1) \
+						|((((x) & 0xaaaaaaaa) != 0) << 0))
+/** Log base 2 rounded up.
+
+	Finds the base 2 logarithm of x rounded up (ceil(log2(x))).
+
+	\param x	The value for which to find the base 2 logarithm.
+	\return     Log base 2 of x, rounded up (2 -> 1, 3 -> 2, 4 -> 2)
+*/
+#define BITOP_LOG2(x) BITOP_LOG2__(BITOP_RUP(x))
+
+///@}
+
+#endif // __QF_math_bitop_h
diff --git a/libs/util/test/Makemodule.am b/libs/util/test/Makemodule.am
index 188239e11..7fa5a5f98 100644
--- a/libs/util/test/Makemodule.am
+++ b/libs/util/test/Makemodule.am
@@ -1,6 +1,7 @@
 libs_util_tests = \
 	libs/util/test/test-bary \
 	libs/util/test/test-baryvf \
+	libs/util/test/test-bitop \
 	libs/util/test/test-bsearch \
 	libs/util/test/test-cexpr \
 	libs/util/test/test-cmem \
@@ -36,6 +37,10 @@ libs_util_test_test_baryvf_SOURCES=libs/util/test/test-baryvf.c
 libs_util_test_test_baryvf_LDADD=libs/util/libQFutil.la
 libs_util_test_test_baryvf_DEPENDENCIES=libs/util/libQFutil.la
 
+libs_util_test_test_bitop_SOURCES=libs/util/test/test-bitop.c
+libs_util_test_test_bitop_LDADD=libs/util/libQFutil.la
+libs_util_test_test_bitop_DEPENDENCIES=libs/util/libQFutil.la
+
 libs_util_test_test_bsearch_SOURCES=libs/util/test/test-bsearch.c
 libs_util_test_test_bsearch_LDADD=libs/util/libQFutil.la
 libs_util_test_test_bsearch_DEPENDENCIES=libs/util/libQFutil.la
diff --git a/libs/util/test/test-bitop.c b/libs/util/test/test-bitop.c
new file mode 100644
index 000000000..795d023f5
--- /dev/null
+++ b/libs/util/test/test-bitop.c
@@ -0,0 +1,55 @@
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+#include <stdio.h>
+
+#include "QF/math/bitop.h"
+
+struct {
+	uint32_t     value;
+	uint32_t     expect;
+} tests [] = {
+	{BITOP_RUP(1), 1},
+	{BITOP_RUP(2), 2},
+	{BITOP_RUP(3), 4},
+	{BITOP_RUP(4), 4},
+	{BITOP_RUP(5), 8},
+	{BITOP_RUP(7), 8},
+	{BITOP_RUP(8), 8},
+	{BITOP_RUP(0x40000000), 0x40000000},
+	{BITOP_RUP(0x40000001), 0x80000000},
+	{BITOP_LOG2(1), 0},
+	{BITOP_LOG2(2), 1},
+	{BITOP_LOG2(3), 2},
+	{BITOP_LOG2(4), 2},
+	{BITOP_LOG2(5), 3},
+	{BITOP_LOG2(7), 3},
+	{BITOP_LOG2(8), 3},
+	{BITOP_LOG2(9), 4},
+	{BITOP_LOG2(15), 4},
+	{BITOP_LOG2(16), 4},
+	{BITOP_LOG2(17), 5},
+	{BITOP_LOG2(31), 5},
+	{BITOP_LOG2(32), 5},
+	{BITOP_LOG2(33), 6},
+	{BITOP_LOG2(0x40000000), 30},
+	{BITOP_LOG2(0x40000001), 31},
+};
+#define num_tests (sizeof (tests) / sizeof (tests[0]))
+
+int
+main (int argc, const char **argv)
+{
+	size_t      i;
+	int         res = 0;
+
+	for (i = 0; i < num_tests; i++) {
+		if (tests[i].value != tests[i].expect) {
+			res |= 1;
+			printf ("test %d failed\n", (int) i);
+			printf ("expect: %8x\n", tests[i].expect);
+			printf ("got   : %8x\n", tests[i].value);
+		}
+	}
+	return res;
+}