简体   繁体   English

如何打印存储在浮点数中的确切值?

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

If I assign the value 0.1 to a float:如果我将值 0.1 分配给浮点数:

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. memory 中存储的实际值不是 0.1 的精确表示,因为 0.1 不是单精度浮点格式可以精确表示的数字。 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.但我无法找到一种方法来让 C# 打印出该值。 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? 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.编辑:在其他地方引起我的注意,您可以使用标准格式字符串获得我要求的行为......在 .NET Core 和 .NET 5.0 上。 So this question is .NET Framework specific, I guess.所以这个问题是 .NET 框架特定的,我猜。

The basic idea here is to convert the float value into a rational value, and then convert the rational into a decimal.这里的基本思想是先把float转成有理数,再把有理数转成小数。

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).下面的代码(for.Net 6,它提供了BitConverter.SingleToUInt32Bits方法)将打印一个float的准确值(包括NaN值是否是quiet/signalling,NaN 的payload 以及是否设置了符号位)。 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.请注意, WriteRational方法并不普遍适用于所有有理数,因为它不会尝试检测非终止十进制表示形式:这在这里不是问题,因为float中的所有值都具有二次幂的分母。

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.我已经根据valueBitsfractionBits (在方法的第一行中定义)编写了代码中的大部分常量,以便尽可能直接地将此方法用于double s。 To do this:去做这个:

  • Change valueBits to sizeof(double) * 8valueBits更改为sizeof(double) * 8
  • Change fractionBits to 52fractionBits更改为 52
  • Change all uint s to ulong s (including converting 1U to 1UL )将所有uint s 更改为ulong s(包括将1U转换为1UL
  • Call BitConverter.DoubleToUInt64Bits instead of BitConverter.SingleToUInt32Bits调用BitConverter.DoubleToUInt64Bits而不是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).是的,这是 C#(或 .net)中非常有趣的挑战。 IMHO, most simple solution would be to multiply float/double with some huge number and then convert floating point result to BigInteger .恕我直言,最简单的解决方案是将 float/double 与一些巨大的数字相乘,然后将浮点结果转换为BigInteger Like, here we try to calculate result of 1e+51*0.1 :就像,在这里我们尝试计算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.因为浮点格式的 0.1 只是近似表示,带有机器 epsilon 误差。 That's why we get this weird result and not 100.... (50 zeros).这就是为什么我们得到这个奇怪的结果而不是 100....(50 个零)。

Oops, this answer relates to C, not C#.糟糕,这个答案与 C 相关,而不是 C#。

Leaving it up as it may provide C# insight as the languages have similarities concerning this.保留它,因为它可能提供 C# 洞察力,因为语言在这方面有相似之处。


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 :要打印的价值float与所有其他足够鲜明的小数位十进制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() .超过某个点(例如 20 个有效数字), big_value可能会使用printf()失去低位数字的正确性。 This incorrectness is allowed in C and IEEE 754 :这种错误在 C 和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.要打印所有float所有小数位的十进制值,请编写一个辅助函数。 Example .例子

// Using custom code
// -FLT_TRUE_MIN 
-0.00000000000000000000000000000000000000000000140129846432481707092372958328991613128026194187651577175706828388979108268586060148663818836212158203125

For .NET Framework, use format string G .对于 .NET Framework,请使用格式字符串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.它只会打印 0.1。

You can check this answer for the differences between float, double and decimal您可以检查此答案以了解浮点数、双精度数和小数点之间的差异

Difference between decimal, float and double in .NET? .NET 中小数、浮点数和双精度数之间的区别?

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 如果我将2的double数据类型值相加,结果将四舍五入并打印出来。如何获得double的确切输出? - If i add 2 double data type value, the result is round off and print.How do i get the exact output in double? 如何打印在存储过程中调用的参数和查询? - How do I print the parameters and query that was called in the stored procedure? 如何从XAML中的数据绑定源舍入存储在字符串中的浮点值 - How can I round a float value stored in a string from a data bound source in xaml 如何正确遍历并打印Int,Long,Float或BigInteger的位? - How do I properly loop through and print bits of an Int, Long, Float, or BigInteger? 实体框架:如何运行存储过程并返回值? - Entity Framework: how do I run a stored procedure and return a value? 如何获取存储在数组中的类中的值? - How do I get a value within a class stored within an array? 如何使用 Dapper 获取存储过程的返回值? - How do I use Dapper to get the return value of stored proc? 如何将空值传递给使用存储过程的SqlDataSource? - How do I pass a null value to a SqlDataSource that is using a stored procedure? C#浮点型不作为精确值插入 - C# Float does not insert as exact value 如何搜索确切的短语? - How do I search for the exact phrase?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM