float - setprecision c++ español
Impresión doble sin perder precisión (7)
¿Cómo se imprime un doble en una secuencia para que cuando se lee no pierda precisión?
Lo intenté:
std::stringstream ss;
double v = 0.1 * 0.1;
ss << std::setprecision(std::numeric_limits<T>::digits10) << v << " ";
double u;
ss >> u;
std::cout << "precision " << ((u == v) ? "retained" : "lost") << std::endl;
Esto no funcionó como esperaba.
Pero puedo aumentar la precisión (lo que me sorprendió ya que pensé que los dígitos10 eran el máximo requerido).
ss << std::setprecision(std::numeric_limits<T>::digits10 + 2) << v << " ";
// ^^^^^^ +2
Tiene que ver con la cantidad de dígitos significativos y los primeros dos no cuentan (0.01).
Entonces, ¿alguien ha visto representar los números de coma flotante exactamente? ¿Cuál es el encantamiento mágico exacto en la transmisión que necesito hacer?
Después de algunos experimentos:
El problema fue con mi versión original. Hubo dígitos no significativos en la cadena después del punto decimal que afectó la precisión.
Para compensar esto, podemos usar la notación científica para compensar:
ss << std::scientific
<< std::setprecision(std::numeric_limits<double>::digits10 + 1)
<< v;
Esto todavía no explica la necesidad del +1 sin embargo.
Además, si imprimo el número con más precisión, ¡obtendré más precisión impresa!
std::cout << std::scientific << std::setprecision(std::numeric_limits<double>::digits10) << v << "/n";
std::cout << std::scientific << std::setprecision(std::numeric_limits<double>::digits10 + 1) << v << "/n";
std::cout << std::scientific << std::setprecision(std::numeric_limits<double>::digits) << v << "/n";
En resultado de:
1.000000000000000e-02
1.0000000000000002e-02
1.00000000000000019428902930940239457413554200000000000e-02
Según la respuesta de @Stephen Canon a continuación:
Podemos imprimir exactamente utilizando el formateador printf (), "% a" o "% A". Para lograr esto en C ++ necesitamos usar los manipuladores fijos y científicos (ver n3225: 22.4.2.2.2p5 Tabla 88)
std::cout.flags(std::ios_base::fixed | std::ios_base::scientific);
std::cout << v;
Por ahora he definido:
template<typename T>
std::ostream& precise(std::ostream& stream)
{
std::cout.flags(std::ios_base::fixed | std::ios_base::scientific);
return stream;
}
std::ostream& preciselngd(std::ostream& stream){ return precise<long double>(stream);}
std::ostream& precisedbl(std::ostream& stream) { return precise<double>(stream);}
std::ostream& preciseflt(std::ostream& stream) { return precise<float>(stream);}
Siguiente: ¿Cómo manejamos NaN / Inf?
@ThomasMcLeod: Creo que la regla de dígitos significativos proviene de mi campo, la física, y significa algo más sutil:
Si tiene una medida que le da el valor 1.52 y no puede leer más detalles de la escala, y dice que se supone que debe agregar otro número (por ejemplo, de otra medición porque la escala de esta era demasiado pequeña), digamos 2 , entonces el resultado (obviamente) tiene solo 2 decimales, es decir, 3.52. Pero del mismo modo, si agrega 1.1111111111 al valor 1.52, obtendrá el valor 2.63 (¡y nada más!).
El motivo de la regla es evitar que se engañe y piense que obtuvo más información de la calculada que la medida (lo cual es imposible, pero parecería así llenándolo de basura, consulte más arriba).
Dicho esto, esta regla específica es solo para suma (para la suma: el error del resultado es la suma de los dos errores, por lo que si mides solo uno mal, aunque suene, ahí va tu precisión ...).
Cómo obtener las otras reglas: Digamos que a es el número medido y δ a es el error. Digamos que tu fórmula original fue: f: = ma Digamos que también mides m con el error δm (deja que sea el lado positivo). Entonces el límite real es: f_up = (m + δm) (a + δa) y f_down = (m-δm) (a-δa) Entonces, f_up = m a + δm δa + (δm a + m δa) f_down = m a + δm δa- (δm a + m δa) Por lo tanto, ahora los dígitos significativos son aún menores: f_up ~ m a + (δm a + m δa) f_down ~ m a- (δm a + m δa) y así δf = δm a + m δa Si observa el error relativo, obtiene: δf / f = δm / m + δa / a
Para la división es δf / f = δm / m-δa / a
Espero que entienda la esencia y espero no haber cometido demasiados errores, es tarde aquí :-)
tl, dr: Los dígitos significativos significan cuántos de los dígitos en la salida en realidad provienen de los dígitos en su entrada (en el mundo real, no en la imagen distorsionada que tienen los números en coma flotante). Si sus medidas fueron 1 con error "no" y 3 con error "no" y se supone que la función es 1/3, entonces sí, todos los dígitos infinitos son dígitos significativos reales. De lo contrario, la operación inversa no funcionaría, así que obviamente tienen que serlo.
Si la regla de dígitos significa algo completamente diferente en otro campo, continúe :-)
La manera más fácil (para doble IEEE 754) de garantizar una conversión de ida y vuelta es usar siempre 17 dígitos significativos. Pero eso tiene la desventaja de incluir a veces dígitos de ruido innecesarios (0.1 → "0.10000000000000001").
Una aproximación que me ha funcionado es hacer un sprintf
del número con 15 dígitos de precisión, luego verificar si atof
le devuelve el valor original. Si no es así, prueba con 16 dígitos. Si eso no funciona, usa 17.
Es posible que desee probar el algoritmo de David Gay (utilizado en Python 3.1 para implementar float.__repr__
).
Me interesé en esta pregunta porque estoy tratando de (de) serializar mis datos a y desde JSON.
Creo que tengo una explicación más clara (con menos renuncia manual) de por qué 17 dígitos decimales son suficientes para reconstruir el número original sin pérdida:
Imagina 3 líneas numéricas:
1. para el número original de la base 2
2. para la representación de base redondeada 10
3. para el número reconstruido (igual que el # 1 porque ambos en la base 2)
Cuando se convierte a la base 10, gráficamente, se elige el tic en la línea del segundo número más cercano al tic en el 1er. Del mismo modo cuando reconstruye el original del valor base 10 redondeado.
La observación crítica que tuve fue que para permitir una reconstrucción exacta, el tamaño de la base de 10 pasos (cuántica) tiene que ser <la base 2 cuántica. De lo contrario, inevitablemente obtendrá la mala reconstrucción que se muestra en rojo.
Tome el caso específico de cuando el exponente es 0 para la representación base2. Entonces el quantum base2 será 2 ^ -52 ~ = 2.22 * 10 ^ -16. El quantum 10 más cercano que es menor que esto es 10 ^ -16. Ahora que conocemos el quantum requerido de la base 10, ¿cuántos dígitos se necesitarán para codificar todos los valores posibles? Dado que solo estamos considerando el caso de exponente = 0, el rango dinámico de valores que necesitamos representar es [1.0, 2.0). Por lo tanto, se necesitarían 17 dígitos (16 dígitos para la fracción y 1 dígito para la parte entera).
Para exponentes que no sean 0, podemos usar la misma lógica:
exponent base2 quant. base10 quant. dynamic range digits needed --------------------------------------------------------------------- 1 2^-51 10^-16 [2, 4) 17 2 2^-50 10^-16 [4, 8) 17 3 2^-49 10^-15 [8, 16) 17 ... 32 2^-20 10^-7 [2^32, 2^33) 17 1022 9.98e291 1.0e291 [4.49e307,8.99e307) 17
Si bien no es exhaustivo, la tabla muestra la tendencia de que 17 dígitos son suficientes.
Espero que te guste mi explicación.
No es correcto decir que "el punto flotante es inexacto", aunque admito que es una simplificación útil. Si usáramos la base 8 o 16 en la vida real, entonces la gente de aquí diría que "los paquetes de fracción decimal de base 10 son inexactos, ¿por qué alguien cocinó todo eso?".
El problema es que los valores integrales se traducen exactamente de una base a otra, pero los valores fraccionarios no, porque representan fracciones del paso integral y solo se usan unos pocos.
La aritmética de punto flotante es técnicamente perfectamente precisa. Cada cálculo tiene un único resultado posible. Hay un problema, y es que la mayoría de las fracciones decimales tienen representaciones de base 2 que se repiten. De hecho, en la secuencia 0.01, 0.02, ... 0.99, solo 3 meros valores tienen representaciones binarias exactas. (0.25, 0.50 y 0.75.) Hay 96 valores que se repiten y, por lo tanto, obviamente no están representados exactamente.
Ahora, hay varias formas de escribir y leer números de coma flotante sin perder un solo bit. La idea es evitar tratar de expresar el número binario con una fracción de base 10.
- Escríbelos como binarios. Hoy en día, todo el mundo implementa el formato IEEE-754, por lo que siempre que elija un orden de bytes y escriba o lea solo ese orden de bytes, los números serán transferibles.
- Escríbalos como valores enteros de 64 bits. Aquí puede usar la base habitual 10. (Porque representa el entero alias de 64 bits, no la fracción de 52 bits).
También puede simplemente escribir más dígitos de fracción decimal. Si esto es preciso bit por bit dependerá de la calidad de las bibliotecas de conversión y no estoy seguro de contar aquí con la precisión perfecta ( del software ). Pero cualquier error será excesivamente pequeño y sus datos originales ciertamente no tienen información en los bits más bajos. (Ninguna de las constantes de la física y la química se conocen por 52 bits, ni se ha medido ninguna distancia en la Tierra con 52 bits de precisión.) Pero para una copia de seguridad o restauración donde la precisión bit a bit se puede comparar automáticamente, obviamente no es ideal
No imprima valores de punto flotante en decimal si no quiere perder precisión. Incluso si imprime suficientes dígitos para representar exactamente el número, no todas las implementaciones tienen conversiones redondeadas correctamente a / desde cadenas decimales en todo el rango de coma flotante, por lo que aún puede perder precisión.
Use punto flotante hexadecimal en su lugar. Cª:
printf("%a/n", yourNumber);
C ++ 0x proporciona el manipulador hexfloat
para iostreams que hace lo mismo (en algunas plataformas, usar el modificador std::hex
tiene el mismo resultado, pero esto no es una suposición portátil).
Se prefiere usar punto flotante hex por varias razones.
Primero, el valor impreso es siempre exacto. No se produce redondeo al escribir o leer un valor formateado de esta manera. Más allá de los beneficios de precisión, esto significa que leer y escribir dichos valores puede ser más rápido con una biblioteca de E / S bien ajustada. También requieren menos dígitos para representar valores exactamente.
Un doble tiene la precisión de 52 dígitos binarios o 15.95 dígitos decimales. Ver http://en.wikipedia.org/wiki/IEEE_754-2008 . Necesita al menos 16 dígitos decimales para registrar la precisión completa de un doble en todos los casos. [Pero vea cuarta edición, abajo].
Por cierto, esto significa dígitos significativos.
Responder a las ediciones de OP:
Su punto flotante a tiempo de ejecución de serie decimal está generando más dígitos que significativos. Un doble solo puede contener 52 bits de significado (en realidad, 53, si cuenta un 1 "oculto" 1 que no está almacenado). Eso significa que la resolución no es más de 2 ^ -53 = 1.11e-16.
Por ejemplo: 1 + 2 ^ -52 = 1.0000000000000002220446049250313. . . .
Esos dígitos decimales, .0000000000000002220446049250313. . . . son el "paso" binario más pequeño en un doble cuando se convierten a decimal.
El "paso" dentro del doble es:
.0000000000000000000000000000000000000000000000000001 en binario.
Tenga en cuenta que el paso binario es exacto, mientras que el paso decimal es inexacto.
De ahí la representación decimal arriba,
1.0000000000000002220446049250313. . .
es una representación inexacta del número binario exacto:
1.0000000000000000000000000000000000000000000000000001.
Tercera edición:
El siguiente valor posible para un doble, que en el binario exacto es:
1.0000000000000000000000000000000000000000000000000010
convierte inexactamente en decimal a
1.0000000000000004440892098500626. . . .
Entonces, todos esos dígitos adicionales en el decimal no son realmente significativos, solo son artefactos básicos de conversión.
Cuarta Edición:
Aunque un doble almacena como máximo 16 dígitos decimales significativos, a veces se necesitan 17 dígitos decimales para representar el número . La razón tiene que ver con el corte de dígitos .
Como mencioné anteriormente, hay 52 + 1 dígitos binarios en el doble. El "+1" es una supuesta ventaja 1, y no se almacena ni es significativo. En el caso de un entero, esos 52 dígitos binarios forman un número entre 0 y 2 ^ 53 - 1. ¿Cuántos dígitos decimales son necesarios para almacenar dicho número? Bueno, log_10 (2 ^ 53 - 1) es aproximadamente 15.95. Así que a lo más 16 dígitos decimales son necesarios. Etiquetemos estos d_0 a d_15.
Ahora considere que los números de punto flotante IEEE también tienen un exponente binario . ¿Qué sucede cuando incrementamos el exponente por, digamos, 2? Hemos multiplicado nuestro número de 52 bits, sea lo que sea, por 4. Ahora, en lugar de nuestros 52 dígitos binarios alineados perfectamente con nuestros dígitos decimales d_0 a d_15, tenemos algunos dígitos binarios significativos representados en d_16. Sin embargo, dado que multiplicamos por algo menos de 10, todavía tenemos dígitos binarios significativos representados en d_0. Entonces nuestros dígitos decimales de 15.95 ahora ocupan d_1 a d_15, más algunos bits superiores de d_0 y algunos bits más bajos de d_16. Esta es la razón por la que a veces se necesitan 17 dígitos decimales para representar un doble IEEE.
Quinta edición
Corregidos errores numéricos
Gracias a ThomasMcLeod por señalar el error en el cálculo de mi tabla
Para garantizar la conversión de ida y vuelta con 15, 16 o 17 dígitos, solo es posible para unos pocos casos. El número 15.95 proviene de tomar 2 ^ 53 (1 bit implícito + 52 bits en la significante / "mantisa") que sale a un número entero en el rango 10 ^ 15 a 10 ^ 16 (más cerca de 10 ^ 16).
Considere un valor doble de precisión x con un exponente de 0, es decir, cae dentro del rango de rango de punto flotante 1.0 <= x <2.0. El bit implícito marcará el componente 2 ^ 0 (parte) de x. El bit explícito más alto del significado denotará el siguiente exponente inferior (de 0) <=> -1 => 2 ^ -1 o el componente 0.5.
El próximo bit 0.25, los que están después de 0.125, 0.0625, 0.03125, 0.015625 y así sucesivamente (ver tabla a continuación). El valor 1.5 se representará por lo tanto mediante dos componentes agregados juntos: el bit implícito que denota 1.0 y el bit de significado explícito más alto que denota 0.5.
Esto ilustra que desde el bit implícito hacia abajo tiene 52 bits adicionales y explícitos para representar componentes posibles donde el más pequeño es 0 (exponente) - 52 (bits explícitos en significando) = -52 => 2 ^ -52 que según la tabla siguiente es ... bueno, pueden ver por ustedes mismos que tiene un poco más de 15.95 dígitos significativos (37 para ser exactos). Para decirlo de otra manera, el número más pequeño en el rango 2 ^ 0 que es! = 1.0 en sí es 2 ^ 0 + 2 ^ -52 que es 1.0 + el número al lado de 2 ^ -52 (abajo) = (exactamente) 1.0000000000000002220446049250313080847263336181640625, un valor que cuento como 53 dígitos significativos de largo. Con 17 dígitos de "precisión" de formateo, el número se mostrará como 1.0000000000000002 y esto dependerá de que la biblioteca se convierta correctamente.
Entonces, tal vez "conversión de ida y vuelta en 17 dígitos" no es realmente un concepto válido (suficiente).
2^ -1 = 0.5000000000000000000000000000000000000000000000000000
2^ -2 = 0.2500000000000000000000000000000000000000000000000000
2^ -3 = 0.1250000000000000000000000000000000000000000000000000
2^ -4 = 0.0625000000000000000000000000000000000000000000000000
2^ -5 = 0.0312500000000000000000000000000000000000000000000000
2^ -6 = 0.0156250000000000000000000000000000000000000000000000
2^ -7 = 0.0078125000000000000000000000000000000000000000000000
2^ -8 = 0.0039062500000000000000000000000000000000000000000000
2^ -9 = 0.0019531250000000000000000000000000000000000000000000
2^-10 = 0.0009765625000000000000000000000000000000000000000000
2^-11 = 0.0004882812500000000000000000000000000000000000000000
2^-12 = 0.0002441406250000000000000000000000000000000000000000
2^-13 = 0.0001220703125000000000000000000000000000000000000000
2^-14 = 0.0000610351562500000000000000000000000000000000000000
2^-15 = 0.0000305175781250000000000000000000000000000000000000
2^-16 = 0.0000152587890625000000000000000000000000000000000000
2^-17 = 0.0000076293945312500000000000000000000000000000000000
2^-18 = 0.0000038146972656250000000000000000000000000000000000
2^-19 = 0.0000019073486328125000000000000000000000000000000000
2^-20 = 0.0000009536743164062500000000000000000000000000000000
2^-21 = 0.0000004768371582031250000000000000000000000000000000
2^-22 = 0.0000002384185791015625000000000000000000000000000000
2^-23 = 0.0000001192092895507812500000000000000000000000000000
2^-24 = 0.0000000596046447753906250000000000000000000000000000
2^-25 = 0.0000000298023223876953125000000000000000000000000000
2^-26 = 0.0000000149011611938476562500000000000000000000000000
2^-27 = 0.0000000074505805969238281250000000000000000000000000
2^-28 = 0.0000000037252902984619140625000000000000000000000000
2^-29 = 0.0000000018626451492309570312500000000000000000000000
2^-30 = 0.0000000009313225746154785156250000000000000000000000
2^-31 = 0.0000000004656612873077392578125000000000000000000000
2^-32 = 0.0000000002328306436538696289062500000000000000000000
2^-33 = 0.0000000001164153218269348144531250000000000000000000
2^-34 = 0.0000000000582076609134674072265625000000000000000000
2^-35 = 0.0000000000291038304567337036132812500000000000000000
2^-36 = 0.0000000000145519152283668518066406250000000000000000
2^-37 = 0.0000000000072759576141834259033203125000000000000000
2^-38 = 0.0000000000036379788070917129516601562500000000000000
2^-39 = 0.0000000000018189894035458564758300781250000000000000
2^-40 = 0.0000000000009094947017729282379150390625000000000000
2^-41 = 0.0000000000004547473508864641189575195312500000000000
2^-42 = 0.0000000000002273736754432320594787597656250000000000
2^-43 = 0.0000000000001136868377216160297393798828125000000000
2^-44 = 0.0000000000000568434188608080148696899414062500000000
2^-45 = 0.0000000000000284217094304040074348449707031250000000
2^-46 = 0.0000000000000142108547152020037174224853515625000000
2^-47 = 0.0000000000000071054273576010018587112426757812500000
2^-48 = 0.0000000000000035527136788005009293556213378906250000
2^-49 = 0.0000000000000017763568394002504646778106689453125000
2^-50 = 0.0000000000000008881784197001252323389053344726562500
2^-51 = 0.0000000000000004440892098500626161694526672363281250
2^-52 = 0.0000000000000002220446049250313080847263336181640625