C# 将双精度舍入到 x 位有效数字

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/374316/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-04 01:03:25  来源:igfitidea点击:

Round a double to x significant figures

c#mathroundingsignificant-digits

提问by Rocco

If I have a double (234.004223), etc., I would like to round this to x significant digits in C#.

如果我有一个双精度 (234.004223) 等,我想在 C# 中将其四舍五入为 x 位有效数字。

So far I can only find ways to round to x decimal places, but this simply removes the precision if there are any 0s in the number.

到目前为止,我只能找到四舍五入到 x 位小数的方法,但是如果数字中有任何 0,这只会删除精度。

For example, 0.086 to one decimal place becomes 0.1, but I would like it to stay at 0.08.

例如,小数点后一位的 0.086 变为 0.1,但我希望它保持在 0.08。

采纳答案by P Daddy

The framework doesn't have a built-in function to round (or truncate, as in your example) to a number of significant digits. One way you can do this, though, is to scale your number so that your first significant digit is right after the decimal point, round (or truncate), then scale back. The following code should do the trick:

该框架没有内置函数来舍入(或截断,如您的示例)为多个有效数字。但是,您可以这样做的一种方法是缩放您的数字,以便您的第一个有效数字正好在小数点之后,舍入(或截断),然后再缩小。以下代码应该可以解决问题:

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);
}

If, as in your example, you really want to truncate, then you want:

如果像您的示例一样,您真的想截断,那么您需要:

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);
}

回答by Bravax

This question is similiar to the one you're asking:

这个问题与您要问的问题相似:

Formatting numbers with significant figures in C#

在 C# 中使用有效数字格式化数字

Thus you could do the following:

因此,您可以执行以下操作:

double Input2 = 234.004223;
string Result2 = Math.Floor(Input2) + Convert.ToDouble(String.Format("{0:G1}", Input2 - Math.Floor(Input2))).ToString("R6");

Rounded to 1 significant digit.

四舍五入为 1 位有效数字。

回答by Jon Skeet

It sounds to me like you don't want to round to x decimal places at all - you want to round to x significant digits. So in your example, you want to round 0.086 to one significant digit, not one decimal place.

在我看来,您根本不想四舍五入到 x 位小数 - 您想四舍五入到 x 位有效数字。因此,在您的示例中,您希望将 0.086 舍入为一位有效数字,而不是一位小数。

Now, using a double and rounding to a number of significant digits is problematic to start with, due to the way doubles are stored. For instance, you could round 0.12 to something closeto 0.1, but 0.1 isn't exactly representable as a double. Are you sure you shouldn't actually be using a decimal? Alternatively, is this actually for display purposes? If it's for display purposes, I suspect you should actually convert the double directly to a string with the relevant number of significant digits.

现在,由于双精度的存储方式,开始使用双精度数并四舍五入到多个有效数字是有问题的。例如,您可以将 0.12 舍入到接近0.1 的程度,但 0.1 不能完全表示为双精度值。你确定你真的不应该使用小数吗?或者,这实际上是为了显示目的吗?如果是出于显示目的,我怀疑您实际上应该将 double 直接转换为具有相关有效数字位数的字符串。

If you can answer those points, I can try to come up with some appropriate code. Awful as it sounds, converting to a number of significant digits as a string by converting the number to a "full" string and then finding the first significant digit (and then taking appropriate rounding action after that) may well be the best way to go.

如果你能回答这些问题,我可以试着想出一些合适的代码。听起来很糟糕,通过将数字转换为“完整”字符串然后找到第一个有效数字(然后在此之后采取适当的舍入操作)来将多个有效数字转换为字符串可能是最好的方法.

回答by lakshmanaraj

Let inputNumberbe input that needs to be converted with significantDigitsRequiredafter decimal point, then significantDigitsResultis the answer to the following pseudo code.

inputNumber输入需要转换为significantDigitsRequired小数点后,然后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

}

回答by Eric

I've been using pDaddy's sigfig function for a few months and found a bug in it. You cannot take the Log of a negative number, so if d is negative the results is NaN.

几个月来我一直在使用 pDaddy 的 sigfig 功能,并在其中发现了一个错误。您不能取负数的对数,因此如果 d 为负,则结果为 NaN。

The following corrects the bug:

以下更正错误:

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));
}

回答by Javier

I just did:

我已经做了:

int integer1 = Math.Round(double you want to round, 
    significant figures you want to round to)

回答by Rowanto

I found two bugs in the methods of P Daddy and Eric. This solves for example the precision error that was presented by Andrew Hancox in this Q&A. There was also a problem with round directions. 1050 with two significant figures isn't 1000.0, it's 1100.0. The rounding was fixed with MidpointRounding.AwayFromZero.

我在P Daddy和Eric的方法中发现了两个错误。例如,这解决了 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);
    }
  }
}

回答by user1275860

Here is something I did in C++

这是我在 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
    [email protected]
*/

#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 << "\n r= " <<output;
        cout << "\nEnter 0 as number to exit loop";
    }
    while (number != 0);

    return 0;
}

Hopefully I did not change anything formatting it.

希望我没有改变任何格式化它。

回答by farfareast

If it is for display purposes (as you state in the comment to Jon Skeet's answer), you should use Gn format specifier. Where nis the number of significant digits - exactly what you are after.

如果是出于显示目的(正如您在对 Jon Skeet 的回答的评论中所述),您应该使用 Gn格式说明符。其中n是有效数字的位数 - 正是您所追求的。

Here is the the example of usage if you want 3 significant digits (printed output is in the comment of each line):

如果您想要 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

回答by Oliver Bock

Math.Round() on doubles is flawed (see Notes to Callers in its documentation). The later step of multiplying the rounded number back up by its decimal exponent will introduce further floating point errors in the trailing digits. Using another Round() as @Rowanto does won't reliably help and suffers from other problems. However if you're willing to go via decimal then Math.Round() is reliable, as is multiplying and dividing by powers of 10:

双打上的 Math.Round() 有缺陷(请参阅其文档中的呼叫者注释)。将舍入后的数乘以其十进制指数的后面步骤将在尾随数字中引入更多的浮点错误。像@Rowanto 一样使用另一个 Round() 不会可靠地提供帮助,并且会遇到其他问题。但是,如果您愿意使用十进制,那么 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));
        }
    }
}