visual tipo solo redondear quitar puede por poner operandos operador multiplicar mostrar entero decimales dato como aplicar c# .net hash decimal

tipo - C#¿Por qué los decimales iguales producen valores hash desiguales?



redondear decimales c# (6)

Nos encontramos con un número mágico decimal que rompió nuestra tabla hash. Lo reduje al siguiente caso mínimo:

decimal d0 = 295.50000000000000000000000000m; decimal d1 = 295.5m; Console.WriteLine("{0} == {1} : {2}", d0, d1, (d0 == d1)); Console.WriteLine("0x{0:X8} == 0x{1:X8} : {2}", d0.GetHashCode(), d1.GetHashCode() , (d0.GetHashCode() == d1.GetHashCode()));

Dando el siguiente resultado:

295.50000000000000000000000000 == 295.5 : True 0xBF8D880F == 0x40727800 : False

Lo que es realmente peculiar: cambiar, agregar o eliminar cualquiera de los dígitos en d0 y el problema desaparece. ¡Incluso agregando o eliminando uno de los ceros finales! El signo no parece importar sin embargo.

Nuestra solución es dividir el valor para deshacerse de los ceros finales, así:

decimal d0 = 295.50000000000000000000000000m / 1.000000000000000000000000000000000m;

Pero mi pregunta es, ¿cómo está C # haciendo esto mal?


Este es un error de redondeo decimal.

Se requiere demasiada precisión para establecer d0 con .000000000000000, como consecuencia, el algoritmo a cargo de él comete un error y termina dando un resultado diferente. Podría clasificarse como un error en este ejemplo, aunque tenga en cuenta que se supone que el tipo "decimal" tiene una precisión de 28 dígitos , y en este caso, en realidad necesita una precisión de 29 dígitos para d0.

Esto se puede probar pidiendo la representación hexadecimal bruta completa de d0 y d1.


La documetation sugiere que debido a que GetHashCode() es impredecible, debe crear uno propio. Se considera impredecible porque cada Tipo tiene su propia implementación y, como no conocemos sus elementos internos, debemos crear el nuestro de acuerdo con la forma en que evaluemos la singularidad.

Sin embargo, creo que la respuesta es que GetHashCode() no está utilizando el valor decimal matemático para crear el código hash.

Matemáticamente vemos que 295.50000000 y 295.5 son lo mismo. Cuando miras los objetos decimales en el IDE esto también es cierto. Sin embargo, si hace un ToString() en ambos decimales verá que el compilador los ve de manera diferente, es decir, todavía verá 295.50000000. GetHashCode() evidentemente no está utilizando la representación matemática del decimal para crear el código hash.

Su solución es simplemente crear un nuevo decimal sin todos los ceros finales, razón por la cual funciona.


