স্ট্রিংয়ের মাধ্যমে রাউন্ড-ট্রিপ রূপান্তর কেন ডাবল জন্য নিরাপদ নয়?


185

সম্প্রতি আমাকে পাঠ্যে একটি ডাবল সিরিয়ালিয়াস করতে হয়েছিল এবং তারপরে এটি ফিরে পেতে। মানটি সমতুল্য বলে মনে হচ্ছে না:

double d1 = 0.84551240822557006;
string s = d1.ToString("R");
double d2 = double.Parse(s);
bool s1 = d1 == d2;
// -> s1 is False

তবে এমএসডিএন: স্ট্যান্ডার্ড সংখ্যাসূচক ফর্ম্যাট স্ট্রিংস অনুসারে , "আর" বিকল্পটি রাউন্ড ট্রিপ নিরাপত্তার গ্যারান্টি দেবে বলে মনে করা হচ্ছে।

রাউন্ড ট্রিপ ("আর") ফর্ম্যাট স্পেসিফায়ারটি নিশ্চিত করা যায় যে একটি সংখ্যার মান যা একটি স্ট্রিংয়ে রূপান্তরিত হয় আবার একই সংখ্যার মানতে পার্স হবে

এটা কেন হল?


6
আমি আমার ভিএস এবং এটির প্রত্যাবর্তনের সত্যটি এখানে ডিবাগ করেছি
নীল

19
আমি এটি মিথ্যা প্রত্যাবর্তন পুনরুত্পাদন করেছি। খুব মজার প্রশ্ন।
জন স্কিটি

40
.net 4.0 x86 - সত্য
,।

25
নেট। এ যেমন একটি চিত্তাকর্ষক বাগ সন্ধানের জন্য অভিনন্দন।
অ্যারন

14
@ ক্যাস্পেরাহ রাউন্ড ট্রিপটি বিশেষত ভাসমান পয়েন্টের অসঙ্গতি এড়াতে বোঝানো হয়েছে
গুডডর

উত্তর:


178

আমি বাগ খুঁজে পেয়েছি।

.NET নিম্নলিখিত ইন clr\src\vm\comnumber.cpp:

DoubleToNumber(value, DOUBLE_PRECISION, &number);

if (number.scale == (int) SCALE_NAN) {
    gc.refRetVal = gc.numfmt->sNaN;
    goto lExit;
}

if (number.scale == SCALE_INF) {
    gc.refRetVal = (number.sign? gc.numfmt->sNegativeInfinity: gc.numfmt->sPositiveInfinity);
    goto lExit;
}

NumberToDouble(&number, &dTest);

if (dTest == value) {
    gc.refRetVal = NumberToString(&number, 'G', DOUBLE_PRECISION, gc.numfmt);
    goto lExit;
}

DoubleToNumber(value, 17, &number);

DoubleToNumberবেশ সহজ - এটি কেবল কল করে _ecvt, যা সি রানটাইমের মধ্যে রয়েছে:

void DoubleToNumber(double value, int precision, NUMBER* number)
{
    WRAPPER_CONTRACT
    _ASSERTE(number != NULL);

    number->precision = precision;
    if (((FPDOUBLE*)&value)->exp == 0x7FF) {
        number->scale = (((FPDOUBLE*)&value)->mantLo || ((FPDOUBLE*)&value)->mantHi) ? SCALE_NAN: SCALE_INF;
        number->sign = ((FPDOUBLE*)&value)->sign;
        number->digits[0] = 0;
    }
    else {
        char* src = _ecvt(value, precision, &number->scale, &number->sign);
        wchar* dst = number->digits;
        if (*src != '0') {
            while (*src) *dst++ = *src++;
        }
        *dst = 0;
    }
}

দেখা যাচ্ছে যে _ecvtস্ট্রিংটি ফিরে আসে 845512408225570

শূন্যের পিছনে লক্ষ্য করবেন? দেখা যাচ্ছে যে সমস্ত পার্থক্য!
যখন শূন্য উপস্থিত থাকে, ফলাফলটি প্রকৃতপক্ষে ফিরে আসে0.84551240822557006, যা আপনার আসল সংখ্যা - সুতরাং এটি সমান তুলনা করে, এবং তাই কেবল 15 টি সংখ্যা ফিরে আসে।

যাইহোক, যদি আমি সেই শূন্য থেকে স্ট্রিংটি কেটে ফেলেছি 84551240822557তবে আমি ফিরে আসব 0.84551240822556994, এটি আপনার মূল সংখ্যা নয় এবং তাই এটি 17 টি সংখ্যা ফিরে আসবে।

