Приведенный ниже код работает с целочисленными типами данных не более 64 бит и типами данных с плавающей запятой не более чем с точностью до ieee-754. Для более широких типов данных можно использовать ту же идею, но вам придется адаптировать код. Поскольку я не очень знаком с C ++, код написан на C. Не должно быть слишком сложно преобразовать его в код в стиле C ++. Код не имеет ответвлений, что может повысить производительность.
#include <stdio.h>
// gcc -O3 -march=haswell cmp.c
// Assume long long int is 64 bits.
// Assume ieee-754 double precision.
int long_long_less_than_double(long long int i, double y) {
long long i_lo = i & 0x00000000FFFFFFFF; // Extract lower 32 bits.
long long i_hi = i & 0xFFFFFFFF00000000; // Extract upper 32 bits.
double x_lo = (double)i_lo; // Exact conversion to double, no rounding errors!
double x_hi = (double)i_hi; //
return ( x_lo < (y - x_hi) ); // If i is close to y then y - x_hi is exact,
// due to Sterbenz' lemma.
// i < y
// i_lo +i_hi < y
// i_lo < (y - i_hi)
// x_lo < (y - x_hi)
}
int long_long_equals_double(long long int i, double y) {
long long i_lo = i & 0x00000000FFFFFFFF;
long long i_hi = i & 0xFFFFFFFF00000000;
double x_lo = (double)i_lo;
double x_hi = (double)i_hi;
return ( x_lo == (y - x_hi) );
}
int main()
{
long long a0 = 999999984306749439;
long long a1 = 999999984306749440; // Hex number: 0x0DE0B6B000000000
long long a2 = 999999984306749441;
float b = 999999984306749440.f; // This number can be represented exactly by a `float`.
printf("%lli less_than %20.1f = %i\n", a0, b, long_long_less_than_double(a0, b)); // Implicit conversion from float to double
printf("%lli less_than %20.1f = %i\n", a1, b, long_long_less_than_double(a1, b));
printf("%lli equals %20.1f = %i\n", a0, b, long_long_equals_double(a0, b));
printf("%lli equals %20.1f = %i\n", a1, b, long_long_equals_double(a1, b));
printf("%lli equals %20.1f = %i\n\n", a2, b, long_long_equals_double(a2, b));
long long c0 = 1311693406324658687;
long long c1 = 1311693406324658688; // Hex number: 0x1234123412341200
long long c2 = 1311693406324658689;
double d = 1311693406324658688.0; // This number can be represented exactly by a `double`.
printf("%lli less_than %20.1f = %i\n", c0, d, long_long_less_than_double(c0, d));
printf("%lli less_than %20.1f = %i\n", c1, d, long_long_less_than_double(c1, d));
printf("%lli equals %20.1f = %i\n", c0, d, long_long_equals_double(c0, d));
printf("%lli equals %20.1f = %i\n", c1, d, long_long_equals_double(c1, d));
printf("%lli equals %20.1f = %i\n", c2, d, long_long_equals_double(c2, d));
return 0;
}
Идея состоит в том, чтобы разбить 64-битное целое число i
на 32 старших бита i_hi
и 32 младших бита i_lo
, которые преобразуются в двойные числа x_hi
и x_lo
без каких-либоошибки округления. Если double y
близко к x_hi
, то вычитание с плавающей запятой y - x_hi
является точным из-за леммы Sterbenz . Таким образом, вместо x_lo + x_hi < y
мы можем проверить x_lo < (y - x_hi)
, что более точно! Если double y
не близко к x_hi
, то y - x_hi
является неточным, но в этом случае нам не нужна точность, потому что тогда |y - x_hi|
намного больше, чем |x_lo|
. Другими словами: если i
и y
сильно отличаются, нам не нужно беспокоиться о значении младших 32 бит.
Вывод:
999999984306749439 less_than 999999984306749440.0 = 1
999999984306749440 less_than 999999984306749440.0 = 0
999999984306749439 equals 999999984306749440.0 = 0
999999984306749440 equals 999999984306749440.0 = 1
999999984306749441 equals 999999984306749440.0 = 0
1311693406324658687 less_than 1311693406324658688.0 = 1
1311693406324658688 less_than 1311693406324658688.0 = 0
1311693406324658687 equals 1311693406324658688.0 = 0
1311693406324658688 equals 1311693406324658688.0 = 1
1311693406324658689 equals 1311693406324658688.0 = 0