例如,十进制数0.1不能用二进制表示 任何有限精度的浮点
然而,在C#
中string s = 0.1.ToString(CultureInfo.InvariantCulture);
Console.WriteLine(s);
写0.1
。
我原本期望0.099999999999999999
左右。
我正在寻找至少一个双精简版的例子,它不能完全代表双重。
修改
正如其他人所指出的,0.1
确实是我一直在寻找的文字,正如下面的经典代码示例所示:
double sum = 0.0;
for (int i = 0; i < 10; i++)
{
sum += 0.1;
}
Console.WriteLine(sum.Equals(1.0)); // false
当双精度转换为其他数据类型时,会发生奇怪的事情。这不仅仅是string
的情况,因为这个表达式是真的:0.1m.Equals((decimal)0.1)
答案 0 :(得分:7)
我有small source file打印存储在double
中的完全值。答案结束时的代码,以防链接消失。基本上,它取出double
的确切位,并从那里开始。它不漂亮或不高效,但它有效:)
string s = DoubleConverter.ToExactString(0.1);
Console.WriteLine(s);
输出:
0.1000000000000000055511151231257827021181583404541015625
当您使用0.1.ToString()
时,BCL会截断您的文字表示。
至于哪些double
值可以准确表示 - 基本上,您需要计算出最接近的二进制表示形式,并查看是的确切值。基本上它需要在正确的范围和精度范围内由2的幂(包括2的负幂)组成。
例如,4.75可以准确表示,因为它是2 2 + 2 -1 + 2 -2
源代码:
using System;
using System.Globalization;
/// <summary>
/// A class to allow the conversion of doubles to string representations of
/// their exact decimal values. The implementation aims for readability over
/// efficiency.
/// </summary>
public class DoubleConverter
{
/// <summary>
/// Converts the given double to a string representation of its
/// exact decimal value.
/// </summary>
/// <param name="d">The double to convert.</param>
/// <returns>A string representation of the double's exact decimal value.</return>
public static string ToExactString (double d)
{
if (double.IsPositiveInfinity(d))
return "+Infinity";
if (double.IsNegativeInfinity(d))
return "-Infinity";
if (double.IsNaN(d))
return "NaN";
// Translate the double into sign, exponent and mantissa.
long bits = BitConverter.DoubleToInt64Bits(d);
// Note that the shift is sign-extended, hence the test against -1 not 1
bool negative = (bits < 0);
int exponent = (int) ((bits >> 52) & 0x7ffL);
long mantissa = bits & 0xfffffffffffffL;
// Subnormal numbers; exponent is effectively one higher,
// but there's no extra normalisation bit in the mantissa
if (exponent==0)
{
exponent++;
}
// Normal numbers; leave exponent as it is but add extra
// bit to the front of the mantissa
else
{
mantissa = mantissa | (1L<<52);
}
// Bias the exponent. It's actually biased by 1023, but we're
// treating the mantissa as m.0 rather than 0.m, so we need
// to subtract another 52 from it.
exponent -= 1075;
if (mantissa == 0)
{
return "0";
}
/* Normalize */
while((mantissa & 1) == 0)
{ /* i.e., Mantissa is even */
mantissa >>= 1;
exponent++;
}
/// Construct a new decimal expansion with the mantissa
ArbitraryDecimal ad = new ArbitraryDecimal (mantissa);
// If the exponent is less than 0, we need to repeatedly
// divide by 2 - which is the equivalent of multiplying
// by 5 and dividing by 10.
if (exponent < 0)
{
for (int i=0; i < -exponent; i++)
ad.MultiplyBy(5);
ad.Shift(-exponent);
}
// Otherwise, we need to repeatedly multiply by 2
else
{
for (int i=0; i < exponent; i++)
ad.MultiplyBy(2);
}
// Finally, return the string with an appropriate sign
if (negative)
return "-"+ad.ToString();
else
return ad.ToString();
}
/// <summary>Private class used for manipulating
class ArbitraryDecimal
{
/// <summary>Digits in the decimal expansion, one byte per digit
byte[] digits;
/// <summary>
/// How many digits are *after* the decimal point
/// </summary>
int decimalPoint=0;
/// <summary>
/// Constructs an arbitrary decimal expansion from the given long.
/// The long must not be negative.
/// </summary>
internal ArbitraryDecimal (long x)
{
string tmp = x.ToString(CultureInfo.InvariantCulture);
digits = new byte[tmp.Length];
for (int i=0; i < tmp.Length; i++)
digits[i] = (byte) (tmp[i]-'0');
Normalize();
}
/// <summary>
/// Multiplies the current expansion by the given amount, which should
/// only be 2 or 5.
/// </summary>
internal void MultiplyBy(int amount)
{
byte[] result = new byte[digits.Length+1];
for (int i=digits.Length-1; i >= 0; i--)
{
int resultDigit = digits[i]*amount+result[i+1];
result[i]=(byte)(resultDigit/10);
result[i+1]=(byte)(resultDigit%10);
}
if (result[0] != 0)
{
digits=result;
}
else
{
Array.Copy (result, 1, digits, 0, digits.Length);
}
Normalize();
}
/// <summary>
/// Shifts the decimal point; a negative value makes
/// the decimal expansion bigger (as fewer digits come after the
/// decimal place) and a positive value makes the decimal
/// expansion smaller.
/// </summary>
internal void Shift (int amount)
{
decimalPoint += amount;
}
/// <summary>
/// Removes leading/trailing zeroes from the expansion.
/// </summary>
internal void Normalize()
{
int first;
for (first=0; first < digits.Length; first++)
if (digits[first]!=0)
break;
int last;
for (last=digits.Length-1; last >= 0; last--)
if (digits[last]!=0)
break;
if (first==0 && last==digits.Length-1)
return;
byte[] tmp = new byte[last-first+1];
for (int i=0; i < tmp.Length; i++)
tmp[i]=digits[i+first];
decimalPoint -= digits.Length-(last+1);
digits=tmp;
}
/// <summary>
/// Converts the value to a proper decimal string representation.
/// </summary>
public override String ToString()
{
char[] digitString = new char[digits.Length];
for (int i=0; i < digits.Length; i++)
digitString[i] = (char)(digits[i]+'0');
// Simplest case - nothing after the decimal point,
// and last real digit is non-zero, eg value=35
if (decimalPoint==0)
{
return new string (digitString);
}
// Fairly simple case - nothing after the decimal
// point, but some 0s to add, eg value=350
if (decimalPoint < 0)
{
return new string (digitString)+
new string ('0', -decimalPoint);
}
// Nothing before the decimal point, eg 0.035
if (decimalPoint >= digitString.Length)
{
return "0."+
new string ('0',(decimalPoint-digitString.Length))+
new string (digitString);
}
// Most complicated case - part of the string comes
// before the decimal point, part comes after it,
// eg 3.5
return new string (digitString, 0,
digitString.Length-decimalPoint)+
"."+
new string (digitString,
digitString.Length-decimalPoint,
decimalPoint);
}
}
}
答案 1 :(得分:2)
BCL在打印时给你一个舍入值。应该没有可以打印不同表示或准确性的文字。
这很好,因为它大多数时候都与直觉相符。但到目前为止,我还没有找到一种很好的方法来获取完全值来打印。
答案 2 :(得分:0)
0.1
就是一个例子。但在C#中,它是double
类型的文字。 Double.ToString()
默认使用精度为15而不是17位的格式。
documentation的相关引用:
默认情况下,返回值仅包含15位精度,但内部最多保留17位数。 [...]如果您需要更高的精度,请使用&#34; G17&#34;指定格式。格式规范,总是返回17位精度,或者#34; R&#34;如果数字可以用该精度表示则返回15位数,如果数字只能以最大精度表示,则返回17位数。
因此,0.1.ToString("G17")
等于"0.10000000000000001"
,这是来自Jon Skeet的数字0.1000000000000000055511151231257827021181583404541015625
正确舍入到无穷大。请注意,第一个数字中的最后1
是第17个有效数字,第二个数字中的第一个5
是第18个有效数字。 0.1.ToString()
与0.1.ToString("G")
基本相同,等于"0.1"
。如果您在小数点后打印15位数,或"0.100000000000000"
。这是"0.10000000000000001"
正确舍入为零。
有趣的是,Convert.ToDecimal(double)
仅使用了15位有效数字。
documentation的相关引用:
此方法返回的十进制值最多包含15位有效数字。如果value参数包含超过15位有效数字,则使用舍入舍入为最接近的数字。
您可以使用相同的G17格式和decimal.Parse()
将0.1
转换为十进制:decimal.Parse(0.1.ToString("G17"))
。此代码段产生的数字不等于0.1m
。
有关详细信息,请查看MSDN上的The General ("G") Format Specifier页。