问题
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 hightlighted is:
Three Fifths: 0.6000000000
Six Hundred Thousand: 600000.0000000000
Single: 599093.4000000000
Double: 599999.9999886850
Decimal: 600000.0000000000
vThree Fifths: 0.6000000000
Six Hundred Thousand: 600000.0000000000
Single: 599093.4000000000
Double: 600000.0238418580
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? (I.e. produce a THREE_FIFTHS
with the same results as vTHREE_FIFTHS
.)
回答1:
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. E.g. 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, e.g.:
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
回答2:
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
Double: 599999.9999886850
Decimal: 600000.0000000000
S9A99193F
#333333333333E33F
D00000006000000000000000000010000
vThree Fifths: 0.6000000000
Six Hundred Thousand: 600000.0000000000
Single: 599093.4000000000
Double: 600000.0238418580
Decimal: 600000.0000000000
S9A99193F
#000000403333E33F
D00000006000000000000000000010000
来源:https://stackoverflow.com/questions/19593234/get-float-constant-to-be-the-same-the-runtime-result-and-vb-net