围绕一个双重的X有效数字
如果我有一个double(234.004223)等,我想在C#中将其舍入为x个有效数字。
到目前为止,我只能find方法来四舍五入到小数点后的位置,但是这只是在数字中有0的情况下删除精度。
例如,小数点后一位0.086变为0.1,但我希望保持在0.08。
该框架没有一个内置的函数来轮回(或截断,如在你的例子)的有效数字。 但是,您可以这样做的一种方法是缩放数字,使您的第一个有效数字位于小数点后面,四舍五入(或截断),然后缩小。 下面的代码应该做的伎俩:
static double RoundToSignificantDigits(this double d, int digits){ if(d == 0) return 0; double scale = Math.Pow(10, Math.Floor(Math.Log10(Math.Abs(d))) + 1); return scale * Math.Round(d / scale, digits); }
如果,如你的例子,你真的想要截断,那么你想要:
static double TruncateToSignificantDigits(this double d, int digits){ if(d == 0) return 0; double scale = Math.Pow(10, Math.Floor(Math.Log10(Math.Abs(d))) + 1 - digits); return scale * Math.Truncate(d / scale); }
我已经使用pDaddy的sigfig函数了几个月,发现了一个错误。 你不能把一个负数的日志,所以如果d是负数的结果是NaN。
以下更正了错误:
public static double SetSigFigs(double d, int digits) { if(d == 0) return 0; decimal scale = (decimal)Math.Pow(10, Math.Floor(Math.Log10(Math.Abs(d))) + 1); return (double) (scale * Math.Round((decimal)d / scale, digits)); }
这听起来像你不希望四舍五入到十位小数 – 你想要四舍五入有效数字。 因此,在你的例子中,你想要0.086到一个有效数字,而不是一个小数位。
现在,由于存储双精度的方式,使用双精度和四舍五入成为有效数字是有问题的。 例如,你可以将0.12舍入到接近 0.1的值,但是0.1不能完全表示为double值。 你确定你不应该使用小数? 或者,这实际上是为了显示的目的? 如果是出于显示的目的,我怀疑你应该直接将double转换为相关有效数字的string。
如果你能回答这些问题,我可以尝试拿出一些合适的代码。 听起来很糟糕,通过将数字转换为“完整”string,然后find第一个有效数字(然后在此之后采取适当的舍入操作),将其转换为若干有效数字作为string,可能是最好的方式。
如果是用于显示的目的(如你在Jon Skeet的回答中所述),你应该使用Gn 格式说明符 。 其中n是有效数字的数量 – 正是你在之后。
如果您需要3位有效数字(打印输出在每行的注释中),请使用以下示例:
Console.WriteLine(1.2345e-10.ToString("G3"));//1.23E-10 Console.WriteLine(1.2345e-5.ToString("G3")); //1.23E-05 Console.WriteLine(1.2345e-4.ToString("G3")); //0.000123 Console.WriteLine(1.2345e-3.ToString("G3")); //0.00123 Console.WriteLine(1.2345e-2.ToString("G3")); //0.0123 Console.WriteLine(1.2345e-1.ToString("G3")); //0.123 Console.WriteLine(1.2345e2.ToString("G3")); //123 Console.WriteLine(1.2345e3.ToString("G3")); //1.23E+03 Console.WriteLine(1.2345e4.ToString("G3")); //1.23E+04 Console.WriteLine(1.2345e5.ToString("G3")); //1.23E+05 Console.WriteLine(1.2345e10.ToString("G3")); //1.23E+10
我发现P爸爸和埃里克的方法有两个错误。 这就解决了Andrew Hancox在这个问答中所提出的精度错误。 圆方向也有问题。 1050有两位有效数字不是1000.0,是1100.0。 四舍五入修复了MidpointRounding.AwayFromZero。
static void Main(string[] args) { double x = RoundToSignificantDigits(1050, 2); // Old = 1000.0, New = 1100.0 double y = RoundToSignificantDigits(5084611353.0, 4); // Old = 5084999999.999999, New = 5085000000.0 double z = RoundToSignificantDigits(50.846, 4); // Old = 50.849999999999994, New = 50.85 } static double RoundToSignificantDigits(double d, int digits) { if (d == 0.0) { return 0.0; } else { double leftSideNumbers = Math.Floor(Math.Log10(Math.Abs(d))) + 1; double scale = Math.Pow(10, leftSideNumbers); double result = scale * Math.Round(d / scale, digits, MidpointRounding.AwayFromZero); // Clean possible precision error. if ((int)leftSideNumbers >= digits) { return Math.Round(result, 0, MidpointRounding.AwayFromZero); } else { return Math.Round(result, digits - (int)leftSideNumbers, MidpointRounding.AwayFromZero); } } }
正如Jon Skeet所说:在文本领域中更好地处理这个问题。 作为一个规则:为了显示的目的,不要尝试四舍五入/改变你的浮点值,它从来没有完全正常工作。 显示是一个次要的问题,你应该处理任何特殊的格式要求,如使用string。
我几年前实施的解决scheme,已经certificate非常可靠。 它已经过彻底的testing,performance也相当不错。 执行时间比P Daddy / Eric的解决scheme长大约5倍。
input+输出在代码中给出的例子。
using System; using System.Text; namespace KZ.SigDig { public static class SignificantDigits { public static string DecimalSeparator; static SignificantDigits() { System.Globalization.CultureInfo ci = System.Threading.Thread.CurrentThread.CurrentCulture; DecimalSeparator = ci.NumberFormat.NumberDecimalSeparator; } /// <summary> /// Format a double to a given number of significant digits. /// </summary> /// <example> /// 0.086 -> "0.09" (digits = 1) /// 0.00030908 -> "0.00031" (digits = 2) /// 1239451.0 -> "1240000" (digits = 3) /// 5084611353.0 -> "5085000000" (digits = 4) /// 0.00000000000000000846113537656557 -> "0.00000000000000000846114" (digits = 6) /// 50.8437 -> "50.84" (digits = 4) /// 50.846 -> "50.85" (digits = 4) /// 990.0 -> "1000" (digits = 1) /// -5488.0 -> "-5000" (digits = 1) /// -990.0 -> "-1000" (digits = 1) /// 0.0000789 -> "0.000079" (digits = 2) /// </example> public static string Format(double number, int digits, bool showTrailingZeros = true, bool alwaysShowDecimalSeparator = false) { if (Double.IsNaN(number) || Double.IsInfinity(number)) { return number.ToString(); } string sSign = ""; string sBefore = "0"; // Before the decimal separator string sAfter = ""; // After the decimal separator if (number != 0d) { if (digits < 1) { throw new ArgumentException("The digits parameter must be greater than zero."); } if (number < 0d) { sSign = "-"; number = Math.Abs(number); } // Use scientific formatting as an intermediate step string sFormatString = "{0:" + new String('#', digits) + "E0}"; string sScientific = String.Format(sFormatString, number); string sSignificand = sScientific.Substring(0, digits); int exponent = Int32.Parse(sScientific.Substring(digits + 1)); // (the significand now already contains the requested number of digits with no decimal separator in it) StringBuilder sFractionalBreakup = new StringBuilder(sSignificand); if (!showTrailingZeros) { while (sFractionalBreakup[sFractionalBreakup.Length - 1] == '0') { sFractionalBreakup.Length--; exponent++; } } // Place decimal separator (insert zeros if necessary) int separatorPosition = 0; if ((sFractionalBreakup.Length + exponent) < 1) { sFractionalBreakup.Insert(0, "0", 1 - sFractionalBreakup.Length - exponent); separatorPosition = 1; } else if (exponent > 0) { sFractionalBreakup.Append('0', exponent); separatorPosition = sFractionalBreakup.Length; } else { separatorPosition = sFractionalBreakup.Length + exponent; } sBefore = sFractionalBreakup.ToString(); if (separatorPosition < sBefore.Length) { sAfter = sBefore.Substring(separatorPosition); sBefore = sBefore.Remove(separatorPosition); } } string sReturnValue = sSign + sBefore; if (sAfter == "") { if (alwaysShowDecimalSeparator) { sReturnValue += DecimalSeparator + "0"; } } else { sReturnValue += DecimalSeparator + sAfter; } return sReturnValue; } } }
Math.Round()在双打上是有缺陷的(参见文档中的调用者注释)。 稍后将舍入数乘以十进制指数的步骤将在尾部数字中引入更多的浮点错误。 使用另一个Round()作为@Rowanto不会可靠地帮助和遭受其他问题。 然而,如果你愿意通过十进制去,那么Math.Round()是可靠的,正如乘以10的幂除以:
static ClassName() { powersOf10 = new decimal[28 + 1 + 28]; powersOf10[28] = 1; decimal pup = 1, pdown = 1; for (int i = 1; i < 29; i++) { pup *= 10; powersOf10[i + 28] = pup; pdown /= 10; powersOf10[28 - i] = pdown; } } /// <summary>Powers of 10 indexed by power+28. These are all the powers /// of 10 that can be represented using decimal.</summary> static decimal[] powersOf10; static double RoundToSignificantDigits(double v, int digits) { if (v == 0.0 || Double.IsNaN(v) || Double.IsInfinity(v)) { return v; } else { int decimal_exponent = (int)Math.Floor(Math.Log10(Math.Abs(v))) + 1; if (decimal_exponent < -28 + digits || decimal_exponent > 28 - digits) { // Decimals won't help outside their range of representation. // Insert flawed Double solutions here if you like. return v; } else { decimal d = (decimal)v; decimal scale = powersOf10[decimal_exponent + 28]; return (double)(scale * Math.Round(d / scale, digits, MidpointRounding.AwayFromZero)); } } }
这个问题类似于你所问的问题:
在C#中使用有效数字格式化数字
这样你可以做到以下几点:
double Input2 = 234.004223; string Result2 = Math.Floor(Input2) + Convert.ToDouble(String.Format("{0:G1}", Input2 - Math.Floor(Input2))).ToString("R6");
四舍五入为有效数字。
让inputNumber
是需要在小数点后用significantDigitsRequired
转换的input,然后significantDigitsResult
是下面的伪代码的答案。
integerPortion = Math.truncate(**inputNumber**) decimalPortion = myNumber-IntegerPortion if( decimalPortion <> 0 ) { significantDigitsStartFrom = Math.Ceil(-log10(decimalPortion)) scaleRequiredForTruncation= Math.Pow(10,significantDigitsStartFrom-1+**significantDigitsRequired**) **siginficantDigitsResult** = integerPortion + ( Math.Truncate (decimalPortion*scaleRequiredForTruncation))/scaleRequiredForTruncation } else { **siginficantDigitsResult** = integerPortion }
这是我在C ++中做的事情
/* I had this same problem I was writing a design sheet and the standard values were rounded. So not to give my values an advantage in a later comparison I need the number rounded, so I wrote this bit of code. It will round any double to a given number of significant figures. But I have a limited range written into the subroutine. This is to save time as my numbers were not very large or very small. But you can easily change that to the full double range, but it will take more time. Ross Mckinstray rmckinstray01@gmail.com */ #include <iostream> #include <fstream> #include <string> #include <math.h> #include <cmath> #include <iomanip> #using namespace std; double round_off(double input, int places) { double roundA; double range = pow(10, 10); // This limits the range of the rounder to 10/10^10 - 10*10^10 if you want more change range; for (double j = 10/range; j< 10*range;) { if (input >= j && input < j*10){ double figures = pow(10, places)/10; roundA = roundf(input/(j/figures))*(j/figures); } j = j*10; } cout << "\n in sub after loop"; if (input <= 10/(10*10) && input >= 10*10) { roundA = input; cout << "\nDID NOT ROUND change range"; } return roundA; } int main() { double number, sig_fig; do { cout << "\nEnter number "; cin >> number; cout << "\nEnter sig_fig "; cin >> sig_fig; double output = round_off(number, sig_fig); cout << setprecision(10); cout << "\n I= " << number; cout << "\nr= " <<output; cout << "\nEnter 0 as number to exit loop"; } while (number != 0); return 0; }
希望我没有改变任何格式化它。
我已经做了:
int integer1 = Math.Round(double you want to round, significant figures you want to round to)