প্রুফ: আপনার ডিবাগারে নিম্নলিখিত -৪-বিট কোডটি চালান (যার বেশিরভাগটি আমি মাইক্রোসফ্ট শেয়ার্ড সোর্স সিএলআই 2.0 থেকে বের করেছি) এবং vশেষে পরীক্ষা করে দেখুন main:

#include <stdlib.h>
#include <string.h>
#include <math.h>

#define min(a, b) (((a) < (b)) ? (a) : (b))

struct NUMBER {
    int precision;
    int scale;
    int sign;
    wchar_t digits[20 + 1];
    NUMBER() : precision(0), scale(0), sign(0) {}
};


#define I64(x) x##LL
static const unsigned long long rgval64Power10[] = {
    // powers of 10
    /*1*/ I64(0xa000000000000000),
    /*2*/ I64(0xc800000000000000),
    /*3*/ I64(0xfa00000000000000),
    /*4*/ I64(0x9c40000000000000),
    /*5*/ I64(0xc350000000000000),
    /*6*/ I64(0xf424000000000000),
    /*7*/ I64(0x9896800000000000),
    /*8*/ I64(0xbebc200000000000),
    /*9*/ I64(0xee6b280000000000),
    /*10*/ I64(0x9502f90000000000),
    /*11*/ I64(0xba43b74000000000),
    /*12*/ I64(0xe8d4a51000000000),
    /*13*/ I64(0x9184e72a00000000),
    /*14*/ I64(0xb5e620f480000000),
    /*15*/ I64(0xe35fa931a0000000),

    // powers of 0.1
    /*1*/ I64(0xcccccccccccccccd),
    /*2*/ I64(0xa3d70a3d70a3d70b),
    /*3*/ I64(0x83126e978d4fdf3c),
    /*4*/ I64(0xd1b71758e219652e),
    /*5*/ I64(0xa7c5ac471b478425),
    /*6*/ I64(0x8637bd05af6c69b7),
    /*7*/ I64(0xd6bf94d5e57a42be),
    /*8*/ I64(0xabcc77118461ceff),
    /*9*/ I64(0x89705f4136b4a599),
    /*10*/ I64(0xdbe6fecebdedd5c2),
    /*11*/ I64(0xafebff0bcb24ab02),
    /*12*/ I64(0x8cbccc096f5088cf),
    /*13*/ I64(0xe12e13424bb40e18),
    /*14*/ I64(0xb424dc35095cd813),
    /*15*/ I64(0x901d7cf73ab0acdc),
};

static const signed char rgexp64Power10[] = {
    // exponents for both powers of 10 and 0.1
    /*1*/ 4,
    /*2*/ 7,
    /*3*/ 10,
    /*4*/ 14,
    /*5*/ 17,
    /*6*/ 20,
    /*7*/ 24,
    /*8*/ 27,
    /*9*/ 30,
    /*10*/ 34,
    /*11*/ 37,
    /*12*/ 40,
    /*13*/ 44,
    /*14*/ 47,
    /*15*/ 50,
};

static const unsigned long long rgval64Power10By16[] = {
    // powers of 10^16
    /*1*/ I64(0x8e1bc9bf04000000),
    /*2*/ I64(0x9dc5ada82b70b59e),
    /*3*/ I64(0xaf298d050e4395d6),
    /*4*/ I64(0xc2781f49ffcfa6d4),
    /*5*/ I64(0xd7e77a8f87daf7fa),
    /*6*/ I64(0xefb3ab16c59b14a0),
    /*7*/ I64(0x850fadc09923329c),
    /*8*/ I64(0x93ba47c980e98cde),
    /*9*/ I64(0xa402b9c5a8d3a6e6),
    /*10*/ I64(0xb616a12b7fe617a8),
    /*11*/ I64(0xca28a291859bbf90),
    /*12*/ I64(0xe070f78d39275566),
    /*13*/ I64(0xf92e0c3537826140),
    /*14*/ I64(0x8a5296ffe33cc92c),
    /*15*/ I64(0x9991a6f3d6bf1762),
    /*16*/ I64(0xaa7eebfb9df9de8a),
    /*17*/ I64(0xbd49d14aa79dbc7e),
    /*18*/ I64(0xd226fc195c6a2f88),
    /*19*/ I64(0xe950df20247c83f8),
    /*20*/ I64(0x81842f29f2cce373),
    /*21*/ I64(0x8fcac257558ee4e2),

    // powers of 0.1^16
    /*1*/ I64(0xe69594bec44de160),
    /*2*/ I64(0xcfb11ead453994c3),
    /*3*/ I64(0xbb127c53b17ec165),
    /*4*/ I64(0xa87fea27a539e9b3),
    /*5*/ I64(0x97c560ba6b0919b5),
    /*6*/ I64(0x88b402f7fd7553ab),
    /*7*/ I64(0xf64335bcf065d3a0),
    /*8*/ I64(0xddd0467c64bce4c4),
    /*9*/ I64(0xc7caba6e7c5382ed),
    /*10*/ I64(0xb3f4e093db73a0b7),
    /*11*/ I64(0xa21727db38cb0053),
    /*12*/ I64(0x91ff83775423cc29),
    /*13*/ I64(0x8380dea93da4bc82),
    /*14*/ I64(0xece53cec4a314f00),
    /*15*/ I64(0xd5605fcdcf32e217),
    /*16*/ I64(0xc0314325637a1978),
    /*17*/ I64(0xad1c8eab5ee43ba2),
    /*18*/ I64(0x9becce62836ac5b0),
    /*19*/ I64(0x8c71dcd9ba0b495c),
    /*20*/ I64(0xfd00b89747823938),
    /*21*/ I64(0xe3e27a444d8d991a),
};

static const signed short rgexp64Power10By16[] = {
    // exponents for both powers of 10^16 and 0.1^16
    /*1*/ 54,
    /*2*/ 107,
    /*3*/ 160,
    /*4*/ 213,
    /*5*/ 266,
    /*6*/ 319,
    /*7*/ 373,
    /*8*/ 426,
    /*9*/ 479,
    /*10*/ 532,
    /*11*/ 585,
    /*12*/ 638,
    /*13*/ 691,
    /*14*/ 745,
    /*15*/ 798,
    /*16*/ 851,
    /*17*/ 904,
    /*18*/ 957,
    /*19*/ 1010,
    /*20*/ 1064,
    /*21*/ 1117,
};

static unsigned DigitsToInt(wchar_t* p, int count)
{
    wchar_t* end = p + count;
    unsigned res = *p - '0';
    for ( p = p + 1; p < end; p++) {
        res = 10 * res + *p - '0';
    }
    return res;
}
#define Mul32x32To64(a, b) ((unsigned long long)((unsigned long)(a)) * (unsigned long long)((unsigned long)(b)))

static unsigned long long Mul64Lossy(unsigned long long a, unsigned long long b, int* pexp)
{
    // it's ok to losse some precision here - Mul64 will be called
    // at most twice during the conversion, so the error won't propagate
    // to any of the 53 significant bits of the result
    unsigned long long val = Mul32x32To64(a >> 32, b >> 32) +
        (Mul32x32To64(a >> 32, b) >> 32) +
        (Mul32x32To64(a, b >> 32) >> 32);

    // normalize
    if ((val & I64(0x8000000000000000)) == 0) { val <<= 1; *pexp -= 1; }

    return val;
}

