Roc*_*cco 66 c# math rounding significant-digits
如果我有一个双(234.004223)等,我想将其舍入为C#中的x位有效数字.
到目前为止,我只能找到舍入到x小数位的方法,但如果数字中有任何0,则只会删除精度.
例如,0.086到一位小数位变为0.1,但我希望它保持在0.08.
P D*_*ddy 83
该框架没有内置函数来将(或截断,如在您的示例中)舍入为多个有效数字.但是,您可以采用的一种方法是缩放数字,使您的第一个有效数字位于小数点后面,圆形(或截断),然后缩小.以下代码应该可以解决问题:
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);
}
Run Code Online (Sandbox Code Playgroud)
如果在您的示例中,您确实要截断,那么您需要:
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);
}
Run Code Online (Sandbox Code Playgroud)
Eri*_*ric 21
我一直在使用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));
}
Run Code Online (Sandbox Code Playgroud)
Jon*_*eet 18
这听起来像你根本不想舍入到x小数位 - 你想要舍入到x有效数字.因此,在您的示例中,您希望将0.086舍入为一个有效数字,而不是一个小数位.
现在,由于存储双精度的方式,使用双精度和舍入到多个有效数字是有问题的.例如,您可以将0.12舍入到接近 0.1的值,但0.1不能完全表示为double.你确定你不应该使用小数吗?或者,这实际上是用于显示目的吗?如果是出于显示目的,我怀疑你应该将double直接转换为具有相关有效位数的字符串.
如果你能回答这些问题,我可以尝试提出一些适当的代码.听起来很糟糕,通过将数字转换为"完整"字符串然后找到第一个有效数字(然后在此之后采取适当的舍入操作)转换为多个有效数字作为字符串可能是最好的方法.
far*_*ast 14
如果它是出于显示目的(正如你在对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
Run Code Online (Sandbox Code Playgroud)
小智 6
我在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);
}
}
}
Run Code Online (Sandbox Code Playgroud)
正如 Jon Skeet 所提到的:在文本领域更好地处理这个问题。通常:出于显示目的,不要尝试舍入/更改您的浮点值,它永远不会 100% 有效。显示是次要问题,您应该处理任何特殊的格式要求,例如处理字符串。
我几年前实施的以下解决方案已被证明非常可靠。它已经过彻底的测试,而且性能也很好。执行时间比 P Daddy / Eric 的解决方案长约 5 倍。
下面在代码中给出的输入 + 输出示例。
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;
}
}
}
Run Code Online (Sandbox Code Playgroud)
我同意Jon 的评价精神:
听起来很糟糕,通过将数字转换为“完整”字符串,然后找到第一个有效数字(然后在此之后采取适当的舍入操作),将数字转换为字符串,这可能是最好的方法。
我需要对近似和非性能关键的计算目的进行有效数字舍入,并且通过“G”格式的格式解析往返就足够了:
public static double RoundToSignificantDigits(this double value, int numberOfSignificantDigits)
{
return double.Parse(value.ToString("G" + numberOfSignificantDigits));
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
56266 次 |
| 最近记录: |