Figured it out. The trick is you can clamp an integer between 0 and 1 by dividing 1 by it, to "unpack" it you divide 1 by the result and the way that floats work I think you could technically use all 32 bits in the 0-1 range. The problem is precision.
If you're using Shader Model 4.0 you can use bitwise operators for the integer bitfield, otherwise, you can do this:
Read a bit from float:
fmod(1.0 / num, pow(2, bit + 1)) >= pow(2, bit);
Set bit that is 0 to 1 (when I set a bit to 1 I know it's 0 if that's not the case you could always read the bit to check first):
num = 1.0 / (round(1.0 / num) + pow(2, bit))
You can store a bitfield of 23 bits in a float with this method, but beyond 23 bits the precision is too bad for the round function to handle.
For RGB this gives you a bit field of 23*3 = 69 bits. You can access RGB component by integer division:
rgba[bit / 23]
Get bit in RGB component with modulus: