简体   繁体   中英

Get float constant to be the same the runtime result (and VB.NET)

I tried the equivalent of Michael Meadows EDIT 2 , but in VB.NET and got a different result. (Specifically both the Double and Decimal results were 600000.0238418580.)

I have determined the difference is with the compile-time accuracy of a float ( Single ) division stored into a float in C#, (which seems to be more equivalent to VB.NET's accuracy when storing into a Double ) and what happens (in both languages unsurprisingly) when you force the division to occur at runtime.

So, THREE_FIFTHS and vTHREE_FIFTHS provide different results for the asDouble summation:

const int ONE_MILLION = 1000000;

float THREEsng = 3f;
float FIVEsng = 5f;
float vTHREE_FIFTHS = THREEsng / FIVEsng;

const float THREE_FIFTHS = 3f / 5f;

Console.WriteLine("Three Fifths: {0}", THREE_FIFTHS.ToString("F10"));
float asSingle = 0f;
double asDouble = 0d;
decimal asDecimal = 0M;

for (int i = 0; i < ONE_MILLION; i++)
{
    asSingle += (float) THREE_FIFTHS;
    asDouble += (double) THREE_FIFTHS;
    asDecimal += (decimal) THREE_FIFTHS;
}
Console.WriteLine("Six Hundred Thousand: {0:F10}", THREE_FIFTHS * ONE_MILLION);
Console.WriteLine("Single: {0}", asSingle.ToString("F10"));
Console.WriteLine("Double: {0}", asDouble.ToString("F10"));
Console.WriteLine("Decimal: {0}", asDecimal.ToString("F10"));

Console.WriteLine("vThree Fifths: {0}", vTHREE_FIFTHS.ToString("F10"));
asSingle = 0f;
asDouble = 0d;
asDecimal = 0M;

for (int i = 0; i < ONE_MILLION; i++)
{
    asSingle += (float) vTHREE_FIFTHS;
    asDouble += (double) vTHREE_FIFTHS;
    asDecimal += (decimal) vTHREE_FIFTHS;
}
Console.WriteLine("Six Hundred Thousand: {0:F10}", vTHREE_FIFTHS * ONE_MILLION);
Console.WriteLine("Single: {0}", asSingle.ToString("F10"));
Console.WriteLine("Double: {0}", asDouble.ToString("F10"));
Console.WriteLine("Decimal: {0}", asDecimal.ToString("F10"));

The result with the difference is: 的结果是:

Three Fifths: 0.6000000000
Six Hundred Thousand: 600000.0000000000
Single: 599093.4000000000

Decimal: 600000.0000000000
vThree Fifths: 0.6000000000
Six Hundred Thousand: 600000.0000000000
Single: 599093.4000000000

Decimal: 600000.0000000000

My question is, can you get C# to get a const float expression with the equivalent of the runtime (and VB.NET) result? (Ie produce a THREE_FIFTHS with the same results as vTHREE_FIFTHS .)

THREE_FIFTHS has the same value as vTHREE_FIFTHS in your example (you can see this with BitConverter.GetBytes ). It only differs in the way it is added to a double in your code.

I think your difference is due to the way that the C# compiler handles const s as if they were literals, in some ways. Eg this operation, though it's normally not allowed without a cast, is ok because the const lets the compiler see that the int is small enough to work out:

const int i = 5;
byte b = i;

In your case, this means that it doesn't add the single-precision value 3/5 to the double, but calculates the double value 3/5 and adds that. Unfortunately this extra "intelligence" has a side effect. You can get around it by storing the const float as a float first, eg:

float f = THREE_FIFTHS;
asDouble += (double) f;

With this, both ways calculate the double as 600000.0238418580 .

You can see more detail on the weirdness with these outputs:

string GetByteString(double d)
{
    return string.Join("", BitConverter.GetBytes(d).Select(b=>b.ToString("X2")));
}
string GetByteString(float f)
{
    return string.Join("", BitConverter.GetBytes(f).Select(b=>b.ToString("X2")));
}
double vd = vTHREE_FIFTHS;
double d = THREE_FIFTHS;
const double cd = THREE_FIFTHS;
float f = THREE_FIFTHS;
const double cd2 = 3d / 5d;
double d2 = 3d / 5d;
double df = f;