Me encontré con este error también ... :-(

Las pruebas (ver a continuación) indican que esto depende de la precisión máxima disponible para el valor. Los códigos hash incorrectos solo ocurren cerca de la precisión máxima para el valor dado. Como muestran las pruebas, el error parece depender de los dígitos que quedan del punto decimal. A veces, el único código de hash para maxDecimalDigits - 1 es incorrecto, a veces el valor de maxDecimalDigits es incorrecto.

var data = new decimal[] { // 123456789012345678901234567890 1.0m, 1.00m, 1.000m, 1.0000m, 1.00000m, 1.000000m, 1.0000000m, 1.00000000m, 1.000000000m, 1.0000000000m, 1.00000000000m, 1.000000000000m, 1.0000000000000m, 1.00000000000000m, 1.000000000000000m, 1.0000000000000000m, 1.00000000000000000m, 1.000000000000000000m, 1.0000000000000000000m, 1.00000000000000000000m, 1.000000000000000000000m, 1.0000000000000000000000m, 1.00000000000000000000000m, 1.000000000000000000000000m, 1.0000000000000000000000000m, 1.00000000000000000000000000m, 1.000000000000000000000000000m, 1.0000000000000000000000000000m, 1.00000000000000000000000000000m, 1.000000000000000000000000000000m, 1.0000000000000000000000000000000m, 1.00000000000000000000000000000000m, 1.000000000000000000000000000000000m, 1.0000000000000000000000000000000000m, }; for (int i = 0; i < 1000; ++i) { var d0 = i * data[0]; var d0Hash = d0.GetHashCode(); foreach (var d in data) { var value = i * d; var hash = value.GetHashCode(); Console.WriteLine("{0};{1};{2};{3};{4};{5}", d0, value, (d0 == value), d0Hash, hash, d0Hash == hash); } }


Otro error (?) Que da como resultado una representación diferente de bytes para el mismo decimal en diferentes compiladores: intente compilar el siguiente código en VS 2005 y luego VS 2010. O mire mi article sobre Code Project.

class Program { static void Main(string[] args) { decimal one = 1m; PrintBytes(one); PrintBytes(one + 0.0m); // compare this on different compilers! PrintBytes(1m + 0.0m); Console.ReadKey(); } public static void PrintBytes(decimal d) { MemoryStream memoryStream = new MemoryStream(); BinaryWriter binaryWriter = new BinaryWriter(memoryStream); binaryWriter.Write(d); byte[] decimalBytes = memoryStream.ToArray(); Console.WriteLine(BitConverter.ToString(decimalBytes) + " (" + d + ")"); } }

Algunas personas usan el siguiente código de normalización d=d+0.0000m que no funciona correctamente en VS 2010. Su código de normalización ( d=d/1.000000000000000000000000000000000m ) se ve bien - Yo uso el mismo para obtener la misma matriz de bytes para los mismos decimales .


Para empezar, C # no está haciendo nada malo en absoluto. Este es un error de framework .

Sin embargo, de hecho parece un error: básicamente, cualquier normalización involucrada en la comparación para la igualdad debe usarse de la misma manera para el cálculo del código hash. Lo he comprobado y puedo reproducirlo también (usando .NET 4), incluido el control de los métodos Equals(decimal) e Equals(object) , así como el operador == .

Definitivamente parece que es el valor d0 el problema, ya que agregar 0s finales a d1 no cambia los resultados (hasta que es lo mismo que d0 por supuesto). Sospecho que hay un caso de esquina disparado por la representación exacta de bit allí.

Me sorprende que no lo sea (y como dices, funciona la mayor parte del tiempo), pero debes informar el error en Connect .


Probé esto en VB.NET (v3.5) y obtuve lo mismo.

Lo interesante de los códigos hash:

A) 0x40727800 = 1081243648

B) 0xBF8D880F = -1081243648

Usando Decimal.GetBits () Encontré

formato: Mantissa (hhhhhhhh hhhhhhhh hhhhhhhh) Exponente (seee0000) (h es valores, ''s'' es signo, ''e'' es exponente, 0 debe ser ceros)

d1 ==> 00000000 00000000 00000B8B - 00010000 = (2955/10 ^ 1) = 295.5

do ==> 5F7B2FE5 D8EACD6E 2E000000 - 001A0000

... que se convierte en 29550000000000000000000000000/10 ^ 26 = 295.5000000 ... etc.

** editar: vale, escribí una calculadora de decimales hexadecimales de 128 bits y lo anterior es exactamente correcto

Definitivamente parece un error de conversión interna de algún tipo. Microsoft declara explícitamente que no garantiza su implementación predeterminada de GetHashCode. Si lo está utilizando para algo importante, entonces probablemente tenga sentido escribir su propio GetHashCode para el tipo decimal. Al formatearlo con un decimal fijo, cadena de anchura fija y hash parece funcionar, por ejemplo (> 29 decimales,> 58 de ancho: se ajusta a todos los decimales posibles).

* editar: ya no sé más sobre esto. Todavía debe ser un error de conversión en alguna parte, ya que la precisión almacenada cambia fundamentalmente el valor real en la memoria. Que los códigos hash terminen como negativos firmados entre sí es una gran pista, necesitaría buscar más en la implementación predeterminada del código hash para encontrar más.

28 o 29 dígitos no deberían importar a menos que exista un código dependiente que no evalúe las extensiones externas adecuadamente. El entero más grande de 96 bits accesible es:

79228162514264337593543950335

para que pueda tener 29 dígitos siempre que todo (sin punto decimal) sea menor que este valor. No puedo evitar pensar que esto es algo mucho más sutil en el cálculo del código hash en alguna parte.