जावास्क्रिप्ट से सी # न्यूमेरिक प्रेसिजन लॉस


16

जब संदेशवाहक के साथ सिग्नलआर का उपयोग करते हुए जावास्क्रिप्ट और सी # के बीच मूल्यों को क्रमबद्ध और डिसेररलाइज़ करते हुए मैं प्राप्त अंत पर सी # में सटीक नुकसान का एक सा देख रहा हूं।

एक उदाहरण के रूप में मैं जावास्क्रिप्ट से C # के लिए 0.005 मान भेज रहा हूं। जब deserialized मूल्य C # तरफ दिखाई देता है तो मुझे वह मूल्य मिल रहा है 0.004999999888241291, जो करीब है, लेकिन 0.005 बिल्कुल नहीं। जावास्क्रिप्ट पक्ष पर मान है Numberऔर मैं जिस C # पक्ष का उपयोग कर रहा हूं double

मैंने पढ़ा है कि जावास्क्रिप्ट फ्लोटिंग पॉइंट नंबरों का प्रतिनिधित्व नहीं कर सकता है जिससे परिणाम जैसे हो सकते हैं 0.1 + 0.2 == 0.30000000000000004। मुझे संदेह है कि मैं जो समस्या देख रहा हूं वह जावास्क्रिप्ट की इस विशेषता से संबंधित है।

दिलचस्प बात यह है कि मैं उसी मुद्दे को दूसरे तरीके से नहीं देख रहा हूं। जावास्क्रिप्ट में 0.005 को C # से जावास्क्रिप्ट में परिणाम 0.005 पर भेजा जा रहा है।

संपादित करें : C # से मान केवल JS डीबगर विंडो में छोटा किया गया है। जैसा कि @Pete ने उल्लेख किया है कि यह कुछ विस्तार करता है जो 0.5 बिल्कुल नहीं है (0.005000000000000000104083408558)। इसका मतलब यह है कि विसंगति कम से कम दोनों तरफ होती है।

JSON सीरियलाइज़ेशन के पास एक ही मुद्दा नहीं है क्योंकि मैं यह मान रहा हूं कि यह स्ट्रिंग के माध्यम से जाता है जो प्राप्त वातावरण को नियंत्रण में छोड़ देता है, मूल्य को अपने मूल संख्यात्मक प्रकार में पार्स करता है।

मैं सोच रहा हूं कि क्या बाइनरी सीरियलाइजेशन का उपयोग करने का एक तरीका है, जिसमें दोनों तरफ मिलान मूल्य हैं।

यदि नहीं, तो क्या इसका मतलब यह है कि जावास्क्रिप्ट और सी # के बीच 100% सटीक द्विआधारी रूपांतरण का कोई रास्ता नहीं है?

प्रौद्योगिकी का इस्तेमाल किया:

  • जावास्क्रिप्ट
  • .Net Core के साथ SignalR और msgpack5

मेरा कोड इस पोस्ट पर आधारित है । फर्क सिर्फ इतना है कि मैं उपयोग कर रहा हूं ContractlessStandardResolver.Instance


C # में फ्लोटिंग पॉइंट प्रतिनिधित्व हर मूल्य के लिए भी सटीक नहीं है। क्रमबद्ध डेटा पर एक नज़र है। आप इसे C # में पार्स कैसे करते हैं?
जेफ़रसन

C # में आप किस प्रकार का उपयोग करते हैं? डबल इस तरह के मुद्दे के लिए जाना जाता है।
पौल बक

मैं बिल्ट इन मैसेज पैक सीरियलाइज़ेशन / डिसेरिएलाइज़ेशन का उपयोग करता हूं जो सिग्नल के साथ आता है और यह संदेश पैक एकीकरण है।
टीजीएच

फ़्लोटिंग पॉइंट मान कभी सटीक नहीं होते हैं। यदि आपको सटीक मानों की आवश्यकता है, तो स्ट्रिंग्स (स्वरूपण समस्या) या पूर्णांक (जैसे 1000 से गुणा करके) का उपयोग करें।
atmin

क्या आप deserialized संदेश की जांच कर सकते हैं? C # से पहले जो टेक्स्ट आपको मिला है, वह किसी ऑब्जेक्ट में कनवर्ट करता है।
जॉनी पियाज़ी

जवाबों:


9

अपडेट करें

यह अगली रिलीज में तय किया गया है (5.0.0-प्रीव्यू 4)

मूल उत्तर

मैंने इस विशेष मामले में floatऔर double, और दिलचस्प रूप से परीक्षण किया , केवल doubleसमस्या थी, जबकिfloat काम करने (यानी 0.005 सर्वर पर पढ़ा जाता है) लगता है।

