diff --git a/include/bx/inline/allocator.inl b/include/bx/inline/allocator.inl
index b237be525..646ebbc04 100644
--- a/include/bx/inline/allocator.inl
+++ b/include/bx/inline/allocator.inl
@@ -24,12 +24,11 @@ namespace bx
 
 	inline void* alignPtr(void* _ptr, size_t _extra, size_t _align)
 	{
-		union { void* ptr; uintptr_t addr; } un;
-		un.ptr = _ptr;
-		uintptr_t unaligned = un.addr + _extra; // space for header
-		uintptr_t aligned = bx::alignUp(unaligned, int32_t(_align) );
-		un.addr = aligned;
-		return un.ptr;
+		const uintptr_t addr = bitCast<uintptr_t>(_ptr);
+		const uintptr_t unaligned = addr + _extra; // space for header
+		const uintptr_t aligned = bx::alignUp(unaligned, int32_t(_align) );
+
+		return bitCast<void*>(aligned);
 	}
 
 	inline void* alloc(AllocatorI* _allocator, size_t _size, size_t _align, const Location& _location)
diff --git a/include/bx/inline/pixelformat.inl b/include/bx/inline/pixelformat.inl
index d07be313e..1d62bfd2f 100644
--- a/include/bx/inline/pixelformat.inl
+++ b/include/bx/inline/pixelformat.inl
@@ -21,9 +21,7 @@ namespace bx
 
 	inline int32_t toSnorm(float _value, float _scale)
 	{
-		return int32_t(round(
-					clamp(_value, -1.0f, 1.0f) * _scale)
-					);
+		return int32_t(round(clamp(_value, -1.0f, 1.0f) * _scale) );
 	}
 
 	inline float fromSnorm(int32_t _value, float _scale)
@@ -721,46 +719,49 @@ namespace bx
 		memCopy(_dst, _src, 8);
 	}
 
-	template<int32_t MantissaBits, int32_t ExpBits>
+	template<int32_t MantissaBitsT, int32_t ExpBitsT>
 	inline void encodeRgbE(float* _dst, const float* _src)
 	{
 		// Reference(s):
 		// - https://web.archive.org/web/20181126040035/https://www.khronos.org/registry/OpenGL/extensions/EXT/EXT_texture_shared_exponent.txt
 		//
-		const int32_t expMax  = (1<<ExpBits) - 1;
-		const int32_t expBias = (1<<(ExpBits - 1) ) - 1;
+		const int32_t expMax  = (1<< ExpBitsT      ) - 1;
+		const int32_t expBias = (1<<(ExpBitsT - 1) ) - 1;
 		const float   sharedExpMax = float(expMax) / float(expMax + 1) * float(1 << (expMax - expBias) );
 
 		const float rr = clamp(_src[0], 0.0f, sharedExpMax);
 		const float gg = clamp(_src[1], 0.0f, sharedExpMax);
 		const float bb = clamp(_src[2], 0.0f, sharedExpMax);
 		const float mm = max(rr, gg, bb);
-		union { float ff; uint32_t ui; } cast = { mm };
-		int32_t expShared = int32_t(uint32_imax(uint32_t(-expBias-1), ( ( (cast.ui>>23) & 0xff) - 127) ) ) + 1 + expBias;
-		float denom = pow(2.0f, float(expShared - expBias - MantissaBits) );
+		const uint32_t mm_as_ui = bitCast<uint32_t>(mm);
 
-		if ( (1<<MantissaBits) == int32_t(round(mm/denom) ) )
+		int32_t expShared = int32_t(max(uint32_t(-expBias-1), ( ( (mm_as_ui>>23) & 0xff) - 127) ) ) + 1 + expBias;
+		float denom = pow(2.0f, float(expShared - expBias - MantissaBitsT) );
+
+		if ( (1<<MantissaBitsT) == int32_t(round(mm/denom) ) )
 		{
 			denom *= 2.0f;
 			++expShared;
 		}
 
-		const float invDenom = 1.0f/denom;
+		const float invDenom = rcpSafe(denom);
 		_dst[0] = round(rr * invDenom);
 		_dst[1] = round(gg * invDenom);
 		_dst[2] = round(bb * invDenom);
 		_dst[3] = float(expShared);
 	}
 