void NumberToDouble(NUMBER* number, double* value)
{
    unsigned long long val;
    int exp;
    wchar_t* src = number->digits;
    int remaining;
    int total;
    int count;
    int scale;
    int absscale;
    int index;

    total = (int)wcslen(src);
    remaining = total;

    // skip the leading zeros
    while (*src == '0') {
        remaining--;
        src++;
    }

    if (remaining == 0) {
        *value = 0;
        goto done;
    }

    count = min(remaining, 9);
    remaining -= count;
    val = DigitsToInt(src, count);

    if (remaining > 0) {
        count = min(remaining, 9);
        remaining -= count;

        // get the denormalized power of 10
        unsigned long mult = (unsigned long)(rgval64Power10[count-1] >> (64 - rgexp64Power10[count-1]));
        val = Mul32x32To64(val, mult) + DigitsToInt(src+9, count);
    }

    scale = number->scale - (total - remaining);
    absscale = abs(scale);
    if (absscale >= 22 * 16) {
        // overflow / underflow
        *(unsigned long long*)value = (scale > 0) ? I64(0x7FF0000000000000) : 0;
        goto done;
    }

    exp = 64;

    // normalize the mantisa
    if ((val & I64(0xFFFFFFFF00000000)) == 0) { val <<= 32; exp -= 32; }
    if ((val & I64(0xFFFF000000000000)) == 0) { val <<= 16; exp -= 16; }
    if ((val & I64(0xFF00000000000000)) == 0) { val <<= 8; exp -= 8; }
    if ((val & I64(0xF000000000000000)) == 0) { val <<= 4; exp -= 4; }
    if ((val & I64(0xC000000000000000)) == 0) { val <<= 2; exp -= 2; }
    if ((val & I64(0x8000000000000000)) == 0) { val <<= 1; exp -= 1; }

    index = absscale & 15;
    if (index) {
        int multexp = rgexp64Power10[index-1];
        // the exponents are shared between the inverted and regular table
        exp += (scale < 0) ? (-multexp + 1) : multexp;

        unsigned long long multval = rgval64Power10[index + ((scale < 0) ? 15 : 0) - 1];
        val = Mul64Lossy(val, multval, &exp);
    }

    index = absscale >> 4;
    if (index) {
        int multexp = rgexp64Power10By16[index-1];
        // the exponents are shared between the inverted and regular table
        exp += (scale < 0) ? (-multexp + 1) : multexp;

        unsigned long long multval = rgval64Power10By16[index + ((scale < 0) ? 21 : 0) - 1];
        val = Mul64Lossy(val, multval, &exp);
    }

    // round & scale down
    if ((unsigned long)val & (1 << 10))
    {
        // IEEE round to even
        unsigned long long tmp = val + ((1 << 10) - 1) + (((unsigned long)val >> 11) & 1);
        if (tmp < val) {
            // overflow
            tmp = (tmp >> 1) | I64(0x8000000000000000);
            exp += 1;
        }
        val = tmp;
    }
    val >>= 11;

    exp += 0x3FE;

    if (exp <= 0) {
        if (exp <= -52) {
            // underflow
            val = 0;
        }
        else {
            // denormalized
            val >>= (-exp+1);
        }
    }
    else
        if (exp >= 0x7FF) {
            // overflow
            val = I64(0x7FF0000000000000);
        }
        else {
            val = ((unsigned long long)exp << 52) + (val & I64(0x000FFFFFFFFFFFFF));
        }

        *(unsigned long long*)value = val;

done:
        if (number->sign) *(unsigned long long*)value |= I64(0x8000000000000000);
}

int main()
{
    NUMBER number;
    number.precision = 15;
    double v = 0.84551240822557006;
    char *src = _ecvt(v, number.precision, &number.scale, &number.sign);
    int truncate = 0;  // change to 1 if you want to truncate
    if (truncate)
    {
        while (*src && src[strlen(src) - 1] == '0')
        {
            src[strlen(src) - 1] = 0;
        }
    }
    wchar_t* dst = number.digits;
    if (*src != '0') {
        while (*src) *dst++ = *src++;
    }
    *dst++ = 0;
    NumberToDouble(&number, &v);
    return 0;
}

4
ভাল ব্যাখ্যা +1। এই কোডটি শেয়ারড-উত্স-ক্লাই -২.০ থেকে সঠিক? এটিই আমি খুঁজে পেয়েছি think
সোনার গনল

10
আমি অবশ্যই বলার অপেক্ষা রাখে না। স্ট্রিংগুলি গাণিতিকভাবে সমান (যেমন চলমান শূন্যের মতো, বা বলুন যে ২.১ ই -১ বনাম ০.২১) সবসময় অভিন্ন ফলাফল দেওয়া উচিত, এবং গাণিতিকভাবে নির্দেশিত স্ট্রিংগুলি অর্ডারের সাথে সামঞ্জস্যপূর্ণ ফলাফল দেওয়া উচিত।
gnasher729

4
@ মিঃলিস্টার: কেন "২.১ ই -১২.২০ এর মতো হওয়া উচিত না"?
ব্যবহারকারী541686

9
@ gnasher729: আমি কিছুটা "2.1e-1" এবং "0.21" এর সাথে একমত হব ... তবে পিছনের শূন্যের সাথে একটি স্ট্রিং ছাড়াই একের সমান নয় - পূর্ববর্তী ক্ষেত্রে শূন্য একটি উল্লেখযোগ্য অঙ্ক এবং যুক্ত হয় স্পষ্টতা।
সিএইচও

