문제

Double (234.004223) 등이 있으면 C#의 X 중요한 숫자로이를 반올림하고 싶습니다.

지금까지 나는 소수점 이하 x로 반올림하는 방법을 찾을 수 있지만, 숫자에 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));
}

당신이 전혀 소수점 이하 X로 돌아가고 싶지 않은 것처럼 들립니다. 당신은 X 중요한 숫자로 반올림하고 싶습니다. 따라서 당신의 예에서, 당신은 0.086을 하나의 소수점이 아닌 하나의 중요한 숫자로 반올림하려고합니다.

이제 더블과 여러 숫자로 반올림하는 것은 복식이 저장되는 방식으로 인해 처음부터 문제가됩니다. 예를 들어, 0.12를 무언가로 반올림 할 수 있습니다 닫다 0.1이지만 0.1은 이중으로 정확하게 표현할 수 없습니다. 실제로 소수점을 사용해서는 안되는가? 또는 이것은 실제로 디스플레이 목적을위한 것입니까? 디스플레이 목적이라면 실제로는 두 배를 관련 숫자 수의 문자열로 직접 변환해야한다고 생각합니다.

그 점에 답할 수 있다면, 나는 적절한 코드를 만들어 낼 수 있습니다. 숫자를 "전체"문자열로 변환 한 다음 첫 번째 중요한 숫자를 찾은 다음 적절한 반올림 조치를 찾는 데있어 가장 좋은 방법 일 수 있습니다. .

디스플레이 목적으로 (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 Daddy와 Eric의 방법에서 두 가지 버그를 발견했습니다. 이것은 예를 들어이 Q & A에서 Andrew Hancox가 제시 한 정밀 오차를 해결합니다. 둥근 방향에도 문제가있었습니다. 두 개의 중요한 수치를 가진 1050은 1000.0이 아니며 1100.0입니다. 반올림은 중간 지구로 고정되었습니다.

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가 언급 한 바와 같이 : 텍스트 도메인에서 이것을 더 잘 처리하십시오. 일반적으로 : 디스플레이 목적으로 부동 소수점 값을 반올림 / 변경하려고 시도하지 마십시오. 100%작동하지 않습니다. 디스플레이는 2 차 관심사이며 문자열 작업과 같은 특수 서식 요구 사항을 처리해야합니다.

아래의 솔루션은 몇 년 전에 구현되었으며 매우 신뢰할 수있는 것으로 입증되었습니다. 철저히 테스트되었으며 꽤 잘 작동합니다. 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;
        }
    }
}

Doubles의 Math.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");

1 개의 중요한 숫자로 반올림됩니다.

허락하다 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

}

나는의 정신에 동의합니다 존의 평가:

숫자를 "전체"문자열로 변환 한 다음 첫 번째 중요한 숫자를 찾은 다음 적절한 반올림 조치를 찾는 데있어 가장 좋은 방법 일 수 있습니다. .

나는 상당수의 반올림이 필요했다 근사치를 내다 그리고 비 성능-크리티컬 계산 목적과 "g"형식을 통한 형식 패러스 왕복이 충분합니다.

public static double RoundToSignificantDigits(this double value, int numberOfSignificantDigits)
{
    return double.Parse(value.ToString("G" + numberOfSignificantDigits));
}

난 그냥했다:

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

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

    return 0;
}

잘만되면 나는 그것을 형식화하는 어떤 것도 바꾸지 않았다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top