-	template<int32_t MantissaBits, int32_t ExpBits>
+	template<int32_t MantissaBitsT, int32_t ExpBitsT>
 	inline void decodeRgbE(float* _dst, const float* _src)
 	{
-		const int32_t expBias = (1<<(ExpBits - 1) ) - 1;
-		const float exponent  = _src[3]-float(expBias-MantissaBits);
+		const int32_t expBias = (1<<(ExpBitsT - 1) ) - 1;
+		const float exponent  = _src[3]-float(expBias-MantissaBitsT);
 		const float scale     = pow(2.0f, exponent);
-		_dst[0] = _src[0] * scale;
-		_dst[1] = _src[1] * scale;
-		_dst[2] = _src[2] * scale;
+		const float invScale  = rcpSafe(scale);
+
+		_dst[0] = _src[0] * invScale;
+		_dst[1] = _src[1] * invScale;
+		_dst[2] = _src[2] * invScale;
 	}
 
 	// RGB9E5F
@@ -779,12 +780,12 @@ namespace bx
 
 	inline void unpackRgb9E5F(float* _dst, const void* _src)
 	{
-		uint32_t packed = *( (const uint32_t*)_src);
+		const uint32_t packed = *( (const uint32_t*)_src);
 
 		float tmp[4];
-		tmp[0] = float( ( (packed    ) & 0x1ff) ) / 511.0f;
-		tmp[1] = float( ( (packed>> 9) & 0x1ff) ) / 511.0f;
-		tmp[2] = float( ( (packed>>18) & 0x1ff) ) / 511.0f;
+		tmp[0] = float( ( (packed    ) & 0x1ff) );
+		tmp[1] = float( ( (packed>> 9) & 0x1ff) );
+		tmp[2] = float( ( (packed>>18) & 0x1ff) );
 		tmp[3] = float( ( (packed>>27) &  0x1f) );
 
 		decodeRgbE<9, 5>(_dst, tmp);
diff --git a/include/bx/inline/uint32_t.inl b/include/bx/inline/uint32_t.inl
index 3daf8f752..34b92a19d 100644
--- a/include/bx/inline/uint32_t.inl
+++ b/include/bx/inline/uint32_t.inl
@@ -649,15 +649,15 @@ namespace bx
 	template<typename Ty>
 	inline BX_CONSTEXPR_FUNC bool isAligned(Ty* _ptr, int32_t _align)
 	{
-		union { const void* ptr; uintptr_t addr; } un = { _ptr };
-		return isAligned(un.addr, _align);
+		const uintptr_t addr = bitCast<uintptr_t>(_ptr);
+		return isAligned(addr, _align);
 	}
 
 	template<typename Ty>
 	inline BX_CONSTEXPR_FUNC bool isAligned(const Ty* _ptr, int32_t _align)
 	{
-		union { const void* ptr; uintptr_t addr; } un = { _ptr };
-		return isAligned(un.addr, _align);
+		const uintptr_t addr = bitCast<uintptr_t>(_ptr);
+		return isAligned(addr, _align);
 	}
 
 	template<typename Ty>
@@ -670,17 +670,17 @@ namespace bx
 	template<typename Ty>
 	inline BX_CONSTEXPR_FUNC Ty* alignDown(Ty* _ptr, int32_t _align)
 	{
-		union { Ty* ptr; uintptr_t addr; } un = { _ptr };
-		un.addr = alignDown(un.addr, _align);
-		return un.ptr;
+		uintptr_t addr = bitCast<uintptr_t>(_ptr);
+		addr = alignDown(addr, _align);
+		return bitCast<Ty*>(addr);
 	}
 
 	template<typename Ty>
 	inline BX_CONSTEXPR_FUNC const Ty* alignDown(const Ty* _ptr, int32_t _align)
 	{
-		union { const Ty* ptr; uintptr_t addr; } un = { _ptr };
-		un.addr = alignDown(un.addr, _align);
-		return un.ptr;
+		uintptr_t addr = bitCast<uintptr_t>(_ptr);
+		addr = alignDown(addr, _align);
+		return bitCast<const Ty*>(addr);
 	}
 
 	template<typename Ty>
@@ -693,23 +693,22 @@ namespace bx
 	template<typename Ty>
 	inline BX_CONSTEXPR_FUNC Ty* alignUp(Ty* _ptr, int32_t _align)
 	{
-		union { Ty* ptr; uintptr_t addr; } un = { _ptr };
-		un.addr = alignUp(un.addr, _align);
-		return un.ptr;
+		uintptr_t addr = bitCast<uintptr_t>(_ptr);
+		addr = alignUp(addr, _align);
+		return bitCast<Ty*>(addr);
 	}
 
 	template<typename Ty>
 	inline BX_CONSTEXPR_FUNC const Ty* alignUp(const Ty* _ptr, int32_t _align)
 	{
-		union { const Ty* ptr; uintptr_t addr; } un = { _ptr };
-		un.addr = alignUp(un.addr, _align);
-		return un.ptr;
+		uintptr_t addr = bitCast<uintptr_t>(_ptr);
+		addr = alignUp(addr, _align);
+		return bitCast<const Ty*>(addr);
 	}
 
 	inline BX_CONST_FUNC uint16_t halfFromFloat(float _a)
 	{
-		union { uint32_t ui; float flt; } ftou;
-		ftou.flt = _a;
+		const uint32_t a_as_ui = bitCast<uint32_t>(_a);
 
 		const uint32_t one                       = uint32_li(0x00000001);
 		const uint32_t f_s_mask                  = uint32_li(kFloatSignMask);
@@ -728,13 +727,13 @@ namespace bx
 		const uint32_t f_h_m_pos_offset          = uint32_li(0x0000000d);
 		const uint32_t h_nan_min                 = uint32_li(0x00007c01);
 		const uint32_t f_h_e_biased_flag         = uint32_li(0x0000008f);
-		const uint32_t f_s                       = uint32_and(ftou.ui, f_s_mask);
-		const uint32_t f_e                       = uint32_and(ftou.ui, f_e_mask);
+		const uint32_t f_s                       = uint32_and(a_as_ui, f_s_mask);
+		const uint32_t f_e                       = uint32_and(a_as_ui, f_e_mask);
 		const uint16_t h_s                       = (uint16_t)uint32_srl(f_s, f_h_s_pos_offset);
-		const uint32_t f_m                       = uint32_and(ftou.ui, f_m_mask);
+		const uint32_t f_m                       = uint32_and(a_as_ui, f_m_mask);
 		const uint16_t f_e_amount                = (uint16_t)uint32_srl(f_e, f_e_pos);
 		const uint32_t f_e_half_bias             = uint32_sub(f_e_amount, f_h_bias_offset);
-		const uint32_t f_snan                    = uint32_and(ftou.ui, f_snan_mask);
+		const uint32_t f_snan                    = uint32_and(a_as_ui, f_snan_mask);
 		const uint32_t f_m_round_mask            = uint32_and(f_m, f_m_round_bit);
 		const uint32_t f_m_round_offset          = uint32_sll(f_m_round_mask, one);
 		const uint32_t f_m_rounded               = uint32_add(f_m, f_m_round_offset);
@@ -770,7 +769,7 @@ namespace bx
 		const uint32_t h_em_snan_result          = uint32_sels(is_f_snan_msb, h_snan_mask, h_em_denorm_result);
 		const uint32_t h_result                  = uint32_or(h_s, h_em_snan_result);
 
-		return (uint16_t)(h_result);
+		return uint16_t(h_result);
 	}
 
 	inline BX_CONST_FUNC float halfToFloat(uint16_t _a)
@@ -817,9 +816,7 @@ namespace bx
 		const uint32_t f_nan_result         = uint32_sels(is_nan_msb, f_em_nan, f_inf_result);
 		const uint32_t f_result             = uint32_or(f_s, f_nan_result);
 
-		union { uint32_t ui; float flt; } utof;
-		utof.ui = f_result;
-		return utof.flt;
+		return bitCast<float>(f_result);
 	}
 
 } // namespace bx
