[英]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.我已经根据
valueBits
和fractionBits
(在方法的第一行中定义)编写了代码中的大部分常量,以便尽可能直接地将此方法用于double
s。 To do this:去做这个:
valueBits
to sizeof(double) * 8
valueBits
更改为sizeof(double) * 8
fractionBits
to 52fractionBits
更改为 52uint
s to ulong
s (including converting 1U
to 1UL
)uint
s 更改为ulong
s(包括将1U
转换为1UL
)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.