// doubles
Console.WriteLine(GetByteString((double)THREE_FIFTHS));
Console.WriteLine(GetByteString(vd));
Console.WriteLine(GetByteString(df));
Console.WriteLine(GetByteString(d));
Console.WriteLine(GetByteString(cd));
Console.WriteLine(GetByteString(cd2));
Console.WriteLine(GetByteString(d2));

// floats
Console.WriteLine(GetByteString(f));
Console.WriteLine(GetByteString(vTHREE_FIFTHS));
Console.WriteLine(GetByteString(THREE_FIFTHS));

prints this:
333333333333E33F
000000403333E33F <-- these are the only ones that were actually
000000403333E33F <-- converted from 32-bit float values to doubles
333333333333E33F
333333333333E33F
333333333333E33F
333333333333E33F
9A99193F
9A99193F
9A99193F

It does look like the answer is "it can't be done", because, as Tim S. pointed out, the compile-time constant is not really constant, but actually reinterpreted at each use. Specifically the (double) cast version of the constant produces a different result to the runtime result.

void Main()
{
        const int ONE_MILLION = 1000000;

        float THREEsng = 3f;
        float FIVEsng = 5f;
        float vTHREE_FIFTHS = THREEsng / FIVEsng;

        const float THREE_FIFTHS = 3f / 5f;

        Console.WriteLine("Three Fifths: {0}", THREE_FIFTHS.ToString("F10"));
        float asSingle = 0f;
        double asDouble = 0d;
        decimal asDecimal = 0M;

        for (int i = 0; i < ONE_MILLION; i++)
        {
            asSingle += (float) THREE_FIFTHS;
            asDouble += (double) THREE_FIFTHS;
            asDecimal += (decimal) THREE_FIFTHS;
        }
        Console.WriteLine("Six Hundred Thousand: {0:F10}", THREE_FIFTHS * ONE_MILLION);
        Console.WriteLine("Single: {0}", asSingle.ToString("F10"));
        Console.WriteLine("Double: {0}", asDouble.ToString("F10"));
        Console.WriteLine("Decimal: {0}", asDecimal.ToString("F10"));
        Console.WriteLine(GetByteString((float) THREE_FIFTHS));
        Console.WriteLine(GetByteString((double) THREE_FIFTHS));
        Console.WriteLine(GetByteString((decimal) THREE_FIFTHS));

        Console.WriteLine("vThree Fifths: {0}", vTHREE_FIFTHS.ToString("F10"));
        asSingle = 0f;
        asDouble = 0d;
        asDecimal = 0M;

        for (int i = 0; i < ONE_MILLION; i++)
        {
            asSingle += (float) vTHREE_FIFTHS;
            asDouble += (double) vTHREE_FIFTHS;
            asDecimal += (decimal) vTHREE_FIFTHS;
        }
        Console.WriteLine("Six Hundred Thousand: {0:F10}", vTHREE_FIFTHS * ONE_MILLION);
        Console.WriteLine("Single: {0}", asSingle.ToString("F10"));
        Console.WriteLine("Double: {0}", asDouble.ToString("F10"));
        Console.WriteLine("Decimal: {0}", asDecimal.ToString("F10"));
        Console.WriteLine(GetByteString((float) vTHREE_FIFTHS));
        Console.WriteLine(GetByteString((double) vTHREE_FIFTHS));
        Console.WriteLine(GetByteString((decimal) vTHREE_FIFTHS));

}

// Define other methods and classes here
string GetByteString(double d)
{
    return "#" + string.Join("", BitConverter.GetBytes(d).Select(b=>b.ToString("X2")));
}
string GetByteString(decimal d)
{
    return "D" + string.Join("", Decimal.GetBits(d).Select(b=>b.ToString("X8")));
}
string GetByteString(float f)
{
    return "S" + string.Join("", BitConverter.GetBytes(f).Select(b=>b.ToString("X2")));
}

Output:

Three Fifths: 0.6000000000
Six Hundred Thousand: 600000.0000000000
Single: 599093.4000000000

Decimal: 600000.0000000000
S9A99193F

D00000006000000000000000000010000
vThree Fifths: 0.6000000000
Six Hundred Thousand: 600000.0000000000
Single: 599093.4000000000

Decimal: 600000.0000000000
S9A99193F

D00000006000000000000000000010000

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM