我有一個small source file它打印確切值存儲在double
。答案末尾的代碼,以防鏈接消失。基本上,它獲取double
的確切位,並從那裏開始。這不是漂亮或efficent,但它的工作原理:)
string s = DoubleConverter.ToExactString(0.1);
Console.WriteLine(s);
輸出:
0.1000000000000000055511151231257827021181583404541015625
當你只需要使用0.1.ToString()
的BCL截斷的文字表述爲您服務。
至於針對double
values是完全表示 - 基本上,你需要制定出最接近的二進制表示是什麼,看到是否是精確值。基本上它需要在正確的範圍和精確度內由兩個冪(包括兩個負冪)組成。
例如,4。75能精確表示,因爲它是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);
}
}
}
'0.1'僅僅是因爲系統在將其轉換爲字符串表示形式時欺騙了事物並不意味着底層表示*具有*精確地存儲了'0.1'。 –
任何不是{1/2,1/4,1/8,1/16,...}的總和 – Exceptyon
如果您還可以使用Java,則有一個非常有用的類java.util.BigDecimal 。它具有從雙精確轉換到精確轉換爲字符串。它可以完成那些結果爲有限長度小數的操作。我經常用它來分析浮點問題。例如'new BigDecimal(0.1).toString()'是「0.1000000000000000055511151231257827021181583404541015625」 –