[英]Double to string conversion without scientific notation
如何在 .NET Framework 中將 double 轉換為沒有科學記數法的浮點字符串表示形式?
“小”樣本(有效數字可以是任何大小,例如1.5E200
或1e-200
):
3248971234698200000000000000000000000000000000
0.00000000000000000000000000000000000023897356978234562
沒有一個標准數字格式是這樣的,自定義格式似乎也不允許在小數點分隔符后有一個開放的位數。
這不是How to convert double to string without the power to 10 representation (E-05)的副本,因為那里給出的答案並不能解決手頭的問題。 這個問題中公認的解決方案是使用一個固定點(例如 20 位),這不是我想要的。 定點格式化和修剪多余的 0 也不能解決問題,因為固定寬度的最大寬度是 99 個字符。
注意:解決方案必須正確處理自定義數字格式(例如,其他小數分隔符,取決於文化信息)。
編輯:這個問題實際上只是關於取代上述數字。 我知道浮點數是如何工作的,以及可以使用和計算哪些數字。
對於通用¹解決方案,您需要保留 339 個位置:
doubleValue.ToString("0." + new string('#', 339))
非零小數位數的最大數量為 16。15 在小數點右側。 指數最多可以將這 15 位數字向右移動 324 位。 (見范圍和精度。 )
它適用於double.Epsilon
、 double.MinValue
、 double.MaxValue
以及介於兩者之間的任何內容。
性能將比正則表達式/字符串操作解決方案高得多,因為所有格式化和字符串工作都是由非托管 CLR 代碼一次性完成的。 此外,代碼更容易證明是正確的。
為了易於使用和更好的性能,將其設為常數:
public static class FormatStrings
{
public const string DoubleFixedPoint = "0.###################################################################################################################################################################################################################################################################################################################################################";
}
¹更新:我錯誤地說這也是一個無損解決方案。 事實上並非如此,因為ToString
對除r
之外的所有格式進行正常顯示舍入。 活生生的例子。 謝謝,@Loathing! 如果您需要以定點表示法往返的能力(即,如果您今天使用.ToString("r")
),請參閱Lothing 的答案。
我有一個類似的問題,這對我有用:
doubleValue.ToString("F99").TrimEnd('0')
F99 可能是矯枉過正,但你明白了。
這是一個字符串解析解決方案,將源數字(雙精度)轉換為字符串並解析為其組成部分。 然后通過規則將其重新組合成全長數字表示。 它還根據要求考慮語言環境。
更新:轉換測試僅包括一位整數,這是常態,但該算法也適用於:239483.340901e-20
using System;
using System.Text;
using System.Globalization;
using System.Threading;
public class MyClass
{
public static void Main()
{
Console.WriteLine(ToLongString(1.23e-2));
Console.WriteLine(ToLongString(1.234e-5)); // 0.00010234
Console.WriteLine(ToLongString(1.2345E-10)); // 0.00000001002345
Console.WriteLine(ToLongString(1.23456E-20)); // 0.00000000000000000100023456
Console.WriteLine(ToLongString(5E-20));
Console.WriteLine("");
Console.WriteLine(ToLongString(1.23E+2)); // 123
Console.WriteLine(ToLongString(1.234e5)); // 1023400
Console.WriteLine(ToLongString(1.2345E10)); // 1002345000000
Console.WriteLine(ToLongString(-7.576E-05)); // -0.00007576
Console.WriteLine(ToLongString(1.23456e20));
Console.WriteLine(ToLongString(5e+20));
Console.WriteLine("");
Console.WriteLine(ToLongString(9.1093822E-31)); // mass of an electron
Console.WriteLine(ToLongString(5.9736e24)); // mass of the earth
Console.ReadLine();
}
private static string ToLongString(double input)
{
string strOrig = input.ToString();
string str = strOrig.ToUpper();
// if string representation was collapsed from scientific notation, just return it:
if (!str.Contains("E")) return strOrig;
bool negativeNumber = false;
if (str[0] == '-')
{
str = str.Remove(0, 1);
negativeNumber = true;
}
string sep = Thread.CurrentThread.CurrentCulture.NumberFormat.NumberDecimalSeparator;
char decSeparator = sep.ToCharArray()[0];
string[] exponentParts = str.Split('E');
string[] decimalParts = exponentParts[0].Split(decSeparator);
// fix missing decimal point:
if (decimalParts.Length==1) decimalParts = new string[]{exponentParts[0],"0"};
int exponentValue = int.Parse(exponentParts[1]);
string newNumber = decimalParts[0] + decimalParts[1];
string result;
if (exponentValue > 0)
{
result =
newNumber +
GetZeros(exponentValue - decimalParts[1].Length);
}
else // negative exponent
{
result =
"0" +
decSeparator +
GetZeros(exponentValue + decimalParts[0].Length) +
newNumber;
result = result.TrimEnd('0');
}
if (negativeNumber)
result = "-" + result;
return result;
}
private static string GetZeros(int zeroCount)
{
if (zeroCount < 0)
zeroCount = Math.Abs(zeroCount);
StringBuilder sb = new StringBuilder();
for (int i = 0; i < zeroCount; i++) sb.Append("0");
return sb.ToString();
}
}
您可以將double
精度轉換為decimal
,然后執行ToString()
。
(0.000000005).ToString() // 5E-09
((decimal)(0.000000005)).ToString() // 0,000000005
我還沒有進行更快的性能測試,將 64 位double
轉換為 128 位decimal
或超過 300 個字符的格式字符串。 哦,在轉換過程中可能會出現溢出錯誤,但如果你的值適合decimal
,這應該可以正常工作。
更新:鑄造似乎要快得多。 使用另一個答案中給出的准備好的格式字符串,格式化一百萬次需要 2.3 秒,並且只需要 0.19 秒。 可重復。 這快了10 倍。 現在它只是關於價值范圍。
這是我到目前為止所得到的,似乎有效,但也許有人有更好的解決方案:
private static readonly Regex rxScientific = new Regex(@"^(?<sign>-?)(?<head>\d+)(\.(?<tail>\d*?)0*)?E(?<exponent>[+\-]\d+)$", RegexOptions.IgnoreCase|RegexOptions.ExplicitCapture|RegexOptions.CultureInvariant);
public static string ToFloatingPointString(double value) {
return ToFloatingPointString(value, NumberFormatInfo.CurrentInfo);
}
public static string ToFloatingPointString(double value, NumberFormatInfo formatInfo) {
string result = value.ToString("r", NumberFormatInfo.InvariantInfo);
Match match = rxScientific.Match(result);
if (match.Success) {
Debug.WriteLine("Found scientific format: {0} => [{1}] [{2}] [{3}] [{4}]", result, match.Groups["sign"], match.Groups["head"], match.Groups["tail"], match.Groups["exponent"]);
int exponent = int.Parse(match.Groups["exponent"].Value, NumberStyles.Integer, NumberFormatInfo.InvariantInfo);
StringBuilder builder = new StringBuilder(result.Length+Math.Abs(exponent));
builder.Append(match.Groups["sign"].Value);
if (exponent >= 0) {
builder.Append(match.Groups["head"].Value);
string tail = match.Groups["tail"].Value;
if (exponent < tail.Length) {
builder.Append(tail, 0, exponent);
builder.Append(formatInfo.NumberDecimalSeparator);
builder.Append(tail, exponent, tail.Length-exponent);
} else {
builder.Append(tail);
builder.Append('0', exponent-tail.Length);
}
} else {
builder.Append('0');
builder.Append(formatInfo.NumberDecimalSeparator);
builder.Append('0', (-exponent)-1);
builder.Append(match.Groups["head"].Value);
builder.Append(match.Groups["tail"].Value);
}
result = builder.ToString();
}
return result;
}
// test code
double x = 1.0;
for (int i = 0; i < 200; i++) {
x /= 10;
}
Console.WriteLine(x);
Console.WriteLine(ToFloatingPointString(x));
使用#.###...###
或F99
的問題是它不保留小數點末尾的精度,例如:
String t1 = (0.0001/7).ToString("0." + new string('#', 339)); // 0.0000142857142857143
String t2 = (0.0001/7).ToString("r"); // 1.4285714285714287E-05
DecimalConverter.cs
的問題在於它很慢。 此代碼與 Sasik 的答案相同,但速度是后者的兩倍。 單元測試方法在底部。
public static class RoundTrip {
private static String[] zeros = new String[1000];
static RoundTrip() {
for (int i = 0; i < zeros.Length; i++) {
zeros[i] = new String('0', i);
}
}
private static String ToRoundTrip(double value) {
String str = value.ToString("r");
int x = str.IndexOf('E');
if (x < 0) return str;
int x1 = x + 1;
String exp = str.Substring(x1, str.Length - x1);
int e = int.Parse(exp);
String s = null;
int numDecimals = 0;
if (value < 0) {
int len = x - 3;
if (e >= 0) {
if (len > 0) {
s = str.Substring(0, 2) + str.Substring(3, len);
numDecimals = len;
}
else
s = str.Substring(0, 2);
}
else {
// remove the leading minus sign
if (len > 0) {
s = str.Substring(1, 1) + str.Substring(3, len);
numDecimals = len;
}
else
s = str.Substring(1, 1);
}
}
else {
int len = x - 2;
if (len > 0) {
s = str[0] + str.Substring(2, len);
numDecimals = len;
}
else
s = str[0].ToString();
}
if (e >= 0) {
e = e - numDecimals;
String z = (e < zeros.Length ? zeros[e] : new String('0', e));
s = s + z;
}
else {
e = (-e - 1);
String z = (e < zeros.Length ? zeros[e] : new String('0', e));
if (value < 0)
s = "-0." + z + s;
else
s = "0." + z + s;
}
return s;
}
private static void RoundTripUnitTest() {
StringBuilder sb33 = new StringBuilder();
double[] values = new [] { 123450000000000000.0, 1.0 / 7, 10000000000.0/7, 100000000000000000.0/7, 0.001/7, 0.0001/7, 100000000000000000.0, 0.00000000001,
1.23e-2, 1.234e-5, 1.2345E-10, 1.23456E-20, 5E-20, 1.23E+2, 1.234e5, 1.2345E10, -7.576E-05, 1.23456e20, 5e+20, 9.1093822E-31, 5.9736e24, double.Epsilon };
foreach (int sign in new [] { 1, -1 }) {
foreach (double val in values) {
double val2 = sign * val;
String s1 = val2.ToString("r");
String s2 = ToRoundTrip(val2);
double val2_ = double.Parse(s2);
double diff = Math.Abs(val2 - val2_);
if (diff != 0) {
throw new Exception("Value {0} did not pass ToRoundTrip.".Format2(val.ToString("r")));
}
sb33.AppendLine(s1);
sb33.AppendLine(s2);
sb33.AppendLine();
}
}
}
}
在過去,當我們不得不編寫自己的格式化程序時,我們會隔離尾數和指數並分別格式化它們。
在 Jon Skeet 的這篇文章 ( https://csharpindepth.com/articles/FloatingPoint ) 中,他提供了一個指向他的 DoubleConverter.cs 例程的鏈接,該例程應該完全符合您的要求。 Skeet 在從 c# 中的 double 中提取尾數和指數時也提到了這一點。
強制性的基於對數的解決方案。 請注意,此解決方案由於涉及數學運算,可能會稍微降低您的數字的准確性。 沒有經過嚴格測試。
private static string DoubleToLongString(double x)
{
int shift = (int)Math.Log10(x);
if (Math.Abs(shift) <= 2)
{
return x.ToString();
}
if (shift < 0)
{
double y = x * Math.Pow(10, -shift);
return "0.".PadRight(-shift + 2, '0') + y.ToString().Substring(2);
}
else
{
double y = x * Math.Pow(10, 2 - shift);
return y + "".PadRight(shift - 2, '0');
}
}
編輯:如果小數點越過數字的非零部分,該算法將慘遭失敗。 我嘗試簡單但走得太遠了。
我剛剛對上面的代碼進行了即興創作,使其適用於負指數值。
using System;
using System.Text.RegularExpressions;
using System.IO;
using System.Text;
using System.Threading;
namespace ConvertNumbersInScientificNotationToPlainNumbers
{
class Program
{
private static string ToLongString(double input)
{
string str = input.ToString(System.Globalization.CultureInfo.InvariantCulture);
// if string representation was collapsed from scientific notation, just return it:
if (!str.Contains("E")) return str;
var positive = true;
if (input < 0)
{
positive = false;
}
string sep = Thread.CurrentThread.CurrentCulture.NumberFormat.NumberDecimalSeparator;
char decSeparator = sep.ToCharArray()[0];
string[] exponentParts = str.Split('E');
string[] decimalParts = exponentParts[0].Split(decSeparator);
// fix missing decimal point:
if (decimalParts.Length == 1) decimalParts = new string[] { exponentParts[0], "0" };
int exponentValue = int.Parse(exponentParts[1]);
string newNumber = decimalParts[0].Replace("-", "").
Replace("+", "") + decimalParts[1];
string result;
if (exponentValue > 0)
{
if (positive)
result =
newNumber +
GetZeros(exponentValue - decimalParts[1].Length);
else
result = "-" +
newNumber +
GetZeros(exponentValue - decimalParts[1].Length);
}
else // negative exponent
{
if (positive)
result =
"0" +
decSeparator +
GetZeros(exponentValue + decimalParts[0].Replace("-", "").
Replace("+", "").Length) + newNumber;
else
result =
"-0" +
decSeparator +
GetZeros(exponentValue + decimalParts[0].Replace("-", "").
Replace("+", "").Length) + newNumber;
result = result.TrimEnd('0');
}
float temp = 0.00F;
if (float.TryParse(result, out temp))
{
return result;
}
throw new Exception();
}
private static string GetZeros(int zeroCount)
{
if (zeroCount < 0)
zeroCount = Math.Abs(zeroCount);
StringBuilder sb = new StringBuilder();
for (int i = 0; i < zeroCount; i++) sb.Append("0");
return sb.ToString();
}
public static void Main(string[] args)
{
//Get Input Directory.
Console.WriteLine(@"Enter the Input Directory");
var readLine = Console.ReadLine();
if (readLine == null)
{
Console.WriteLine(@"Enter the input path properly.");
return;
}
var pathToInputDirectory = readLine.Trim();
//Get Output Directory.
Console.WriteLine(@"Enter the Output Directory");
readLine = Console.ReadLine();
if (readLine == null)
{
Console.WriteLine(@"Enter the output path properly.");
return;
}
var pathToOutputDirectory = readLine.Trim();
//Get Delimiter.
Console.WriteLine("Enter the delimiter;");
var columnDelimiter = (char)Console.Read();
//Loop over all files in the directory.
foreach (var inputFileName in Directory.GetFiles(pathToInputDirectory))
{
var outputFileWithouthNumbersInScientificNotation = string.Empty;
Console.WriteLine("Started operation on File : " + inputFileName);
if (File.Exists(inputFileName))
{
// Read the file
using (var file = new StreamReader(inputFileName))
{
string line;
while ((line = file.ReadLine()) != null)
{
String[] columns = line.Split(columnDelimiter);
var duplicateLine = string.Empty;
int lengthOfColumns = columns.Length;
int counter = 1;
foreach (var column in columns)
{
var columnDuplicate = column;
try
{
if (Regex.IsMatch(columnDuplicate.Trim(),
@"^[+-]?[0-9]+(\.[0-9]+)?[E]([+-]?[0-9]+)$",
RegexOptions.IgnoreCase))
{
Console.WriteLine("Regular expression matched for this :" + column);
columnDuplicate = ToLongString(Double.Parse
(column,
System.Globalization.NumberStyles.Float));
Console.WriteLine("Converted this no in scientific notation " +
"" + column + " to this number " +
columnDuplicate);
}
}
catch (Exception)
{
}
duplicateLine = duplicateLine + columnDuplicate;
if (counter != lengthOfColumns)
{
duplicateLine = duplicateLine + columnDelimiter.ToString();
}
counter++;
}
duplicateLine = duplicateLine + Environment.NewLine;
outputFileWithouthNumbersInScientificNotation = outputFileWithouthNumbersInScientificNotation + duplicateLine;
}
file.Close();
}
var outputFilePathWithoutNumbersInScientificNotation
= Path.Combine(pathToOutputDirectory, Path.GetFileName(inputFileName));
//Create Directory If it does not exist.
if (!Directory.Exists(pathToOutputDirectory))
Directory.CreateDirectory(pathToOutputDirectory);
using (var outputFile =
new StreamWriter(outputFilePathWithoutNumbersInScientificNotation))
{
outputFile.Write(outputFileWithouthNumbersInScientificNotation);
outputFile.Close();
}
Console.WriteLine("The transformed file is here :" +
outputFilePathWithoutNumbersInScientificNotation);
}
}
}
}
}
此代碼采用輸入目錄並基於分隔符將科學計數法中的所有值轉換為數字格式。
謝謝
試試這個:
public static string DoubleToFullString(double value,
NumberFormatInfo formatInfo)
{
string[] valueExpSplit;
string result, decimalSeparator;
int indexOfDecimalSeparator, exp;
valueExpSplit = value.ToString("r", formatInfo)
.ToUpper()
.Split(new char[] { 'E' });
if (valueExpSplit.Length > 1)
{
result = valueExpSplit[0];
exp = int.Parse(valueExpSplit[1]);
decimalSeparator = formatInfo.NumberDecimalSeparator;
if ((indexOfDecimalSeparator
= valueExpSplit[0].IndexOf(decimalSeparator)) > -1)
{
exp -= (result.Length - indexOfDecimalSeparator - 1);
result = result.Replace(decimalSeparator, "");
}
if (exp >= 0) result += new string('0', Math.Abs(exp));
else
{
exp = Math.Abs(exp);
if (exp >= result.Length)
{
result = "0." + new string('0', exp - result.Length)
+ result;
}
else
{
result = result.Insert(result.Length - exp, decimalSeparator);
}
}
}
else result = valueExpSplit[0];
return result;
}
作為全世界數以百萬計的程序員,嘗試搜索是否有人已經遇到了您的問題總是一個好習慣。 有時有解決方案是垃圾,這意味着是時候自己編寫了,有時也有很棒的,例如以下:
http://www.yoda.arachsys.com/csharp/DoubleConverter.cs
(詳情: http ://www.yoda.arachsys.com/csharp/floatingpoint.html)
string strdScaleFactor = dScaleFactor.ToString(); // where dScaleFactor = 3.531467E-05
decimal decimalScaleFactor = Decimal.Parse(strdScaleFactor, System.Globalization.NumberStyles.Float);
我不知道我對這個問題的回答是否仍然有用。 但在這種情況下,我建議“將雙變量分解為小數位”以將其存儲在字符串類型的數據數組/數組中。
這種從雙精度到字符串的分解和存儲部分(逐個數字)的過程基本上可以使用兩個循環和一個“替代方案”(如果你想到解決方法,我想你明白了),其中第一個循環將從 double 中提取值而不轉換為 String,從而產生有福的科學記數法並將數字按數字存儲在數組中。 這將使用MOD完成 - 檢查回文數的相同方法,例如:
String[] Array_ = new double[ **here you will put an extreme value of places your DOUBLE can reach, you must have a prediction**];
for (int i = 0, variableDoubleMonstrous > 0, i++){
x = variableDoubleMonstrous %10;
Array_[i] = x;
variableDoubleMonstrous /= 10;
}
而第二個循環將Array的值倒置(因為在這個檢查回文的過程中,值從最后一位倒置到第一位,從倒數第二位倒置到第二位等等。還記得嗎?)得到原始值:
String[] ArrayFinal = new String[the same number of "places" / indices of the other Array / Data array];
int lengthArray = Array_.Length;
for (int i = 0, i < Array_.Length, i++){
FinalArray[i] = Array_[lengthArray - 1];
lengthArray--;
}
***警告:有一個我沒有注意的問題。 在這種情況下,將沒有“。” (浮點小數分隔符或雙精度),所以這個解決方案沒有推廣。 但是如果使用小數分隔符真的很重要,不幸的是唯一的可能性(如果做得好,它將有很好的性能)是:**使用例程獲取原始值的小數點位置,與科學記數法 - 重要的是你知道這個浮點在諸如“長度”位置 x 之類的數字之前,在諸如 y 位置之類的數字之后 - 使用循環提取每個數字 - 如上所示 - 並且在最后將數據從最后一個數組“導出”到另一個數組,包括原始變量中的假想位置中的小數位分隔符(逗號或句點,如果變量為十進制、雙精度或浮點數),在“該矩陣的真實”位置。
*** 位置的概念是,找出小數點前有多少個數字,因此有了這些信息,您就可以將點的實際位置存儲在字符串數組中。
可以滿足的需求:
但是你問:
但是,如果問題是在計算中使用轉換后的 Double(字符串數組)的值怎么辦。 然后在這種情況下,你繞着圈子轉。 好吧,即使使用科學記數法,原始變量也將起作用。 浮點和十進制變量類型之間的唯一區別在於值的舍入,這取決於目的,只需要更改所使用的數據類型,但是有大量信息丟失是危險的,看這里
我認為你只需要使用 IFormat
ToString(doubleVar, System.Globalization.NumberStyles.Number)
例子:
double d = double.MaxValue;
string s = d.ToString(d, System.Globalization.NumberStyles.Number);
我的解決方案是使用自定義格式。 試試這個:
double d;
d = 1234.12341234;
d.ToString("#########0.#########");
這對我來說很好......
double number = 1.5E+200;
string s = number.ToString("#");
//Output: "150000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
只是建立在 jcasso 所說的基礎上,您可以做的是通過更改指數來調整您的 double 值,以便您最喜歡的格式會為您執行此操作,應用格式,然后用零填充結果以補償調整。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.