Pérdida JavaScript para C # numérico de precisión

TGH:

Cuando serializar y deserializar valores entre JavaScript y C # usando SignalR con MessagePack estoy viendo un poco de pérdida de precisión en C # en el extremo receptor.

Como ejemplo voy a enviar el valor de 0,005 JavaScript para C #. Cuando el valor deserializado aparece en el lado C # que estoy recibiendo el valor 0.004999999888241291, que está cerca, pero no exactamente 0.005. El valor en el lado JavaScript es Numbery en el lado C # estoy usando double.

He leído que JavaScript no puede representar números en coma flotante exactamente lo que puede conducir a resultados como 0.1 + 0.2 == 0.30000000000000004. Sospecho que el problema que estoy viendo está relacionado con esta función de JavaScript.

La parte interesante es que no estoy viendo el mismo problema en la otra dirección. Envío de 0,005 de C # para JavaScript resulta en el valor de 0.005 en JavaScript.

Editar : El valor de C # es sólo acorta en la ventana del depurador JS. Como se mencionó @Pete que se expande a algo que no es exactamente 0,5 (,005000000000000000104083408558). Esto significa que la discrepancia ocurre en ambos lados por lo menos.

Serialización JSON no tiene el mismo problema ya que estoy asumiendo que va a través de cuerda que sale del medio receptor en WRT control de analizar el valor en su tipo numérico nativo.

Me pregunto si hay una manera de utilizar la serialización binaria tener valores coincidentes en ambos lados.

Si no es así, quiere decir esto que no hay manera de tener 100% de las conversiones binarias exactas entre JavaScript y C #?

Tecnología utilizada:

  • JavaScript
  • .Net Core con SignalR y msgpack5

Mi código se basa en este post . La única diferencia es que estoy usando ContractlessStandardResolver.Instance.

weichch:

I probado floaty double, y de manera interesante en este caso particular, solamente doubletenido el problema, mientras que floatparece estar funcionando (es decir, 0.005 se lee en el servidor).

Inspección en el mensaje de bytes sugirió que 0.005 se envía como tipo Float32Doubleque es un / 32 bits IEEE 754 de precisión simple número de coma flotante de 4 bytes a pesar de Numberque es de punto flotante de 64 bits.

Ejecute el código siguiente en la consola confirma lo anterior:

msgpack5().encode(Number(0.005))

// Output
Uint8Array(5) [202, 59, 163, 215, 10]

mspack5 proporciona una opción para forzar el punto flotante de 64 bits:

msgpack5({forceFloat64:true}).encode(Number(0.005))

// Output
Uint8Array(9) [203, 63, 116, 122, 225, 71, 174, 20, 123]

Sin embargo, la forceFloat64opción no es utilizada por signalr-protocol-msgpack .

A pesar de eso explica por qué floatfunciona en el lado del servidor, pero no es realmente una solución para que a partir de ahora . Vamos a esperar lo que dice Microsoft .

posibles soluciones

  • Hack msgpack5 opciones? Tenedor y compilar sus propios msgpack5 con forceFloat64defecto a cierto ?? No lo sé.
  • Cambiar a floatel lado del servidor
  • Utilizar stringen ambos lados
  • Cambiar a decimalen el lado del servidor y de escritura personalizado IFormatterProvider. decimales de tipo no primitivo, y IFormatterProvider<decimal>se llama para las propiedades de tipo complejo
  • Proporcionar un método para recuperar doublevalor de la propiedad y hacer el double-> float-> decimal-> doubletruco
  • Otras soluciones poco realistas que usted podría pensar

TL; DR

El problema con el envío de cliente JS número único punto flotante para C # backend causa de un problema conocido en coma flotante:

// value = 0.00499999988824129, crazy C# :)
var value = (double)0.005f;

Para usos directos de doublelos métodos, el problema podría ser resuelto por una costumbre MessagePack.IFormatterResolver:

public class MyDoubleFormatterResolver : IFormatterResolver
{
    public static MyDoubleFormatterResolver Instance = new MyDoubleFormatterResolver();

    private MyDoubleFormatterResolver()
    { }

    public IMessagePackFormatter<T> GetFormatter<T>()
    {
        return MyDoubleFormatter.Instance as IMessagePackFormatter<T>;
    }
}

public sealed class MyDoubleFormatter : IMessagePackFormatter<double>, IMessagePackFormatter
{
    public static readonly MyDoubleFormatter Instance = new MyDoubleFormatter();

    private MyDoubleFormatter()
    {
    }

    public int Serialize(
        ref byte[] bytes,
        int offset,
        double value,
        IFormatterResolver formatterResolver)
    {
        return MessagePackBinary.WriteDouble(ref bytes, offset, value);
    }

    public double Deserialize(
        byte[] bytes,
        int offset,
        IFormatterResolver formatterResolver,
        out int readSize)
    {
        double value;
        if (bytes[offset] == 0xca)
        {
            // 4 bytes single
            // cast to decimal then double will fix precision issue
            value = (double)(decimal)MessagePackBinary.ReadSingle(bytes, offset, out readSize);
            return value;
        }

        value = MessagePackBinary.ReadDouble(bytes, offset, out readSize);
        return value;
    }
}

Y utilizar el sistema de resolución:

services.AddSignalR()
    .AddMessagePackProtocol(options =>
    {
        options.FormatterResolvers = new List<MessagePack.IFormatterResolver>()
        {
            MyDoubleFormatterResolver.Instance,
            ContractlessStandardResolver.Instance,
        };
    });

La resolución no es perfecto, como la fundición a decimalcontinuación, a doubleralentiza el proceso y podría ser peligroso .

sin embargo

De acuerdo con la OP señalado en los comentarios, esto no puede resolver el problema si el uso de tipos complejos que tienen doublepropiedades que regresan.

Investigaciones posteriores revelaron la causa del problema en MessagePack-CSharp:

// Type: MessagePack.MessagePackBinary
// Assembly: MessagePack, Version=1.9.0.0, Culture=neutral, PublicKeyToken=b4a0369545f0a1be
// MVID: B72E7BA0-FA95-4EB9-9083-858959938BCE
// Assembly location: ...\.nuget\packages\messagepack\1.9.11\lib\netstandard2.0\MessagePack.dll

namespace MessagePack.Decoders
{
  internal sealed class Float32Double : IDoubleDecoder
  {
    internal static readonly IDoubleDecoder Instance = (IDoubleDecoder) new Float32Double();

    private Float32Double()
    {
    }

    public double Read(byte[] bytes, int offset, out int readSize)
    {
      readSize = 5;
      // The problem is here
      // Cast a float value to double like this causes precision loss
      return (double) new Float32Bits(bytes, checked (offset + 1)).Value;
    }
  }
}

El decodificador anteriormente se utiliza cuando se necesita para convertir un solo floatnúmero a double:

// From MessagePackBinary class
MessagePackBinary.doubleDecoders[202] = Float32Double.Instance;

v2

Este problema existe en v2 versiones de MessagePack-CSharp. He presentado un problema en github , aunque el tema no va a ser fijo .

Supongo que te gusta

Origin http://10.200.1.11:23101/article/api/json?id=391532&siteId=1
Recomendado
Clasificación