Skip to content

Instantly share code, notes, and snippets.

@bkietz
Last active August 16, 2021 15:06
Show Gist options
  • Save bkietz/8e2ef182883b886e532ffde8e537f7a3 to your computer and use it in GitHub Desktop.
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
#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