2010-11-17 13 views
7

Me di cuenta de que cuando almacenaba un valor doble como, por ejemplo, x = 0.56657011973046234 en una base de datos sqlite, y luego recuperarlo más tarde, obtengo y = 0.56657011973046201. De acuerdo con sqlite spec y .NET spec (ninguno de los cuales originalmente me molesté en leer :) esto es esperado y normal.¿Cómo puedo "recortar" un doble de C# al valor que se almacenará como en una base de datos sqlite?

Mi problema es que, si bien la alta precisión no es importante, mi aplicación trata con los usuarios que ingresan/seleccionan dobles que representan información 3D básica y luego ejecutan simulaciones sobre ellos para encontrar un resultado. Y esta entrada se puede guardar en una base de datos sqlite para volver a cargarla y volver a ejecutarla más tarde.

La confusión se produce porque una serie recién creada de entradas obviamente simulará de forma ligeramente diferente a esas mismas entradas una vez almacenadas y cargadas (ya que los valores dobles han cambiado). Esto es lógico, pero no deseable.

No he llegado a un acuerdo sobre cómo lidiar con esto, pero mientras tanto me gustaría limitar/fijar las entradas del usuario a los valores que se pueden almacenar exactamente en una base de datos sqlite. Entonces, si un usuario ingresa 0.56657011973046234, en realidad se transforma en 0.56657011973046201.

Sin embargo, no he podido averiguar, dado un número, qué valor se almacenaría en la base de datos, excepto almacenarlo y recuperarlo de la base de datos, lo que parece torpe. ¿Hay una forma establecida de hacer esto?

+3

Ya sabes, si la microcomputadora Royal McBee hubiera hecho esto, Edward Lorenz nunca habría descubierto la teoría del caos. :) –

+0

@fostandy: ¿Son esos valores reales (0.56657011973046234 y 0.56657011973046201) o los inventaron? 0.56657011973046201 tiene 17 dígitos, por lo que se supone que debe redondear el viaje al mismo valor doble que 0.56657011973046234 (lo compruebo en C y no) –

+0

@Rick Regan - 0.56657011973046234 es algo que obtuve de random.NextDouble(). 0.56657011973046201 es lo que obtuve una vez que lo almacené en un campo numérico sqlite y lo busqué de nuevo. Según lo entiendo (que es limitado), los últimos 2 dígitos no están garantizados para ida y vuelta. – fostandy

Respuesta

2

Double round tiene una implementación con un parámetro que especifica el número de dígitos. Use esto para redondear a 14 dígitos (digamos) con: rval = Math.Round (Val, 14)

Luego, redondee cuando reciba el valor de la base de datos, y al comienzo de las simulaciones, es decir. Entonces, ¿en la coincidencia de valores?

Para más detalles:

http://msdn.microsoft.com/en-us/library/75ks3aby.aspx

Otro pensamiento si no está comparando los valores en la base de datos, simplemente almacenarlos: ¿Por qué no simplemente almacenarlos como datos binarios? Entonces, ¿todos los bits se almacenarían y recuperarían al pie de la letra?

2

Suponiendo que SQL Lite y .NET implementan correctamente la especificación IEEE, debería poder obtener los mismos resultados numéricos si usa el mismo tipo de coma flotante en ambos lados (porque el valor no debe alterarse) cuando pasa de la base de datos a C# y viceversa).

Actualmente está utilizando el punto flotante IEEE de 8 bytes (simple) (*) en SQL Lite y el punto flotante de 16 bytes en C# (doble). El tipo float en C# corresponde al estándar IEEE de 8 bytes, por lo que usar este tipo en lugar de double podría resolver el problema.

(*) La documentación de SQL Lite dice que REAL es un valor de coma flotante, almacenado como un número de coma flotante IEEE de 8 bytes.

+0

Un flotante C# es de 4 bytes, un doble C# es de 8 bytes, por lo que estoy usando un punto flotante de 8 bytes en C# (doble). Como tal, originalmente esperaba que los valores fueran idénticos después de la recuperación, pero no se deben al hecho de que el doble C#, mientras _completa_ con la especificación IEEE, realmente contiene información adicional. SQLite no guarda esta información porque solo cumple con las especificaciones IEEE. – fostandy

1

Puede usar una cadena para almacenar el # en la base de datos. Personalmente, hice lo que winwaed sugirió para redondear antes de almacenar y después de recuperar del db (que usaba numérico()).

Recuerdo haber sido quemado por el redondeo bancario, pero podría ser que no cumplió con las especificaciones.

1

Puede almacenar el doble como una cadena, y utilizando el formato de ida y vuelta al convertir el doble para una cadena, está garantizado para generar el mismo valor cuando analizada:

string formatted = theDouble.ToString("R", CultureInfo.Invariant); 
1

si desea que el valores de entrada decimales para ida y vuelta, entonces tendrá que limitarlos a 15 dígitos significativos. Si desea que los valores de coma flotante de precisión doble internos de SQLite sean de ida y vuelta, es posible que no tenga suerte; que requiere la impresión de un mínimo de 17 dígitos significativos, pero por lo que puedo decir, SQLite los imprime hasta un máximo de 15 (EDIT: tal vez un experto en SQLite puede confirmar esto? Acabo de leer el código fuente y lo remonté - Estaba en lo correcto, la precisión está limitada a 15 dígitos.)

He probado su ejemplo en la interfaz de comandos de SQLite en Windows. Inserté 0.56657011973046234, y seleccioné 0.566570119730462. En C, cuando asigné 0.566570119730462 a un doble y lo imprimí a 17 dígitos, obtuve 0.56657011973046201; ese es el mismo valor que obtienes de C#. 0.56657011973046234 y 0.56657011973046201 se correlacionan con diferentes números de coma flotante, por lo que, en otras palabras, el doble de SQLite no hace ida y vuelta.

3

La respuesta puede ser almacenar los valores dobles como 17 cadenas de dígitos significativos. Mira la diferencia entre la forma en SQLite maneja números reales vs. texto (voy a ilustrar con la interfaz de línea de comandos, para simplificar):

sqlite> create table t1(dr real, dt varchar(25)); 
sqlite> insert into t1 values(0.56657011973046234,'0.56657011973046234'); 
sqlite> select * from t1; 
0.566570119730462|0.56657011973046234 

Almacenamiento con afinidad real es la causa de su problema - sólo se SQLite te devuelve una aproximación de 15 dígitos. Si, en cambio, lo almacena como texto, puede recuperar la cadena original con su programa C# y convertirla nuevamente al doble original.

Cuestiones relacionadas