संदेश बाइट्स पर निरीक्षण ने सुझाव दिया कि 0.005 को टाइप के रूप में भेजा जाता है Float32Doubleजो 4-बाइट / 32-बिट IEEE 754 एकल परिशुद्धता फ़्लोटिंग पॉइंट नंबर के बावजूद Number64 बिट फ़्लोटिंग पॉइंट है।

कंसोल में निम्न कोड चलाएँ उपरोक्त पुष्टि की:

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]

हालाँकि, सिग्नल-प्रोटोकॉल-msgpackforceFloat64 द्वारा विकल्प का उपयोग नहीं किया जाता है

हालांकि यह बताता है कि floatसर्वर की तरफ काम क्यों होता है , लेकिन वास्तव में इसके लिए कोई फिक्स नहीं हैMicrosoft क्या कहता है प्रतीक्षा करें

संभावित वर्कअराउंड

  • Mspack5 विकल्प हैक करें? कांटा और अपना खुद का msgpack5 संकलित करेंforceFloat64 डिफ़ॉल्ट के सच ?? मुझे नहीं पता।
  • पर स्विच floatसर्वर साइड पर
  • उपयोग stringदोनों तरफ का
  • पर स्विच decimalसर्वर साइड और लिखने कस्टम पर IFormatterProviderdecimalआदिम प्रकार नहीं है, और IFormatterProvider<decimal>जटिल प्रकार के गुणों के लिए कहा जाता है
  • doubleसंपत्ति मूल्य प्राप्त करने के लिए विधि प्रदान करें और double-> float-> करेंdecimal -> doubleचाल करें
  • अन्य अवास्तविक समाधान आप सोच सकते हैं

टी एल; डॉ

जेएस क्लाइंट के साथ सी # बैकएंड पर सिंगल फ्लोटिंग पॉइंट नंबर भेजने की समस्या एक फ्लोटिंग पॉइंट इश्यू का कारण बनती है:

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

doubleतरीकों के प्रत्यक्ष उपयोग के लिए, समस्या को एक कस्टम द्वारा हल किया जा सकता है 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प्रक्रिया को धीमा करने के लिए कास्टिंग करना और यह खतरनाक हो सकता है

तथापि

जैसा कि ओपी ने टिप्पणियों में बताया, यह जटिल प्रकारों का उपयोग करते हुए समस्या को हल नहीं कर सकता है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;

वी 2

यह समस्या MessagePack-CSharp के v2 संस्करणों में मौजूद है। मैंने गितुब पर एक मुद्दा दायर किया है , हालांकि यह मुद्दा तय नहीं होने जा रहा है


दिलचस्प निष्कर्ष। यहां एक चुनौती यह है कि यह मुद्दा किसी भी जटिल वस्तु पर किसी भी दोहरे गुण के लिए लागू होता है, इसलिए मुझे लगता है कि सीधे दोहरे को लक्षित करना मुश्किल होगा।
टीजीएच

@TGH हाँ, आप सही कह रहे हैं। मेरा मानना ​​है कि यह MessagePack-CSharp में एक बग है। विवरण के लिए मेरा अद्यतन देखें। अभी के लिए, आपको floatवर्कअराउंड के रूप में उपयोग करने की आवश्यकता हो सकती है । मुझे नहीं पता कि उन्होंने v2 में यह तय किया है या नहीं। एक बार कुछ समय होने के बाद मैं देख लूंगा। हालाँकि, समस्या v2 अभी तक सिग्नलआर के साथ संगत नहीं है। सिग्नलआर के केवल पूर्वावलोकन संस्करण (5.0.0.0- *) v2 का उपयोग कर सकते हैं।
weichch

यह v2 में भी काम नहीं कर रहा है। मैंने MessagePack-CSharp के साथ एक बग उठाया है।
weichch

@TGH दुर्भाग्य से गितुब मुद्दे में चर्चा के अनुसार सर्वर की तरफ कोई फिक्स नहीं है। सबसे अच्छा फिक्स 32 बिट्स के बजाय 64 बिट्स भेजने के लिए क्लाइंट का पक्ष लेना होगा। मैंने देखा कि ऐसा होने के लिए मजबूर करने का एक विकल्प है, लेकिन Microsoft उस (मेरी समझ से) उजागर नहीं करता है। यदि आप एक नज़र रखना चाहते हैं तो बस कुछ गंदे वर्कअराउंड के साथ अद्यतन उत्तर। और इस मुद्दे पर शुभकामनाएँ।
वीच

यह एक दिलचस्प लीड की तरह लगता है। मैं उस पर एक नज़र डालूंगा। इसके साथ आपकी मदद का शुक्रिया!
टीजीएच

14

कृपया सटीक मूल्य की जाँच करें जिसे आप बड़ी सटीकता के लिए भेज रहे हैं। भाषा आम तौर पर प्रिंट पर सटीक को बेहतर बनाने के लिए सीमित करती है।

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

हां, आप इसके बारे में सही हैं।
टीजीएच
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.