¿Las matemáticas de punto flotante son consistentes en C #? ¿Puede ser?

155

No, esta no es otra pregunta "¿Por qué es (1 / 3.0) * 3! = 1" .

He estado leyendo mucho sobre puntos flotantes últimamente; específicamente, cómo el mismo cálculo podría dar diferentes resultados en diferentes arquitecturas o configuraciones de optimización.

Este es un problema para los videojuegos que almacenan repeticiones o están conectados en red entre pares (a diferencia del servidor-cliente), que dependen de que todos los clientes generen exactamente los mismos resultados cada vez que ejecutan el programa, una pequeña discrepancia en uno El cálculo de punto flotante puede conducir a un estado de juego drásticamente diferente en diferentes máquinas (¡o incluso en la misma máquina! )

Esto sucede incluso entre los procesadores que "siguen" IEEE-754 , principalmente porque algunos procesadores (concretamente x86) utilizan una precisión doblemente extendida . Es decir, usan registros de 80 bits para hacer todos los cálculos, luego se truncan a 64 o 32 bits, lo que lleva a resultados de redondeo diferentes que las máquinas que usan 64 o 32 bits para los cálculos.

He visto varias soluciones a este problema en línea, pero todas para C ++, no C #:

  • Deshabilite el modo de doble precisión extendida (para que todos los doublecálculos usen IEEE-754 de 64 bits) usando _controlfp_s(Windows), _FPU_SETCW(Linux?) O fpsetprec(BSD).
  • Siempre ejecute el mismo compilador con la misma configuración de optimización y requiera que todos los usuarios tengan la misma arquitectura de CPU (sin juego multiplataforma). Debido a que mi "compilador" es en realidad el JIT, que puede optimizar de manera diferente cada vez que se ejecuta el programa , no creo que esto sea posible.
  • Utilice la aritmética de punto fijo, y evite floaty por doublecompleto. decimalfuncionaría para este propósito, pero sería mucho más lento, y ninguna de las System.Mathfunciones de la biblioteca lo admite.

Entonces, ¿ es esto incluso un problema en C #? ¿Qué sucede si solo pretendo admitir Windows (no Mono)?

Si es así, ¿hay alguna forma de obligar a mi programa a ejecutarse con doble precisión normal?

Si no, ¿hay alguna biblioteca que ayude a mantener consistentes los cálculos de punto flotante?

BlueRaja - Danny Pflughoeft
fuente
He visto esta pregunta , pero cada respuesta repite el problema sin solución o dice "ignóralo", que no es una opción. Hice una pregunta similar en gamedev , pero (debido a la audiencia) la mayoría de las respuestas parecen estar orientadas a C ++.
BlueRaja - Danny Pflughoeft
1
no una respuesta, pero estoy seguro de que en la mayoría de los dominios se podría diseñar el sistema de tal manera que todo el estado compartido es determinista, y no hay degradación significativa del rendimiento debido a que
driushkin
1
@ Peter, ¿conoces alguna emulación de coma flotante rápida para .net?
CodesInChaos
1
¿Java sufre de este problema?
Josh
3
@ Josh: Java tiene la strictfppalabra clave, que obliga a todos los cálculos a realizarse en el tamaño indicado ( floato double) en lugar de un tamaño extendido. Sin embargo, Java todavía tiene muchos problemas con el soporte IEE-754. Muy (muy, muy) pocos lenguajes de programación admiten bien IEE-754.
porges

Respuestas:

52

No conozco ninguna manera de hacer que los puntos flotantes normales sean deterministas en .net. El JITter puede crear código que se comporta de manera diferente en diferentes plataformas (o entre diferentes versiones de .net). Por lo tanto, floatno es posible usar s normales en código determinista .net.