4
@ সিএইচও: এর ... এটি স্পষ্টতা যুক্ত করেছে, তবে সিগফিগগুলি আপনার বিবেচনায় রাখলে আপনি চূড়ান্ত উত্তরটি কীভাবে সিদ্ধান্ত নেবেন তা কেবল তার উপর নির্ভর করে, কম্পিউটারকে প্রথম স্থানে চূড়ান্ত উত্তরটি কীভাবে গণনা করা উচিত তা নয়। কম্পিউটারের কাজ হ'ল সংখ্যার প্রকৃত পরিমাপ নির্ভুলতা নির্বিশেষে সর্বোচ্চ নির্ভুলতায় সমস্ত কিছু গণনা করা ; চূড়ান্ত ফলাফলটি গোল করতে চাইলে প্রোগ্রামারের সমস্যা।
ব্যবহারকারী541686

107

আমার কাছে মনে হচ্ছে এটি কেবল একটি বাগ। আপনার প্রত্যাশা সম্পূর্ণ যুক্তিসঙ্গত। আমি এটি DoubleConverterক্লাস ব্যবহার করে যা নীচের কনসোল অ্যাপ্লিকেশন চালিয়ে .NET 4.5.1 (x64) ব্যবহার করে এটি পুনরুত্পাদন করেছি । একটি দ্বারা প্রতিনিধিত্ব করা সঠিক মানটি DoubleConverter.ToExactStringদেখায় :double

using System;

class Test
{
    static void Main()
    {
        double d1 = 0.84551240822557006;
        string s = d1.ToString("r");
        double d2 = double.Parse(s);
        Console.WriteLine(s);
        Console.WriteLine(DoubleConverter.ToExactString(d1));
        Console.WriteLine(DoubleConverter.ToExactString(d2));
        Console.WriteLine(d1 == d2);
    }
}

নেট মধ্যে ফলাফল:

0.84551240822557
0.845512408225570055719799711368978023529052734375
0.84551240822556994469749724885332398116588592529296875
False

মনো 3.3.0 এ ফলাফল:

0.84551240822557006
0.845512408225570055719799711368978023529052734375
0.845512408225570055719799711368978023529052734375
True

আপনি যদি মনো থেকে ম্যানুয়ালি স্ট্রিংটি নির্দিষ্ট করে থাকেন (যার শেষে "006" রয়েছে), নেট এটি মূল মানটিতে ফিরে যায় back দেখে মনে হচ্ছে সমস্যাটি ToString("R")পার্সিংয়ের পরিবর্তে হ্যান্ডলিংয়ে রয়েছে।

অন্যান্য মন্তব্যে উল্লিখিত হিসাবে, দেখে মনে হচ্ছে এটি x64 সিএলআর এর অধীনে চলার জন্য নির্দিষ্ট। আপনি যদি উপরের কোডটি x86 টি লক্ষ্য করে সংকলন এবং চালনা করেন তবে এটি ঠিক আছে:

csc /platform:x86 Test.cs DoubleConverter.cs

... আপনি মনোর সাথে একই ফলাফল পান। রিউজিআইটি-র অধীনে বাগটি প্রদর্শিত হচ্ছে কিনা তা জেনে রাখা আকর্ষণীয় হবে - এই মুহূর্তে আমার নিজের ইনস্টল নেই। বিশেষত, আমি এটি সম্ভবত একটি জেআইটি বাগ হিসাবে ধারণা করতে পারি , বা এটি সম্ভব যে double.ToStringআর্কিটেকচারের ভিত্তিতে ইন্টার্নালগুলির সম্পূর্ণ ভিন্ন বাস্তবায়ন রয়েছে ।

আমি আপনাকে পরামর্শ দিচ্ছি যে আপনি http://connect.microsoft.com এ একটি বাগ ফাইল করুন


1
তাহলে জন? নিশ্চিত করার জন্য, এটি কি জিআইটি-তে ইনলাইন করে কোনও বাগ আছে ToString()? যেহেতু আমি হার্ড কোডিং মানটি প্রতিস্থাপন করার চেষ্টা করেছি rand.NextDouble()এবং কোনও সমস্যা নেই।
আরন

1
হ্যাঁ, এটি অবশ্যই ToString("R")রূপান্তর in চেষ্টা করুন ToString("G32")এবং লক্ষ্য করুন এটি সঠিক মানটি প্রিন্ট করে।
ব্যবহারকারী541686

1
@ অ্যারন: আমি এটি বলতে পারব না এটি জাইটারে কোনও বাগ আছে বা ছাত্রলীগের একটি এক্স 6464-নির্দিষ্ট প্রয়োগে। আমি খুব সন্দেহ করি যে এটি ইনলাইনিংয়ের মতোই সহজ। আইএমও ... এলোমেলো মানগুলির সাথে পরীক্ষা করা সত্যিকার অর্থে খুব বেশি সহায়ক হয় না you
জন স্কিটি

