Why can I get trailingZeroBitCount, but not leadingZeroBitCount for a variable using UnsignedInteger?

I’m porting some code that analyzes floating point numbers:

func tz<T>(_ f: T) -> T where T: BinaryFloatingPoint {
	let bits = f.significandBitPattern
	
	return T(bits.trailingZeroBitCount)
}

func lz<T>(_ f: T) -> T where T: BinaryFloatingPoint {
	let bits = f.significandBitPattern
	
	return T(bits.leadingZeroBitCount) // error
}

It currently isn’t very swifty. Make it work, make it good, make it fast.

I’m getting an error on the marked line: “Value of type 'T.RawSignificand' has no member 'leadingZeroBitCount'”.

Why can I get trailingZeroBitCount, but not leadingZeroBitCount for a variable conforming to UnsignedInteger?

Because an integer’s binary representation only has a particular number of leading zero bits if we’re dealing with a fixed bit width: The binary integer 0b1000 always has three trailing zero bits, but it can have 0, 1, 42, a million, or infinite leading zero bits. Put concretely, you need to constrain T.RawSignificand to conform to FixedWidthInteger.

Also, word of advice: I’d implement any code that deals with binary representations of floating-point types only for the concrete type or types you’re actually going to use (chances are, just Float and Double). Float80 doesn’t work like the other types, for example, so you’d either have to expend additional effort to make sure your generic implementation is correct or you’d have a “generic” implementation that isn’t actually. Then consider how you’re actually going to test that your generic code works for a generic floating-point type…

3 Likes

Thank you very much! That explanation makes total sense. :slight_smile:

I actually wanted to constrain to Float and Double. I couldn’t find another way that to use BinaryFloatingPoint. What would you suggest to constrain T thus?

I’d create my own protocol that refines BinaryFloatingPoint, or alternatively just implement everything twice without generics.

Thanks!