Las soluciones que consideré:

  1. Implemente FixedPoint32 en C #. Si bien esto no es demasiado difícil (tengo una implementación a medio terminar), el rango muy pequeño de valores hace que sea molesto de usar. Debe tener cuidado en todo momento para que no se desborde ni pierda demasiada precisión. Al final encontré esto no más fácil que usar enteros directamente.
  2. Implemente FixedPoint64 en C #. Encontré esto bastante difícil de hacer. Para algunas operaciones, los enteros intermedios de 128 bits serían útiles. Pero .net no ofrece ese tipo.
  3. Implemente un punto flotante personalizado de 32 bits. La falta de un intrínseco BitScanReverse causa algunas molestias al implementar esto. Pero actualmente creo que este es el camino más prometedor.
  4. Use código nativo para las operaciones matemáticas. Incurre en los gastos generales de una llamada de delegado en cada operación matemática.

Acabo de comenzar una implementación de software de matemática de coma flotante de 32 bits. Puede hacer alrededor de 70 millones de adiciones / multiplicaciones por segundo en mi i3 de 2.66 GHz. https://github.com/CodesInChaos/SoftFloat . Obviamente todavía está muy incompleto y con errores.

CodesInChaos
fuente
2
hay un número entero "ilimitado" disponible BigInteger, aunque no es tan rápido como el int nativo o largo, por lo que .NET ofrece ese tipo (creado para F #, creo, pero se puede usar en C #)
Rune FS
Otra opción es GNU MP wrapper para .NET . Es un contenedor alrededor de The GNU Multiple Precision Library que admite enteros de precisión "infinitos", racionales (fracciones) y números de coma flotante.
Cole Johnson
2
Si va a hacer cualquiera de estos, también puede intentarlo decimalprimero, ya que es mucho más simple de hacer. Solo si es demasiado lento para la tarea en cuestión, vale la pena pensar en otros enfoques.
Roman Starkov
He aprendido sobre un caso especial donde los puntos flotantes son deterministas. La explicación que obtuve es: para la multiplicación / división, si uno de los números FP es potencia de dos números (2 ^ x), significativo / mantisa no cambiará durante el cálculo. Solo el exponente cambiará (el punto se moverá). Entonces el redondeo nunca sucederá. El resultado será determinista.
zigzag
Ejemplo: un número como 2 ^ 32 se representa como (exponente: 32, mantisa: 1). Si multiplicamos esto con otro flotante (exp, man), el resultado es (exp + 32, man * 1). Para la división, el resultado es (expo - 32, man * 1). Multiplicar la mantisa por 1 no cambia la mantisa, por lo que no importa cuántos bits tenga.
zigzag
28

La especificación C # (§4.1.6 tipos de punto flotante) específicamente permite que los cálculos de punto flotante se realicen utilizando una precisión mayor que la del resultado. Entonces, no, no creo que pueda hacer que esos cálculos sean deterministas directamente en .Net. Otros sugirieron varias soluciones, por lo que podría probarlas.

svick
fuente
9
Me acabo de dar cuenta de que la especificación C # realmente no importa si uno distribuye ensamblados compilados. Solo importa si uno quiere compatibilidad de fuente. Lo que realmente importa es la especificación CLR. Pero estoy bastante seguro de que sus garantías son tan débiles como las garantías de C #.
CodesInChaos
¿No se lanzaría doublecada vez después de una operación para quitar los bits no deseados y obtener resultados consistentes?
IllidanS4 quiere que Monica regrese
2
@ IllidanS4 No creo que eso garantice resultados consistentes.
svick
14

La siguiente página puede ser útil en el caso de que necesite portabilidad absoluta de tales operaciones. Analiza el software para probar implementaciones del estándar IEEE 754, incluido el software para emular operaciones de punto flotante. Sin embargo, la mayoría de la información es probablemente específica de C o C ++.

http://www.math.utah.edu/~beebe/software/ieee/

Una nota sobre punto fijo

Los números binarios de punto fijo también pueden funcionar bien como un sustituto del punto flotante, como es evidente por las cuatro operaciones aritméticas básicas:

  • La suma y la resta son triviales. Funcionan de la misma manera que los enteros. ¡Solo suma o resta!
  • Para multiplicar dos números de punto fijo, multiplique los dos números y luego desplace a la derecha el número definido de bits fraccionarios.
  • Para dividir dos números de punto fijo, desplace el dividendo a la izquierda del número definido de bits fraccionarios, luego divida por el divisor.
  • El capítulo cuatro de este documento tiene una guía adicional sobre la implementación de números binarios de punto fijo.

Los números de punto fijo binario se pueden implementar en cualquier tipo de datos enteros, como int, long y BigInteger, y los tipos no compatibles con CLS uint y ulong.

Como se sugiere en otra respuesta, puede usar tablas de búsqueda, donde cada elemento de la tabla es un número de punto fijo binario, para ayudar a implementar funciones complejas como seno, coseno, raíz cuadrada, etc. Si la tabla de búsqueda es menos granular que el número de punto fijo, se sugiere redondear la entrada agregando la mitad de la granularidad de la tabla de búsqueda a la entrada:

// Assume each number has a 12 bit fractional part. (1/4096)
// Each entry in the lookup table corresponds to a fixed point number
//  with an 8-bit fractional part (1/256)
input+=(1<<3); // Add 2^3 for rounding purposes
input>>=4; // Shift right by 4 (to get 8-bit fractional part)
// --- clamp or restrict input here --
// Look up value.
return lookupTable[input];
Peter O.
fuente
55
Debe cargar esto en un sitio de proyecto de código de código abierto, como sourceforge o github. Esto hace que sea más fácil de encontrar, más fácil de contribuir, más fácil de poner en su currículum, etc. Además, algunos consejos de código fuente (siéntase libre de ignorar): use en constlugar de staticconstantes, para que el compilador pueda optimizarlos; preferir funciones miembro a funciones estáticas (para que podamos llamar, ej. en myDouble.LeadingZeros()lugar de IntDouble.LeadingZeros(myDouble)); trate de evitar nombres de variables de una letra ( MultiplyAnyLengthpor ejemplo, tiene 9, lo que hace que sea muy difícil de seguir)
BlueRaja - Danny Pflughoeft
Tenga cuidado al usar uncheckedtipos no compatibles con CLS como ulong, uintetc., para fines de velocidad, ya que se usan muy raramente, el JIT no los optimiza tan agresivamente, por lo que usarlos puede ser más lento que usar tipos normales como longy int. Además, C # tiene una sobrecarga del operador , de lo que este proyecto se beneficiaría enormemente. Finalmente, ¿hay pruebas unitarias asociadas? Además de esas pequeñas cosas, increíble trabajo Peter, ¡esto es ridículamente impresionante!
BlueRaja - Danny Pflughoeft
Gracias por los comentarios. Realizo pruebas unitarias en el código. Sin embargo, son bastante extensos, demasiado extensos para lanzar por ahora. Incluso escribo rutinas auxiliares de pruebas unitarias para facilitar la escritura de múltiples pruebas. No uso operadores sobrecargados por ahora porque tengo planes de traducir el código a Java cuando termine.
Peter O.
2
Lo curioso es que cuando publiqué en tu blog no me di cuenta de que el blog era tuyo. Acababa de decidir probar google + y en su chispa C # sugirió esa entrada de blog. Entonces pensé "Qué coincidencia notable para nosotros dos comenzar a escribir tal cosa al mismo tiempo". Pero, por supuesto, tuvimos el mismo disparador :)
CodesInChaos
1
¿Por qué molestarse en portar esto a Java? Java ya ha garantizado la matemática determinista de coma flotante a través de strictfp.
Antimonio
9

¿Es esto un problema para C #?

Si. Diferentes arquitecturas son la menor de sus preocupaciones, diferentes velocidades de cuadros, etc. pueden conducir a desviaciones debido a imprecisiones en las representaciones flotantes, incluso si son las mismas inexactitudes (por ejemplo, la misma arquitectura, excepto una GPU más lenta en una máquina).

¿Puedo usar System.Decimal?

No hay razón para que no puedas hacerlo, sin embargo, es un perro lento.

¿Hay alguna manera de obligar a mi programa a ejecutarse con doble precisión?

Si. Hospede el tiempo de ejecución CLR usted mismo ; y compila todas las llamadas / banderas de nessecary (que cambian el comportamiento de la aritmética de coma flotante) en la aplicación C ++ antes de llamar a CorBindToRuntimeEx.

¿Hay alguna biblioteca que ayude a mantener consistentes los cálculos de coma flotante?

No que yo sepa.

¿Hay otra forma de resolver esto?

Ya he abordado este problema antes, la idea es usar QNumbers . Son una forma de reales que son de punto fijo; pero no punto fijo en base-10 (decimal) - más bien base-2 (binario); debido a esto, las primitivas matemáticas en ellas (suma, sub, mul, div) son mucho más rápidas que los ingenuos puntos fijos de base 10; especialmente si nes lo mismo para ambos valores (que en su caso lo sería). Además, debido a que son integrales, tienen resultados bien definidos en cada plataforma.

Tenga en cuenta que la velocidad de fotogramas todavía puede afectarlos, pero no es tan malo y se puede rectificar fácilmente utilizando puntos de sincronización.

¿Puedo usar más funciones matemáticas con QNumbers?

Sí, redondea un decimal para hacer esto. Además, realmente debería usar tablas de búsqueda para las funciones trigonométricas (sin, cos); ya que realmente pueden dar diferentes resultados en diferentes plataformas, y si los codifica correctamente, pueden usar QNumbers directamente.

Jonathan Dickinson
fuente
3
No estoy seguro de lo que estás hablando con el problema de las tasas de cuadros. Claramente, desearía tener una tasa de actualización fija (ver, por ejemplo, aquí ), ya sea que sea igual o no que la velocidad de fotogramas de la pantalla sea irrelevante. Mientras las imprecisiones sean las mismas en todas las máquinas, estamos bien. No entiendo tu tercera respuesta en absoluto.
BlueRaja - Danny Pflughoeft
@BlueRaja: La respuesta "¿Hay alguna manera de obligar a mi programa a ejecutarse con doble precisión?" equivaldría a volver a implementar todo el Common Language Runtime, lo que sería extremadamente complicado, o usar llamadas nativas a un archivo DLL C ++ desde la aplicación C #, como se insinuó en la respuesta del usuario shelleybutterfly. Piense en "QNumbers" simplemente como números de punto fijo binarios, como se insinuó en mi respuesta (hasta ahora no había visto números de punto fijo binarios llamados "QNumbers".)
Peter O.
@Pieter O. No es necesario volver a implementar el tiempo de ejecución. El servidor en el que trabajo en mi empresa aloja el tiempo de ejecución CLR como una aplicación nativa de C ++ (también lo hace SQL Server). Le sugiero que google CorBindToRuntimeEx.
Jonathan Dickinson
@BlueRaja depende del juego en cuestión. La aplicación de pasos de velocidad de fotogramas fijos a todos los juegos no es una opción viable, porque el algoritmo AOE introduce latencia artificial; que es inaceptable, por ejemplo, en un FPS.
Jonathan Dickinson
1
@ Jonathan: Esto es sólo un problema en los juegos peer-to-peer que envían la entrada única - para éstos, que tienen que tener una velocidad de actualización fijo. La mayoría de los FPS no funcionan así, pero los pocos que necesariamente tienen una tasa de actualización fija. Ver esta pregunta .
BlueRaja - Danny Pflughoeft
6

De acuerdo con esta entrada de blog de MSDN un poco antigua, el JIT no usará SSE / SSE2 para coma flotante, todo es x87. Por eso, como mencionó, debe preocuparse por los modos y las banderas, y en C # eso no es posible controlarlo. Por lo tanto, el uso de operaciones normales de coma flotante no garantizará exactamente el mismo resultado en cada máquina para su programa.

Para obtener una reproducibilidad precisa de doble precisión, tendrá que hacer una emulación de punto flotante (o punto fijo) de software. No sé de las bibliotecas de C # para hacer esto.

Dependiendo de las operaciones que necesite, es posible que pueda escapar con una precisión única. Aquí está la idea:

  • almacenar todos los valores que le interesan en precisión simple
  • para realizar una operación:
    • expandir entradas a doble precisión
    • hacer operación en doble precisión
    • convertir el resultado a precisión simple

El gran problema con x87 es que los cálculos pueden hacerse con una precisión de 53 bits o 64 bits, dependiendo del indicador de precisión y de si el registro se derramó en la memoria. Pero para muchas operaciones, realizar la operación con alta precisión y redondear a una precisión menor garantizará la respuesta correcta, lo que implica que se garantizará que la respuesta sea la misma en todos los sistemas. Si obtiene la precisión adicional no importará, ya que tiene suficiente precisión para garantizar la respuesta correcta en cualquier caso.

Operaciones que deberían funcionar en este esquema: suma, resta, multiplicación, división, sqrt. Cosas como sin, exp, etc. no funcionarán (los resultados generalmente coincidirán pero no hay garantía). "¿Cuándo es inocuo el doble redondeo?" Referencia de ACM (req. De pago)

¡Espero que esto ayude!

Nathan Whitehead
fuente
2
También es un problema que .NET 5, 6 o 42 ya no utilicen el modo de cálculo x87. No hay nada en el estándar que lo requiera.
Eric J.
5

Como ya se dijo en otras respuestas: Sí, este es un problema en C #, incluso cuando se mantiene puro Windows.

En cuanto a una solución: puede reducir (y con un poco de esfuerzo / rendimiento) evitar el problema por completo si usa la BigIntegerclase integrada y escala todos los cálculos a una precisión definida mediante el uso de un denominador común para cualquier cálculo / almacenamiento de dichos números.

Según lo solicitado por OP - con respecto al rendimiento:

System.Decimalrepresenta un número con 1 bit para un signo y un entero de 96 bits y una "escala" (que representa dónde está el punto decimal). Para todos los cálculos que realice, debe operar en esta estructura de datos y no puede usar ninguna instrucción de coma flotante integrada en la CPU.

La BigInteger"solución" hace algo similar: solo que usted puede definir cuántos dígitos necesita / desea ... quizás solo desee 80 bits o 240 bits de precisión.

La lentitud proviene siempre de tener que simular todas las operaciones en este número a través de instrucciones de solo entero sin usar las instrucciones incorporadas de CPU / FPU, lo que a su vez conduce a muchas más instrucciones por operación matemática.

Para reducir el impacto en el rendimiento, existen varias estrategias, como QNumbers (ver respuesta de Jonathan Dickinson: ¿las matemáticas de punto flotante son consistentes en C #? ¿Puede ser? ) Y / o el almacenamiento en caché (por ejemplo, cálculos trigonométricos ...) etc.

Yahia
fuente
1
Tenga en cuenta que BigIntegersolo está disponible en .Net 4.0.
svick
Mi conjetura es que el rendimiento de rendimiento BigIntegersupera incluso el rendimiento alcanzado por Decimal.
CodesInChaos
Un par de veces en las respuestas aquí hay una referencia al éxito en el rendimiento del uso Decimal(@Jonathan Dickinson - 'dog slow') o BigInteger(@CodeInChaos comentario arriba) - ¿alguien puede dar una pequeña explicación sobre estos éxitos en el desempeño y si / por qué realmente son un obstáculo para proporcionar una solución.
Barry Kaye
@Yahia - gracias por la edición - lectura interesante, sin embargo, ¿podrías por favor también dar una estimación aproximada del impacto del rendimiento de no usar 'flotador' si estamos hablando un 10% más lento o 10 veces más lento? desea tener una idea del orden de magnitud implícito.
Barry Kaye
es más parecido en el área de 1: 5 que "solo el 10%"
Yahia
2

Bueno, aquí estaría mi primer intento de cómo hacer esto :

  1. Cree un proyecto ATL.dll que tenga un objeto simple que se utilizará para sus operaciones críticas de coma flotante. asegúrese de compilarlo con banderas que deshabiliten el uso de cualquier hardware que no sea xx87 para hacer coma flotante.
  2. Cree funciones que llamen operaciones de punto flotante y devuelva los resultados; comience de manera simple y luego, si funciona para usted, siempre puede aumentar la complejidad para satisfacer sus necesidades de rendimiento más adelante si es necesario.
  3. Coloque las llamadas control_fp alrededor de las matemáticas reales para asegurarse de que se haga de la misma manera en todas las máquinas.
  4. Consulte su nueva biblioteca y pruebe para asegurarse de que funciona como se esperaba.

(Creo que puede compilar a un archivo .dll de 32 bits y luego usarlo con x86 o AnyCpu [o probablemente solo apunte a x86 en un sistema de 64 bits; vea el comentario a continuación]).

Luego, suponiendo que funcione, si desea usar Mono, imagino que debería poder replicar la biblioteca en otras plataformas x86 de manera similar (no COM, por supuesto; aunque, tal vez, ¿con vino? Un poco fuera de mi área una vez vamos allí sin embargo ...).

Suponiendo que puede hacer que funcione, debería poder configurar funciones personalizadas que pueden realizar múltiples operaciones a la vez para solucionar cualquier problema de rendimiento, y tendrá matemática de punto flotante que le permite tener resultados consistentes en todas las plataformas con una cantidad mínima de código escrito en C ++, y dejando el resto de su código en C #.

shelleybutterfly
fuente
"compila en un .dll de 32 bits y luego usa ... AnyCpu" Creo que esto solo funcionará cuando se ejecute en un sistema de 32 bits. En un sistema de 64 bits, solo un programa dirigido x86puede cargar el dll de 32 bits.
CodesInChaos
2

No soy un desarrollador de juegos, aunque tengo mucha experiencia con problemas computacionalmente difíciles ... así que haré lo mejor que pueda.

La estrategia que adoptaría es esencialmente esta:

  • Use un método más lento (si es necesario; si hay una forma más rápida, ¡genial!), Pero predecible para obtener resultados reproducibles
  • Use doble para todo lo demás (por ejemplo, renderizado)

El resumen de esto es: necesitas encontrar un equilibrio. Si está gastando 30 ms de renderizado (~ 33 fps) y solo 1 ms haciendo detección de colisión (o inserta alguna otra operación altamente sensible), incluso si triplica el tiempo que lleva hacer la aritmética crítica, el impacto que tiene en su velocidad de cuadros es baja de 33.3 fps a 30.3 fps.

Le sugiero que haga un perfil de todo, tenga en cuenta la cantidad de tiempo que pasa haciendo cada uno de los cálculos notablemente caros, luego repita las mediciones con 1 o más métodos para resolver este problema y vea cuál es el impacto.

Brian Vandenberg
fuente
1

Verificando los enlaces en las otras respuestas deja en claro que nunca tendrá la garantía de si el punto flotante se implementa "correctamente" o si siempre recibirá una cierta precisión para un cálculo dado, pero quizás podría hacer un mejor esfuerzo al (1) truncando todos los cálculos a un mínimo común (por ejemplo, si diferentes implementaciones le darán 32 a 80 bits de precisión, siempre truncando cada operación a 30 o 31 bits), (2) tenga una tabla de algunos casos de prueba al inicio (casos límite de sumar, restar, multiplicar, dividir, sqrt, coseno, etc.) y si la implementación calcula valores que coinciden con la tabla, no se moleste en hacer ningún ajuste.

Miguel
fuente
siempre truncando cada operación a 30 o 31 bits , esto es exactamente lo que hace el floattipo de datos en máquinas x86, sin embargo, esto causará resultados ligeramente diferentes de las máquinas que hacen todos sus cálculos usando solo 32 bits, y estos pequeños cambios se propagarán con el tiempo. De ahí la pregunta.
BlueRaja - Danny Pflughoeft
Si "N bits de precisión" significa que cualquier cálculo es preciso para esa cantidad de bits, y la máquina A tiene una precisión de 32 bits mientras que la máquina B tiene una precisión de 48 bits, entonces los primeros 32 bits de cualquier cálculo de ambas máquinas deben ser idénticos. ¿No se truncaría a 32 bits o menos después de cada operación mantener ambas máquinas exactamente sincronizadas? Si no, ¿cuál es un ejemplo?
Identificación de protección de testigos 44583292
-3

Su pregunta en cosas bastante difíciles y técnicas O_o. Sin embargo, puedo tener una idea.

Seguro que sabe que la CPU realiza algunos ajustes después de cualquier operación flotante. Y la CPU ofrece varias instrucciones diferentes que hacen diferentes operaciones de redondeo.

Entonces, para una expresión, su compilador elegirá un conjunto de instrucciones que lo llevarán a un resultado. Pero cualquier otro flujo de trabajo de instrucciones, incluso si tienen la intención de calcular la misma expresión, puede proporcionar otro resultado.

Los 'errores' cometidos por un ajuste de redondeo crecerán con cada instrucción adicional.

Como ejemplo, podemos decir que a nivel de ensamblaje: a * b * c no es equivalente a a * c * b.

No estoy completamente seguro de eso, tendrá que preguntar por alguien que conozca la arquitectura de la CPU mucho más que yo: p

Sin embargo, para responder a su pregunta: en C o C ++ puede resolver su problema porque tiene cierto control sobre el código de máquina generado por su compilador, sin embargo, en .NET no tiene ninguno. Por lo tanto, siempre que su código de máquina pueda ser diferente, nunca estará seguro del resultado exacto.

Tengo curiosidad por saber de qué manera esto puede ser un problema porque la variación parece muy mínima, pero si necesita una operación realmente precisa, la única solución en la que puedo pensar será aumentar el tamaño de sus registros flotantes. Use doble precisión o incluso doble largo si puede (no estoy seguro de que sea posible usando CLI).

Espero haber sido lo suficientemente claro, no soy perfecto en inglés (... en absoluto: s)

AxFab
fuente
9
Imagina un tirador P2P. Le disparas a un chico, lo golpeas y él muere, pero está muy cerca, casi te lo pierdes. En la PC del otro tipo, se utilizan cálculos ligeramente diferentes y se calcula que echas de menos. ¿Ves el problema ahora? En este caso, aumentar el tamaño de los registros no ayudará (al menos no completamente). Usar exactamente el mismo cálculo en cada computadora lo hará.
svick
55
En este escenario, a uno generalmente no le importa cuán cerca esté el resultado del resultado real (siempre que sea razonable), pero lo que importa es que sea exactamente igual para todos los usuarios.
CodesInChaos
1
Tienes razón, no pensé en este tipo de escenario. Sin embargo, estoy de acuerdo con @CodeInChaos en este caso. No me pareció realmente inteligente tomar una decisión importante dos veces. Esto es más un problema de arquitectura de software. Un programa, la aplicación del tirador por ejemplo, debe realizar el cálculo y enviar el resultado a los demás. Nunca tendrá errores de esta manera. Tienes un golpe o no, pero solo uno toma la decisión. Como decir @driushkin
AxFab
2
@Aesgar: Sí, así es como funcionan la mayoría de los tiradores; esa "autoridad" se llama servidor, y llamamos a la arquitectura general una arquitectura "cliente / servidor". Sin embargo, hay otro tipo de arquitectura: de igual a igual. En P2P, no hay servidor; más bien, todos los clientes deben verificar todas las acciones entre ellos antes de que algo suceda. Esto aumenta el retraso, por lo que no es aceptable para los tiradores, pero disminuye enormemente el tráfico de la red, por lo que es perfecto para juegos en los que un pequeño retraso (~ 250 ms) es aceptable, pero la sincronización de todo el estado del juego no lo es. A saber, los juegos RTS como C&C y Starcraft usan P2P.
BlueRaja - Danny Pflughoeft
55
En un juego p2p no tienes una máquina confiable en la que confiar. Si permite que una estación decida si su bala golpeó o no, abre la posibilidad de que un cliente haga trampa. Además, los enlaces ni siquiera pueden manejar la cantidad de datos que a veces resultan: los juegos funcionan enviando las órdenes en lugar de los resultados. Juego juegos de estrategia en tiempo real y muchas veces he visto tanta basura volando que no hay forma de que pueda enviarse a través de enlaces ascendentes domésticos normales.
Loren Pechtel