Porting half<>float conversion to C# from C++ is not that precise. Let’s look at the code:
//https://stackoverflow.com/questions/1659440/32-bit-to-16-bit-floating-point-conversion/60047308#60047308
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe ushort FloatToHalf(float x) { // IEEE-754 16-bit floating-point format (without infinity): 1-5-10, exp-15, +-131008.0, +-6.1035156E-5, +-5.9604645E-8, 3.311 digits
var uintx = *(uint*)&x;
uint b = uintx+0x00001000; // round-to-nearest-even: add last bit after truncated mantissa
uint e = (b&0x7F800000)>>23; // exponent
uint m = b&0x007FFFFF; // mantissa; in line below: 0x007FF000 = 0x00800000-0x00001000 = decimal indicator flag - initial rounding
var v1 = (e > 112) ? 1 : 0;
var v2 = (e > 143) ? 1 : 0;
var v3 = (e < 113u) ? 1 : 0;
var v4 = (e > 101u) ? 1 : 0;
return (ushort)((b&0x80000000u)>>16 | v1*((((e-112u)<<10) & 0x7C00u)|m>>13) | (v3&v4)*((((0x007FF000u+m)>> (int)(125-e))+1)>>1) | v2*0x7FFFu); // sign : normalized : denormalized : saturate
}
There are multiple bool -> uint conversions. They’re done via (bool) ? 1 : 0
, normalized bool. This construction producing compare and jump, right?
Even release configuration couldn’t help optimizing that! Let’s test what would we get in C++
With -O0 we would get same construction as in C#, but even with O1 compiler is smart enough to understand that we just need to return cmp instruction result.
Note that bool size IN STORAGE (as field of class/struct or array element) can vary in .NET, so it’s needed to convert to int. Internally it could be 1-byte, 4-byte or even more for alignment purposes, it’s not strictly defined. But the bool itself takes 1-byte.
In C# we can’t just make (int)bool
or (byte)bool
unfortunately. So our variants are:
- Convert via branch, e.g (bool) ? 1 : 0 (TestByDefault)
- Convert via pointer cast
*(byte*)&bool
(TestByUnsafe) [not always safe!] - Convert via Explicit layout struct (haven’t measured that)
- Reinterpret bool array to byte [only applicable for pinned arrays] (TestByArrayPointer) [not always safe!]
- Use IL (TestByLib)
Let’s measure different approaches, here are results (for 1024*1024 bool[] filled with 50% randomness bools):
As we can see here, IL conversion is only slightly slower than plain array copy.
As in previous article for C# pointers we will just use IL modification to build it into DLL https://meetemq.com/2023/01/14/using-pointers-in-c-unity/#solution
I’ve used this code for function uint BoolToUint32(bool v)
ldarg.0
conv.u4
ret
For signed integer you can use conv.i4.
DnSpy or other decompiler will show this function as it would be with branch, but not be fooled, it’s incorrect. Correct interpretation would be return (uint)v
but that’s invalid in C# of course.
After adding this function to your DLL, you can use it in your code and hope compiler will inline it.
Then we can go further (and safier) and write compare functions returning int directly, but that’s for the next times.
Cheers!