que punto programacion precisión numero numericos metodos informatica flotante doble coma binario c++ floating-point type-conversion c++03 integral

punto - Número entero más cercano al valor de coma flotante en C++ 03



punto flotante plc (1)

Tengo una solución práctica para tipos de coma flotante radix-2 (binarios) y tipos enteros de hasta 64 bits y más largos. Vea abajo. Los comentarios deben ser claros. La salida sigue.

// file: f2i.cpp // // compiled with MinGW x86 (gcc version 4.6.2) as: // g++ -Wall -O2 -std=c++03 f2i.cpp -o f2i.exe #include <iostream> #include <iomanip> #include <limits> using namespace std; template<class I, class F> I truncAndCap(F f) { /* This function converts (by truncating the fractional part) the floating-point value f (of type F) into an integer value (of type I), avoiding undefined behavior by returning std::numeric_limits<I>::min() and std::numeric_limits<I>::max() when f is too small or too big to be converted to type I directly. 2 problems: - F may fail to convert to I, which is undefined behavior and we want to avoid that. - I may not convert exactly into F - Direct I & F comparison fails because of I to F promotion, which can be inexact. This solution is for the most practical case when I and F are radix-2 (binary) integer and floating-point types. */ int Idigits = numeric_limits<I>::digits; int Isigned = numeric_limits<I>::is_signed; /* Calculate cutOffMax = 2 ^ std::numeric_limits<I>::digits (where ^ denotes exponentiation) as a value of type F. We assume that F is a radix-2 (binary) floating-point type AND it has a big enough exponent part to hold the value of std::numeric_limits<I>::digits. FLT_MAX_10_EXP/DBL_MAX_10_EXP/LDBL_MAX_10_EXP >= 37 (guaranteed per C++ standard from 2003/C standard from 1999) corresponds to log2(1e37) ~= 122, so the type I can contain up to 122 bits. In practice, integers longer than 64 bits are extremely rare (if existent at all), especially on old systems of the 2003 C++ standard''s time. */ const F cutOffMax = F(I(1) << Idigits / 2) * F(I(1) << (Idigits / 2 + Idigits % 2)); if (f >= cutOffMax) return numeric_limits<I>::max(); /* Calculate cutOffMin = - 2 ^ std::numeric_limits<I>::digits (where ^ denotes exponentiation) as a value of type F for signed I''s OR cutOffMin = 0 for unsigned I''s in a similar fashion. */ const F cutOffMin = Isigned ? -F(I(1) << Idigits / 2) * F(I(1) << (Idigits / 2 + Idigits % 2)) : 0; if (f <= cutOffMin) return numeric_limits<I>::min(); /* Mathematically, we may still have a little problem (2 cases): cutOffMin < f < std::numeric_limits<I>::min() srd::numeric_limits<I>::max() < f < cutOffMax These cases are only possible when f isn''t a whole number, when it''s either std::numeric_limits<I>::min() - value in the range (0,1) or std::numeric_limits<I>::max() + value in the range (0,1). We can ignore this altogether because converting f to type I is guaranteed to truncate the fractional part off, and therefore I(f) will always be in the range [std::numeric_limits<I>::min(), std::numeric_limits<I>::max()]. */ return I(f); } template<class I, class F> void test(const char* msg, F f) { I i = truncAndCap<I,F>(f); cout << msg << setiosflags(ios_base::showpos) << setw(14) << setprecision(12) << f << " -> " << i << resetiosflags(ios_base::showpos) << endl; } #define TEST(I,F,VAL) / test<I,F>(#F " -> " #I ": ", VAL); int main() { TEST(short, float, -1.75f); TEST(short, float, -1.25f); TEST(short, float, +0.00f); TEST(short, float, +1.25f); TEST(short, float, +1.75f); TEST(short, float, -32769.00f); TEST(short, float, -32768.50f); TEST(short, float, -32768.00f); TEST(short, float, -32767.75f); TEST(short, float, -32767.25f); TEST(short, float, -32767.00f); TEST(short, float, -32766.00f); TEST(short, float, +32766.00f); TEST(short, float, +32767.00f); TEST(short, float, +32767.25f); TEST(short, float, +32767.75f); TEST(short, float, +32768.00f); TEST(short, float, +32768.50f); TEST(short, float, +32769.00f); TEST(int, float, -2147483904.00f); TEST(int, float, -2147483648.00f); TEST(int, float, -16777218.00f); TEST(int, float, -16777216.00f); TEST(int, float, -16777215.00f); TEST(int, float, +16777215.00f); TEST(int, float, +16777216.00f); TEST(int, float, +16777218.00f); TEST(int, float, +2147483648.00f); TEST(int, float, +2147483904.00f); TEST(int, double, -2147483649.00); TEST(int, double, -2147483648.00); TEST(int, double, -2147483647.75); TEST(int, double, -2147483647.25); TEST(int, double, -2147483647.00); TEST(int, double, +2147483647.00); TEST(int, double, +2147483647.25); TEST(int, double, +2147483647.75); TEST(int, double, +2147483648.00); TEST(int, double, +2147483649.00); TEST(unsigned, double, -1.00); TEST(unsigned, double, +1.00); TEST(unsigned, double, +4294967295.00); TEST(unsigned, double, +4294967295.25); TEST(unsigned, double, +4294967295.75); TEST(unsigned, double, +4294967296.00); TEST(unsigned, double, +4294967297.00); return 0; }

Salida ( Idone imprime igual que mi PC):

float -> short: -1.75 -> -1 float -> short: -1.25 -> -1 float -> short: +0 -> +0 float -> short: +1.25 -> +1 float -> short: +1.75 -> +1 float -> short: -32769 -> -32768 float -> short: -32768.5 -> -32768 float -> short: -32768 -> -32768 float -> short: -32767.75 -> -32767 float -> short: -32767.25 -> -32767 float -> short: -32767 -> -32767 float -> short: -32766 -> -32766 float -> short: +32766 -> +32766 float -> short: +32767 -> +32767 float -> short: +32767.25 -> +32767 float -> short: +32767.75 -> +32767 float -> short: +32768 -> +32767 float -> short: +32768.5 -> +32767 float -> short: +32769 -> +32767 float -> int: -2147483904 -> -2147483648 float -> int: -2147483648 -> -2147483648 float -> int: -16777218 -> -16777218 float -> int: -16777216 -> -16777216 float -> int: -16777215 -> -16777215 float -> int: +16777215 -> +16777215 float -> int: +16777216 -> +16777216 float -> int: +16777218 -> +16777218 float -> int: +2147483648 -> +2147483647 float -> int: +2147483904 -> +2147483647 double -> int: -2147483649 -> -2147483648 double -> int: -2147483648 -> -2147483648 double -> int: -2147483647.75 -> -2147483647 double -> int: -2147483647.25 -> -2147483647 double -> int: -2147483647 -> -2147483647 double -> int: +2147483647 -> +2147483647 double -> int: +2147483647.25 -> +2147483647 double -> int: +2147483647.75 -> +2147483647 double -> int: +2147483648 -> +2147483647 double -> int: +2147483649 -> +2147483647 double -> unsigned: -1 -> 0 double -> unsigned: +1 -> 1 double -> unsigned: +4294967295 -> 4294967295 double -> unsigned: +4294967295.25 -> 4294967295 double -> unsigned: +4294967295.75 -> 4294967295 double -> unsigned: +4294967296 -> 4294967295 double -> unsigned: +4294967297 -> 4294967295

Para algún tipo de entero, ¿cómo puedo encontrar el valor que está más cerca de algún valor de un tipo de punto flotante, incluso cuando el valor del punto flotante está muy por fuera del rango representable del número entero?

O más precisamente:

Sea F un tipo de coma flotante (probablemente float , double o long double ). Deje I sea ​​un tipo entero.

Supongamos que tanto F como I tienen especializaciones válidas de std::numeric_limits<> .

Dado un valor representable de F , y usando solo C ++ 03, ¿cómo puedo encontrar el valor representable más cercano de I ?

Persigo en una solución pura, eficiente y segura para subprocesos, y una que no asume nada sobre la plataforma excepto lo que está garantizado por C ++ 03.

Si tal solución no existe, ¿es posible encontrar una utilizando las nuevas características de C99 / C ++ 11?

El uso de lround() de C99 parece ser problemático debido a la forma no trivial en que se informan los errores de dominio. ¿Pueden estos errores de dominio ser capturados de una manera portátil y segura para hilos?

Nota: soy consciente de que Boost probablemente ofrece una solución a través de su plantilla boost::numerics::converter<> , pero debido a su gran complejidad y verbosidad, y no he podido extraer lo esencial de ella, y por lo tanto tengo no ha podido verificar si su solución realiza suposiciones más allá de C ++ 03.

El siguiente enfoque ingenuo falla debido al hecho de que el resultado de I(f) no está definido por C ++ 03 cuando la parte integral de f no es un valor representable de I

template<class I, class F> I closest_int(F f) { return I(f); }

Considere entonces el siguiente enfoque:

template<class I, class F> I closest_int(F f) { if (f < std::numeric_limits<I>::min()) return std::numeric_limits<I>::min(); if (std::numeric_limits<I>::max() < f) return std::numeric_limits<I>::max(); return I(f); }

Esto también falla porque las partes integrales de F(std::numeric_limits<I>::min()) y F(std::numeric_limits<I>::max()) aún no se pueden representar en I

Finalmente considere este tercer enfoque que también falla:

template<class I, class F> I closest_int(F f) { if (f <= std::numeric_limits<I>::min()) return std::numeric_limits<I>::min(); if (std::numeric_limits<I>::max() <= f) return std::numeric_limits<I>::max(); return I(f); }

Esta vez, I(f) siempre tendrá un resultado bien definido, sin embargo, dado que F(std::numeric_limits<I>::max()) puede ser mucho más pequeño que std::numeric_limits<I>::max() , es posible que std::numeric_limits<I>::max() para un valor de punto flotante que sea valores enteros múltiples debajo de std::numeric_limits<I>::max() .

Tenga en cuenta que todos los problemas surgen porque no está definido si la conversión F(i) redondea hacia arriba o hacia abajo hasta el valor de punto flotante representable más cercano.

Aquí está la sección relevante de C ++ 03 (4.9 conversiones flotantes-integrales):

Un valor de un tipo entero o de un tipo de enumeración se puede convertir en un valor r de un tipo de coma flotante. El resultado es exacto si es posible. De lo contrario, es una elección definida por la implementación del próximo valor representable más bajo o más alto.