2
আমার মনে হয় যা ঘটছে তা হ'ল "রাউন্ড ট্রিপ" ফর্ম্যাটটি এমন একটি মানের আউটপুট দিচ্ছে যা 0.498ulp এর চেয়ে বড় আকারের হতে পারে এবং যুক্তিকে পার্সিং করে মাঝে মাঝে ভুল করে ভুল করে শেষ করে দেয় একটি ছোট ছোট অংশটিকে। আমি কোন কোডটিকে বেশি দোষ দিচ্ছি তা নিশ্চিত নই, যেহেতু আমি মনে করি যে "রাউন্ড-ট্রিপ" ফর্ম্যাটটিতে একটি সংখ্যাসূচক মান আউটপুট করা উচিত যা সংখ্যার দিক থেকে সঠিক হওয়ার চতুর্থাংশ-ইউএলপি-র মধ্যে থাকে; লজিককে পার্সিং যা নির্দিষ্ট করে তার 0.75ulp এর মধ্যে একটি মান দেয় যা যুক্তিযুক্ত তুলনায় অনেক সহজ যা নির্দিষ্ট করে আছে তার 0.502ulp এর মধ্যে একটি ফল দিতে হবে।
সুপারক্যাট

1
জন স্কিটির ওয়েবসাইট ডাউন? আমি দেখতে পাচ্ছি যে আমি এতটা অসম্ভব ... এখানে সমস্ত বিশ্বাস হারিয়ে ফেলছি।
প্যাট্রিক এম

2

সম্প্রতি, আমি এই সমস্যাটি সমাধান করার চেষ্টা করছি । কোডটির মাধ্যমে নির্দেশিত হিসাবে , ডাবল। টুস্ট্রিং ("আর") এর নিম্নলিখিত যুক্তি রয়েছে:

  1. ডাবলকে স্ট্রিংয়ে 15 এর যথার্থ হিসাবে রূপান্তর করার চেষ্টা করুন।
  2. স্ট্রিংটিকে ডাবলে রূপান্তর করুন এবং মূল ডাবলের সাথে তুলনা করুন। যদি সেগুলি একই হয় তবে আমরা রূপান্তরিত স্ট্রিংটি ফিরিয়ে দেব যার যথার্থতা 15।
  3. অন্যথায়, 17 এর যথার্থে ডাবলকে স্ট্রিংয়ে রূপান্তর করুন।

এই ক্ষেত্রে ডাবল। টুস্ট্রিং ("আর") ভুলভাবে 15 এর যথার্থতার জন্য ফলাফলটি বেছে নিয়েছে যাতে বাগটি ঘটে। এমএসডিএন ডকের একটি অফিসিয়াল কাজ রয়েছে:

কিছু ক্ষেত্রে, "আর" স্ট্যান্ডার্ড সংখ্যাসূচক ফরম্যাটের স্ট্রিংয়ের সাথে ফর্ম্যাট করা ডাবল মানগুলি / প্ল্যাটফর্ম: x64 বা / প্ল্যাটফর্ম: যে কোনওcpu স্যুইচ করে 64-বিট সিস্টেমে চালিত হয় তা সফলভাবে রাউন্ড ট্রিপ করে না। এই সমস্যাটি সমাধান করার জন্য, আপনি "G17" স্ট্যান্ডার্ড সংখ্যার বিন্যাসের স্ট্রিংটি ব্যবহার করে ডাবল মানগুলিকে ফর্ম্যাট করতে পারেন। নিম্নলিখিত উদাহরণটিতে ডাবল মান সহ "আর" ফর্ম্যাট স্ট্রিং ব্যবহার করা হয়েছে যা সফলভাবে রাউন্ড ট্রিপ করে না এবং মূল মানটিকে সফলভাবে রাউন্ড-ট্রিপ করতে "G17" ফর্ম্যাট স্ট্রিংটিও ব্যবহার করে।

সুতরাং এই সমস্যাটি সমাধান না হওয়া পর্যন্ত আপনাকে রাউন্ড-ট্রিপিংয়ের জন্য ডাবল.টোস্ট্রিং ("G17") ব্যবহার করতে হবে।

আপডেট : এখন এই বাগটি ট্র্যাক করার জন্য একটি নির্দিষ্ট সমস্যা আছে।

আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.