[javascript] JavaScript에서 C #으로 숫자 정밀도 손실

MessagePack과 함께 SignalR을 사용하여 JavaScript와 C # 간의 값을 직렬화 및 역 직렬화하는 경우 수신 측에서 C #에 약간의 정밀 손실이 나타납니다.

예를 들어 JavaScript에서 C #으로 0.005 값을 보내고 있습니다. deserialized 값이 C # 측에 나타나면 value 0.004999999888241291가 가깝습니다. 그러나 정확히 0.005는 아닙니다. JavaScript 측의 값 Number은 C # 측에서 사용하고 double있습니다.

JavaScript는 부동 소수점 숫자를 정확하게 표현할 수 없으므로 이와 같은 결과를 초래할 수 있음을 읽었습니다 0.1 + 0.2 == 0.30000000000000004. 내가보고있는 문제 가이 JavaScript 기능과 관련이 있다고 생각합니다.

흥미로운 부분은 같은 문제가 다른 방향으로 가고 있지 않다는 것입니다. C #에서 JavaScript로 0.005를 보내면 JavaScript에서 값이 0.005가됩니다.

편집 : C #의 값이 JS 디버거 창에서 짧아졌습니다. @Pete가 언급했듯이 정확히 0.5가 아닌 것으로 확장됩니다 (0.005000000000000000104083408558). 이것은 적어도 불일치가 양쪽에서 발생한다는 것을 의미합니다.

JSON 직렬화는 문자열을 통과한다고 가정하기 때문에 동일한 문제가 없습니다. 수신 환경이 제어 wrt에서 값을 기본 숫자 유형으로 구문 분석하는 것을 허용합니다.

이진 직렬화를 사용하여 양쪽에 일치하는 값을 갖는 방법이 있는지 궁금합니다.

그렇지 않으면 JavaScript와 C #간에 100 % 정확한 이진 변환을 수행 할 수있는 방법이 없다는 의미입니까?

사용 된 기술 :

  • 자바 스크립트
  • SignalR 및 msgpack5가 포함 된 .Net Core

내 코드는 이 게시물을 기반으로 합니다 . 유일한 차이점은 내가 사용하고 있다는 것 ContractlessStandardResolver.Instance입니다.



답변

최신 정보

이것은 다음 릴리스 (5.0.0-preview4)에서 수정 되었습니다 .

원래 답변

나는 이 특별한 경우에 테스트 float하고 double흥미롭게 만 double문제가있는 반면 float작동 하는 것처럼 보입니다 (즉, 서버에서 0.005를 읽습니다).

메시지 바이트를 조사한 결과, 0.005 가 64 비트 부동 소수점 Float32Double임에도 불구하고 4 바이트 / 32 비트 IEEE 754 단 정밀도 부동 소수점 수인 유형으로 전송됨을 제안했습니다 Number.

콘솔에서 다음 코드를 실행하여 위를 확인하십시오.

msgpack5().encode(Number(0.005))

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

mspack5 는 64 비트 부동 소수점을 강제하는 옵션을 제공합니다.

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

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

그러나 signalr-protocol-msgpack은이forceFloat64 옵션을 사용하지 않습니다 .

그것은 왜 float서버 측에서 작동 하는지 설명 하지만, 현재로서는 그에 대한 해결책이 없습니다 . 마이크로 소프트의 말을 기다리 자 .

가능한 해결 방법

  • msgpack5 옵션을 해킹 하시겠습니까? 포크로 자신의 msgpack5를 forceFloat64기본값으로 true로 컴파일하십시오 ?? 모르겠어요
  • float서버 측으로 전환
  • 사용하여 string양쪽에
  • decimal서버 측으로 전환하고 custom을 작성하십시오 IFormatterProvider. decimal기본 유형이 아니며 IFormatterProvider<decimal>복합 유형 특성에 대해 호출됩니다.
  • double속성 값 을 검색 하고 double-> float-> decimal-> double트릭을 수행하는 방법을 제공하십시오.
  • 생각할 수있는 다른 비현실적인 솔루션

TL; DR

JS 클라이언트가 단일 부동 소수점 숫자를 C # 백엔드로 보내는 문제로 인해 알려진 부동 소수점 문제가 발생합니다.

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

doublein 메소드 를 직접 사용 하는 경우 사용자 지정으로 문제를 해결할 수 있습니다 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;
    }
}

그리고 리졸버를 사용하십시오 :

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

decimal다음 으로 캐스팅 double하면 프로세스 속도가 느려지고 위험 할 수 있으므로 리졸버는 완벽하지 않습니다 .

하나

주석에서 지적한 OP에 따라 속성 을 반환 하는 복잡한 유형을 사용하는 경우 문제를 해결할 수 없습니다double .

추가 조사 결과 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;
    }
  }
}

위의 디코더는 단일 float숫자를 double다음 으로 변환해야 할 때 사용됩니다 .

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

v2

이 문제는 v2 버전의 MessagePack-CSharp에 존재합니다. 내가 제기 한 GitHub의에 문제를 , 문제가 해결 될 것 아니지만 .


답변

더 정확한 값을 보내려면 정확한 값을 확인하십시오. 언어는 일반적으로 인쇄물의 정밀도를 제한하여보기에 좋습니다.

var n = Number(0.005);
console.log(n);
0.005
console.log(n.toPrecision(100));
0.00500000000000000010408340855860842566471546888351440429687500000000...


답변