Last active
August 16, 2021 15:06
-
-
Save bkietz/8e2ef182883b886e532ffde8e537f7a3 to your computer and use it in GitHub Desktop.
A simple function for configuring and disambiguating the ordering of floating point numbers
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <cassert> | |
#include <cmath> | |
#include <cstddef> | |
#include <cstdint> | |
#include <iostream> | |
#include <limits> | |
#include <optional> | |
#include <vector> | |
enum NanOrdering { | |
kValuesNansNulls, | |
kNansValuesNulls, | |
kValuesNullsNans, | |
kNansNullsValues, | |
kNullsValuesNans, | |
kNullsNansValues, | |
}; | |
int32_t float_ord(std::optional<float> f, | |
bool negative_zero_equals_positive_zero = true, | |
NanOrdering nan_ordering = kValuesNansNulls) { | |
if (!f) { | |
switch (nan_ordering) { | |
case kValuesNansNulls: | |
case kNansValuesNulls: | |
return std::numeric_limits<int32_t>::max(); | |
case kValuesNullsNans: | |
return std::numeric_limits<int32_t>::max() - 1; | |
case kNansNullsValues: | |
return std::numeric_limits<int32_t>::min() + 1; | |
case kNullsValuesNans: | |
case kNullsNansValues: | |
return std::numeric_limits<int32_t>::min(); | |
} | |
} | |
if (std::isnan(*f)) { | |
switch (nan_ordering) { | |
case kValuesNullsNans: | |
case kNullsValuesNans: | |
return std::numeric_limits<int32_t>::max(); | |
case kValuesNansNulls: | |
return std::numeric_limits<int32_t>::max() - 1; | |
case kNullsNansValues: | |
return std::numeric_limits<int32_t>::min() + 1; | |
case kNansValuesNulls: | |
case kNansNullsValues: | |
return std::numeric_limits<int32_t>::min(); | |
} | |
} | |
if (negative_zero_equals_positive_zero) { | |
if (*f == 0.F) return 0; | |
} | |
const float abs_f = std::abs(*f); | |
const bool sign_f = std::signbit(*f); | |
return *reinterpret_cast<const int32_t*>(&abs_f) * (sign_f ? -1 : 1); | |
} | |
using Float = std::numeric_limits<float>; | |
const std::vector<std::optional<float>> kAllFloats{ | |
// negative infinity | |
-Float::infinity(), | |
// extremely negative numbers | |
Float::lowest(), | |
std::nextafter(Float::lowest(), Float::infinity()), | |
// negative numbers | |
-1e10, | |
-2.0F, | |
-1.0F, | |
-0.5F, | |
// negative numbers close to denormal boundary | |
std::nextafter(-Float::min(), -Float::infinity()), | |
-Float::min(), | |
// negative denormal numbers | |
std::nextafter(-Float::min(), Float::infinity()), | |
-Float::denorm_min() * 2, | |
-Float::denorm_min(), | |
// zeroes | |
-0.F, | |
0.F, | |
// positive denormal numbers | |
Float::denorm_min(), | |
Float::denorm_min() * 2, | |
std::nextafter(Float::min(), -Float::infinity()), | |
// positive numbers close to denormal boundary | |
Float::min(), | |
std::nextafter(Float::min(), Float::infinity()), | |
// positive numbers | |
0.5F, | |
1.0F, | |
2.0F, | |
1e10, | |
// large numbers | |
std::nextafter(Float::max(), -Float::infinity()), | |
Float::max(), | |
// infinity | |
Float::infinity(), | |
// non-numbers | |
Float::quiet_NaN(), | |
Float::signaling_NaN(), | |
std::nullopt, | |
}; | |
int main() { | |
for (size_t i = 0; i < kAllFloats.size() - 1; ++i) { | |
auto f0 = kAllFloats[i]; | |
auto f1 = kAllFloats[i + 1]; | |
if (!f0 || !f1) continue; | |
if (std::isnan(*f0) || std::isnan(*f1)) continue; | |
if (*f0 == 0 && *f1 == 0) continue; | |
assert(*f0 < *f1); | |
} | |
for (auto f0 : kAllFloats) { | |
for (auto f1 : kAllFloats) { | |
if (!f0) { | |
if (!f1) { | |
assert(float_ord(f0) == float_ord(f1)); | |
} else { | |
assert(float_ord(f0) > float_ord(f1)); | |
} | |
continue; | |
} | |
if (!f1) continue; | |
if (std::isnan(*f0)) { | |
// by default, NaN is ordered greater even than infinity | |
if (std::isnan(*f1)) { | |
assert(float_ord(f0) == float_ord(f1)); | |
} else { | |
assert(float_ord(f0) > float_ord(f1)); | |
} | |
} | |
// ordering is preserved: | |
if (*f0 < *f1) assert(float_ord(f0) < float_ord(f1)); | |
if (*f0 > *f1) assert(float_ord(f0) > float_ord(f1)); | |
if (*f0 == *f1) assert(float_ord(f0) == float_ord(f1)); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment