简体   繁体   中英

How do I print the exact value stored in a float?

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:

  • Change valueBits to sizeof(double) * 8
  • Change fractionBits to 52
  • Change all uint s to ulong s (including converting 1U to 1UL )
  • Call 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

Difference between decimal, float and double in .NET?

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