If I assign the value 0.1 to a float:
float f = 0.1;
The actual value stored in memory is not an exact representation of 0.1, because 0.1 is not a number that can be exactly represented in single-precision floating-point format. The actual value stored - if I did my maths correctly - is
0.100000001490116119384765625
But I can't identify a way to get C# to print out that value. Even if I ask it to print the number to a great many decimal places, it doesn't give the correct answer:
// prints 0.10000000000000000000000000000000000000000000000000
Console.WriteLine(f.ToString("F50"));
How can I print the exact value stored in a float; the value actually represented by the bit-pattern in memory?
EDIT: It has been brought to my attention elsewhere that you can get the behaviour I ask for using standard format strings... on .NET Core and .NET 5.0. So this question is .NET Framework specific, I guess.
The basic idea here is to convert the float
value into a rational value, and then convert the rational into a decimal.
The following code (for.Net 6, which provides the BitConverter.SingleToUInt32Bits
method) will print the exact value of a float
(including whether a NaN
value is quiet/signalling, the payload of the NaN and whether the sign bit is set). Note that the WriteRational
method is not generally-applicable to all rationals as it makes no attempt to detect non-terminating decimal representations: this is not an issue here since all values in a float
have power-of-two denominators.
using System; // not necessary with implicit usings
using System.Globalization;
using System.Numerics;
using System.Text;
static string ExactStringSingle(float value)
{
const int valueBits = sizeof(float) * 8;
const int fractionBits = 23; // excludes implicit leading 1 in normal values
const int exponentBits = valueBits - fractionBits - 1;
const uint signMask = 1U << (valueBits - 1);
const uint fractionMask = (1U << fractionBits) - 1;
var bits = BitConverter.SingleToUInt32Bits(value);
var result = new StringBuilder();
if ((bits & signMask) != 0) { result.Append('-'); }
var biasedExponent = (int)((bits & ~signMask) >> fractionBits);
var fraction = bits & fractionMask;
// Maximum possible value of the biased exponent: infinities and NaNs
const int maxExponent = (1 << exponentBits) - 1;
if (biasedExponent == maxExponent)
{
if (fraction == 0)
{
result.Append("inf");
}
else
{
// NaN type is stored in the most significant bit of the fraction
const uint nanTypeMask = 1U << (fractionBits - 1);
// NaN payload
const uint nanPayloadMask = nanTypeMask - 1;
// NaN type, valid for x86, x86-64, 68000, ARM, SPARC
var isQuiet = (fraction & nanTypeMask) != 0;
var nanPayload = fraction & nanPayloadMask;
result.Append(isQuiet
? FormattableString.Invariant($"qNaN(0x{nanPayload:x})")
: FormattableString.Invariant($"sNaN(0x{nanPayload:x})"));
}
return result.ToString();
}
// Minimum value of biased exponent above which no fractional part will exist
const int noFractionThreshold = (1 << (exponentBits - 1)) + fractionBits - 1;
if (biasedExponent == 0)
{
// zeroes and subnormal numbers
// shift for the denominator of the rational part of a subnormal number
const int denormalDenominatorShift = noFractionThreshold - 1;
WriteRational(fraction, BigInteger.One << denormalDenominatorShift, result);
return result.ToString();
}
// implicit leading one in the fraction part
const uint implicitLeadingOne = 1U << fractionBits;
var numerator = (BigInteger)(fraction | implicitLeadingOne);
if (biasedExponent >= noFractionThreshold)
{
numerator <<= biasedExponent - noFractionThreshold;
result.Append(numerator.ToString(CultureInfo.InvariantCulture));
}
else
{
var denominator = BigInteger.One << (noFractionThreshold - (int)biasedExponent);
WriteRational(numerator, denominator, result);
}
return result.ToString();
}
static void WriteRational(BigInteger numerator, BigInteger denominator, StringBuilder result)
{
// precondition: denominator contains only factors of 2 and 5
var intPart = BigInteger.DivRem(numerator, denominator, out numerator);
result.Append(intPart.ToString(CultureInfo.InvariantCulture));
if (numerator.IsZero) { return; }
result.Append('.');
do
{
numerator *= 10;
var gcd = BigInteger.GreatestCommonDivisor(numerator, denominator);
denominator /= gcd;
intPart = BigInteger.DivRem(numerator / gcd, denominator, out numerator);
result.Append(intPart.ToString(CultureInfo.InvariantCulture));
} while (!numerator.IsZero);
}
I've written most of the constants in the code in terms of valueBits
and fractionBits
(defined in the first lines of the method), in order to make it as straightforward as possible to adapt this method for double
s. To do this:
valueBits
to sizeof(double) * 8
fractionBits
to 52uint
s to ulong
s (including converting 1U
to 1UL
)BitConverter.DoubleToUInt64Bits
instead of BitConverter.SingleToUInt32Bits
Making this code culture-aware is left as an exercise for the reader:-)
Yeah, this is very fun challenge in C# (or .net). IMHO, most simple solution would be to multiply float/double with some huge number and then convert floating point result to BigInteger
. Like, here we try to calculate result of 1e+51*0.1
:
using System.Numerics;
class HelloWorld {
static void Main() {
// Ideally, 1e+51*0.1 should be 1 followed by 50 zeros, but =>
System.Console.WriteLine(new BigInteger(1e+51*0.1));
// Outputs 100000000000000007629769841091887003294964970946560
}
}
Because 0.1 in floating point format is represented just approximately, with machine epsilon error. That's why we get this weird result and not 100.... (50 zeros).
Oops, this answer relates to C, not C#.
Leaving it up as it may provide C# insight as the languages have similarities concerning this.
How do I print the exact value stored in a float?
// Print exact value with a hexadecimal significant.
printf("%a\n", some_float);
// e.g. 0x1.99999ap-4 for 0.1f
To print the value of a float
in decimal with sufficient distinctive decimal places from all other float
:
int digits_after_the_decimal_point = FLT_DECIMAL_DIG - 1; // e.g. 9 -1
printf("%.*e\n", digits_after_the_decimal_point, some_float);
// e.g. 1.00000001e-01 for 0.1f
To print the value in decimal with all decimal places places is hard - and rarely needed. Code could use a greater precision. Past a certain point (eg 20 significant digits), big_value
may lose correctness in the lower digits with printf()
. This incorrectness is allowed in C and IEEE 754 :
int big_value = 19; // More may be a problem.
printf("%.*e\n", big_value, some_float);
// e.g. 1.0000000149011611938e-01 for 0.1f
// for FLT_TRUE_MIN and big_value = 50, not quite right
// e.g. 1.40129846432481707092372958328991613128026200000000e-45
To print the value in decimal with all decimal places places for all float
, write a helper function. Example .
// Using custom code
// -FLT_TRUE_MIN
-0.00000000000000000000000000000000000000000000140129846432481707092372958328991613128026194187651577175706828388979108268586060148663818836212158203125
For .NET Framework, use format string G
. Not exactly but enough for the float errors.
> (0.3d).ToString("G70")
0.29999999999999999
> (0.1d+0.2d).ToString("G70")
0.30000000000000004
You should use decimal instead of float, like this:
decimal f = 0.1m;
it will print 0.1 only.
You can check this answer for the differences between float, double and decimal
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.