variable sirve que programa para informatica float ejemplos dev definicion con c++ templates c++11 floating-point template-meta-programming

c++ - sirve - que es double en informatica



Alternativas para la inicializaciĆ³n de punto flotante en tiempo de compilaciĆ³n (1)

Actualmente estoy trabajando en una implementación de aritmética de punto flotante basada en una plantilla de meta-programación. La plantilla que representa los valores flotantes de tiempo de compilación es la siguiente:

template<bool S , std::int16_t E , std::uint64_t M> struct number{};

Dado que la inicialización de dichos valores con mantisas, exponentes, etc., codificados de forma rígida es un proceso engorroso y propenso a errores, he escrito una plantilla para convertir valores decimales en valores de punto flotante:

template<std::int64_t INT , std::uint64_t DECS> struct decimal{};

Donde el primer parámetro representa la parte integral y el segundo los dígitos fraccionarios. Creo que esta es una forma común y bien conocida.
Sin embargo, este patrón tiene algunos problemas (¿Cómo introduzco números negativos menores de uno?), Donde uno de los más molestos para mí es el hecho de que no hay forma de ingresar cero dígitos justo después de la coma, es decir, números como 0.00032 .

Soy consciente de C ++ 11, y estaba pensando en un enfoque definido por el usuario + decltype() (Incluso con una macro #define FLOAT(x) decltype(x_MY_LITERAL) ) pero no estoy seguro de que ese enfoque sea posible en todos los contextos, quiero decir, si el literal + decltype es evaluable en el contexto de un parámetro de plantilla.

Incluso si eso pudiera funcionar, quiero saber si hay otros enfoques posibles para este problema. Entonces, ¿qué alternativas existen para la inicialización de punto flotante en tiempo de compilación a través de tmp?

Alternativas intentadas:

Solo para completar la información, describiré las alternativas que he implementado, cómo funcionan, y sus constantes y profesionales. La pregunta en sí permanece abierta, para permitir que alguien agregue más alternativas.

Algunos antecedentes

Primero describiré las características que he usado, solo para asegurarme de que todos comprendan el código.

Mi biblioteca, la Turbo Metaprogramming Library , se basa en tres principios:

  • Solo parámetros de plantilla de tipo : ser completamente genérico mezclar parámetros de tipo, parámetros de valor y parámetros de plantilla de plantilla es realmente difícil (casi imposible), por lo que esta biblioteca solo utiliza parámetros de tipo Siempre que sea necesario usar valores o plantillas, la biblioteca proporciona envoltorios para pasar dichos parámetros a través del boxeo.

  • Evaluación uniforme de expresiones : una de las primeras necesidades cuando se trabaja en un lenguaje de programación es una forma de evaluar expresiones y tomar su valor. Turbo proporciona la tml::eval , que toma cualquier tipo de expresión y devuelve (evalúa) su valor.

  • Algoritmos genéricos y metafunciones personalizadas a través de la especialización de plantillas : Siempre que puedo, puedo usar alias de plantillas de C ++ 11 para evitar el engorroso typename ::type construcción de typename ::type . Mi convención es definir plantillas de implementación (las metafunciones que realmente hacen el trabajo) en un espacio de nombres impl anidado, y un alias de plantilla de C ++ 11 al resultado en el espacio de nombres actual. Dado que dichos alias devuelven el resultado directamente, no son evaluables en una expresión compleja (Considere una instancia de metafunción add<X,Y> , donde X e Y son variables de un lambda. Si add fue un alias al resultado, eso no significa Funciona porque la evaluación no tiene sentido. Si necesitamos la expresión (metafunción) en lugar de su resultado directamente, mi convención fue poner un alias a la metafunción en un espacio de nombres anidado func .

Aquí hay unos ejemplos:

using bits = tml::util::sizeof_bits<int>; //bits is a size_t integral constant with the //size on bits of an int //A metafunction which returns the size on bits of a type doubled using double_size = tml::lambda<_1 , tml::mul<tml::util::func::sizeof_bits<_1>,tml::Int<2>> >; using int_double_size = tml::eval<double_size,int>; //Read as "double_size(int)"

tml es el espacio de nombres principal de la biblioteca, y las características de punto flotante están expuestas en el espacio de nombres tml::floating .

TL; DR

  • tml::eval toma cualquier expresión y la evalúa, devolviendo su valor. Es un alias de plantilla de C ++ 11, por lo que typename ::type no es necesario.

  • tml::integral_constant (Solo un alias de std::integral_constant ) es el envoltorio de valores de facto para pasar parámetros de valor como parámetros de tipo a través del boxeo. La biblioteca tiene la convención de usar solo parámetros de tipo (también hay envoltorios para los parámetros de plantilla de plantilla, vea tml::lazy y tml::bind ).

Intento 1: De entero

Aquí definimos un integer metafunción que devuelve un valor de punto flotante de un entero:

template<std::int64_t mantissa , sign_t S = (sign_t)(mantissa >= 0)> struct integer { using m = tml::floating::number<S,0,static_cast<mantissa_t>((mantissa >= 0) ? mantissa : -mantissa)>; using hsb = tml::floating::highest_set_bit<m>; static constexpr const exponent_t exp = hsb::value - 31; using result = tml::floating::number<S,exp,(m::mantissa << (31 - hsb::value))>; //Note the number is normalized };

Lo que hace es tomar el valor integral directamente, usarlo como mantisa y normalizar el número que computa explícitamente el bit de conjunto más alto (más significativo), cambiando la mantisa en consecuencia.

Un ejemplo de su uso podría ser:

using ten = tml::floating::integer<10>;

Ventajas:

  • Eficiencia : no se requieren cálculos complejos adicionales para obtener el número de punto flotante equivalente. La única operación relevante es la llamada a highest_set_bit .

  • El número está normalizado por defecto (en cuanto a la eficiencia también). Además, no hay problemas de precisión (al menos no para valores pequeños).

Desventajas:

  • Solo funciona con valores integrales.

Intento 2: Inicialización decimal

Esta alternativa utiliza un par de valores integrales para representar las partes integrales y fraccionarias del número, respectivamente:

template<std::int64_t INTEGRAL , std::uint64_t FRACTIONAL> struct decimal{ ... }; using pi = decimal<3,141592654>;

Lo que hace es calcular el valor de la parte integral (solo llamada a integer , el intento anterior) y el valor de la parte fraccional.
El valor de la parte fraccionaria es el valor del número entero ajustado hasta que el punto de la raíz se encuentra al comienzo del número. En otras palabras:

integer<fractional_part> fractional_value = ________________________________ 10^number_of_digits

Entonces el valor del número es solo la suma de ambos valores:

result = integer_part_value + fractional_value

El número de dígitos de un número integral es log10(number) + 1 . Terminé con una metafunción log10 para valores integrales que no requiere recursión:

template<typename N> struct log10 { using result = tml::Int<(0 <= N::value && N::value < 10) ? 0 : (10 <= N::value && N::value < 100) ? 1 : ... >; }

Por lo tanto, tiene O (1) complejidad (medición de la profundidad de la instantánea de la plantilla, por supuesto).

Con esta metafunción, la fórmula anterior se convierte en:

//First some aliases, to make the code more handy: using integral_i = tml::integral_constant<std::int64_t,INTEGRAL>; using integral_f = tml::floating::integer<INTEGRAL>; using fractional_f = tml::floating::integer<FRACTIONAL>; using ten = tml::floating::integer<10>; using one = tml::Int<1>; using fractional_value = tml::eval<tml::div<fractional_f , tml::pow<ten, tml::add<tml::log10<integral_i>, one > > > >

Y luego el resultado es:

using result = tml::eval<tml::add<integral_f,fractional_value>>;

Ventajas

  • Permite la 12.123 instancias de valores no integrales como 12.123 .

Desventajas:

  • Rendimiento: tml::pow es recursivo, con una complejidad de O (n). tml::div para valores de punto flotante se implementa como una multiplicación del numerador por el recíproco del denominador. Ese recíproco se calcula mediante una aproximación de Newton-Raphson (cinco iteraciones por defecto).

  • Problemas de precisión : las multiplicaciones secuenciales realizadas para calcular la potencia podrían conducir a problemas de precisión menores acumulativos. Lo mismo para la aproximación de Newton-Raphson hecha para calcular la división.

  • La notación es limitada : no hay manera de especificar números con ceros finales después del punto, por ejemplo, 13.0004 , ya que un entero entero 0004 no es válido.

Intento 3 (3.1 y 3.2): Notación científica decimal

En lugar de escribir el número usando dígitos codificados, usamos la notación científica decimal (potencia de 10) para inicializar los números de punto flotante:

using pi = tml::floating::decimal_sci<3141592654,-9>; //3141592654 x 10^-9

Para calcular el número, solo tiene que tomar el valor del significativo y multiplicarlo por la potencia correspondiente de 10:

template<std::int64_t S , std::int64_t E> struct decimal_sci { using significant = tml::floating::integer<S>; using power = tml::eval<tml::pow<tml::floating::integer<10>,tml::Int<E>>>; using result = tml::eval<tml::mul<significant,power>>; };

Hay una mejora para este intento, que trata el hecho significativo si se normalizó a un solo dígito entero. Entonces, un valor 0.0034565432 podría escribirse como (34565432 , -3) lugar de (34565432 , -11) .
Lo llamo tml::floating::decimal_scinorm :

template<std::int64_t S , std::int64_t E = 0> struct decimal_scinorm { using significant_i = tml::integral_constant<std::int64_t,S>; using exponent_i = tml::integral_constant<std::int64_t,E>; using adjust = tml::eval<tml::log10<significant_i>>; using new_exp = tml::eval<tml::sub<exponent_i,adjust>>; using result = typename decimal_sci<S,new_exp::value>::result; }; using pi = tml::floating::decimal_scinorm<3141592654>; //3.141592654 using i = tml::floating::decimal_scinorm<999999,-4>; //0.000999999

Ventajas

  • Conduce con números anchos, con ceros de encabezado incluidos, de una manera sencilla.
  • Utiliza una notación bien conocida, sin trucos sintácticos involucrados.

Desventajas

  • Baja precisión con números muy grandes / pequeños (bueno, eso se espera ya que así es como funciona la notación científica). Tenga en cuenta que los cálculos internos de punto flotante podrían llevar a errores de precisión acumulativos, proporcionales a la longitud (de la mantisa) y al exponente del número. Son los mismos errores de precisión de los intentos anteriores (por el uso de tml::pow , tml::div , etc.).

Es posible que desee utilizar literales definidos por el usuario. Según cppreference.com,

Permite que los literales de enteros, puntos flotantes, caracteres y cadenas produzcan objetos del tipo definido por el usuario mediante la definición de un sufijo definido por el usuario.

(Ver también http://en.cppreference.com/w/cpp/language/user_literal ). De esta manera, podrías hacer la expresión.

123.456_mysuffix

ceda al tipo que desee, si define el operador literal para _mysuffix. Con ese operador, puede acceder a la entrada 123.456 como un número de punto flotante (estándar de C ++) o puede hacer la conversión necesaria de la cadena en bruto como un const char * usted mismo.

EDITAR: Después de leer su pregunta editada y darme cuenta de qué tipo de meta-programación de plantillas estaba hablando, solo quería enfatizar que también se puede acceder al literal como un paquete de parámetros de parámetros de plantillas de caracteres. Es posible que pueda integrar esto en su marco de tiempo de compilación.