diff --git a/src/dtoa.cpp b/src/dtoa.cpp
index e65c2ffba..47c057a5d 100644
--- a/src/dtoa.cpp
+++ b/src/dtoa.cpp
@@ -606,12 +606,6 @@ namespace bx
 #define DOUBLE_PLUS_INFINITY  UINT64_C(0x7ff0000000000000)
 #define DOUBLE_MINUS_INFINITY UINT64_C(0xfff0000000000000)
 
-	union HexDouble
-	{
-		double d;
-		uint64_t u;
-	};
-
 #define lsr96(s2, s1, s0, d2, d1, d0)      \
 	d0 = ( (s0) >> 1) | ( ( (s1) & 1) << 31); \
 	d1 = ( (s1) >> 1) | ( ( (s2) & 1) << 31); \
@@ -932,13 +926,12 @@ namespace bx
 	static double converter(PrepNumber* _pn)
 	{
 		int binexp = 92;
-		HexDouble hd;
 		uint32_t s2, s1, s0; /* 96-bit precision integer */
 		uint32_t q2, q1, q0; /* 96-bit precision integer */
 		uint32_t r2, r1, r0; /* 96-bit precision integer */
 		uint32_t mask28 = UINT32_C(0xf) << 28;
 
-		hd.u = 0;
+		uint64_t hdu = 0;
 
 		s0 = (uint32_t)(_pn->mantissa & UINT32_MAX);
 		s1 = (uint32_t)(_pn->mantissa >> 32);
@@ -1011,18 +1004,18 @@ namespace bx
 		{
 			if (_pn->negative)
 			{
-				hd.u = DOUBLE_MINUS_INFINITY;
+				hdu = DOUBLE_MINUS_INFINITY;
 			}
 			else
 			{
-				hd.u = DOUBLE_PLUS_INFINITY;
+				hdu = DOUBLE_PLUS_INFINITY;
 			}
 		}
 		else if (binexp < 1)
 		{
 			if (_pn->negative)
 			{
-				hd.u = DOUBLE_MINUS_ZERO;
+				hdu = DOUBLE_MINUS_ZERO;
 			}
 		}
 		else if (s2)
@@ -1039,10 +1032,10 @@ namespace bx
 				q |= (1ULL << 63);
 			}
 
-			hd.u = q;
+			hdu = q;
 		}
 
-		return hd.d;
+		return bitCast<double>(hdu);
 	}
 
 	int32_t toString(char* _out, int32_t _max, bool _value)
@@ -1074,9 +1067,6 @@ namespace bx
 		pn.negative = 0;
 		pn.exponent = 0;
 
-		HexDouble hd;
-		hd.u = DOUBLE_PLUS_ZERO;
-
 		switch (parser(_str.getPtr(), _str.getTerm(), &pn) )
 		{
 		case PARSER_OK:
@@ -1084,22 +1074,19 @@ namespace bx
 			break;
 
 		case PARSER_PZERO:
-			*_out = hd.d;
+			*_out = bitCast<double>(DOUBLE_PLUS_ZERO);
 			break;
 
 		case PARSER_MZERO:
-			hd.u = DOUBLE_MINUS_ZERO;
-			*_out = hd.d;
+			*_out = bitCast<double>(DOUBLE_MINUS_ZERO);
 			break;
 
 		case PARSER_PINF:
-			hd.u = DOUBLE_PLUS_INFINITY;
-			*_out = hd.d;
+			*_out = bitCast<double>(DOUBLE_PLUS_INFINITY);
 			break;
 
 		case PARSER_MINF:
-			hd.u = DOUBLE_MINUS_INFINITY;
-			*_out = hd.d;
+			*_out = bitCast<double>(DOUBLE_MINUS_INFINITY);
 			break;
 		}
 
diff --git a/tests/pixelformat_test.cpp b/tests/pixelformat_test.cpp
new file mode 100644
index 000000000..03119688a
--- /dev/null
+++ b/tests/pixelformat_test.cpp
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2010-2024 Branimir Karadzic. All rights reserved.
+ * License: https://github.com/bkaradzic/bx/blob/master/LICENSE
+ */
+
+#include "test.h"
+#include <bx/pixelformat.h>
+
+TEST_CASE("pack/unpack Rgba8", "[pixelformat]")
+{
+	float rgba[4] = { 0.1f, 0.3f, 0.8f, 0.9f };
+	uint32_t encoded;
+	bx::packRgba8(&encoded, rgba);
+
+	float decoded[4];
+	bx::unpackRgba8(decoded, &encoded);
+
+	REQUIRE(bx::isEqual(rgba, decoded, 4, 0.01f) );
+}
+
+TEST_CASE("pack/unpack Rgb9E5F", "[pixelformat]")
+{
+	float rgba[3] = { 0.1f, 0.3f, 0.89f };
+	uint32_t encoded;
+	bx::packRgb9E5F(&encoded, rgba);
+
+	float decoded[3];
+	bx::unpackRgb9E5F(decoded, &encoded);
+
+	REQUIRE(bx::isEqual(rgba, decoded, BX_COUNTOF(rgba), 0.001f) );
+}