inline unsigned char FTOC_ARM(float a)
{
const float v = a < 0.0f ? 0.0f : (a > 1.0f ? 1.0f : a);
return static_cast<unsigned char>(v * 255.0f);
}
inline unsigned char FTOC(float a)
{
//This value is 2^52 * 1.5.
const double INT_MANTISSA = 6755399441055744.0;
/* Be sure to truncate (not round) positive values. The highest value that
* should be converted to 1 is roughly(1 / 256 - 0.00001); if we don't
* truncate, values up to (1/256 + 0.5) will be converted to 1, which is
* wrong. */
double base = double(a * 256.f - 0.5f);
/* INT_MANTISSA is chosen such that, when added to a sufficiently small
* double, the mantissa bits of that double can be reinterpreted as that
* number rounded to an integer. This is done to improve performance. */
base += INT_MANTISSA;
int ret = reinterpret_cast<int&>(base);
/* Benchmarking shows that clamping here, as integers, is much faster than clamping
* before the conversion, as floats. */
if (ret < 0) {
return 0;
}
if (ret > 255) {
return 255;
}
return static_cast<unsigned char>(ret);
}
inline unsigned char FTOC_union(float a)
{
//This value is 2^52 * 1.5.
const double INT_MANTISSA = 6755399441055744.0;
/* Be sure to truncate (not round) positive values. The highest value that
* should be converted to 1 is roughly(1 / 256 - 0.00001); if we don't
* truncate, values up to (1/256 + 0.5) will be converted to 1, which is
* wrong. */
double base = double(a * 256.f - 0.5f);
/* INT_MANTISSA is chosen such that, when added to a sufficiently small
* double, the mantissa bits of that double can be reinterpreted as that
* number rounded to an integer. This is done to improve performance. */
base += INT_MANTISSA;
// union doesn't break strict aliasing rule in contrary to reinterpret cast
union {
double d;
int i;
} u;
u.d = base;
int ret = u.i;
/* Benchmarking shows that clamping here, as integers, is much faster than clamping
* before the conversion, as floats. */
if (ret < 0) {
return 0;
}
if (ret > 255) {
return 255;
}
return static_cast<unsigned char>(ret);
}
#include <algorithm>
unsigned char FTOC_clamp(float a)
{
int value = static_cast<int>(a * 256.0f);
return static_cast<unsigned char>(std::clamp(value, 0, 255));
}
#include <cstdio>
#include <cstring>
void test(const char* fname, unsigned char (*f)(float)){
int cnts[1000];
std::memset(cnts, 0, sizeof(cnts));
printf("%s\ncnts: ", fname);
for( float n = 0; n <= 1.0; n += 0.0001 ){
cnts[f(n)]++;
};
for( int i = 0; i < 256; ++i ) std::printf("%i ", cnts[i]);
printf("\n");
// orig comment says:
/* Be sure to truncate (not round) positive values. The highest value that
* should be converted to 1 is roughly(1 / 256 - 0.00001); if we don't
* truncate, values up to (1/256 + 0.5) will be converted to 1, which is
* wrong. */
// but does it make any sense? 1/256 ~= 0.00390625, subtracting 0.00001 would make it <0 but later we just clamp anyway
float checkpoints[] = {-1.0f, -0.5f, 0.0f, 0.00001f, 0.0001f, 0.0002f, 1.0f / 256.0f - 0.00001f, 1.0f / 256.0f, 1.0f / 256.0f + 0.00001f, 1.0f / 256.0f + 0.6f, 0.5f, 0.9f, 1.0f-1.0f/256.0f, 1.0f};
for( float n: checkpoints ) printf("%f: %u, ", n, f(n));
printf("\n");
}
int main()
{
test("FTOC", FTOC);
test("FTOC ARM", FTOC_ARM);
test("FTOC union", FTOC_union);
test("FTOC clamp", FTOC_clamp);
}