2 배에서 x 개의 유효 숫자로 반올림
이중 (234.004223) 등이있는 경우 C #의 유효 숫자 x로 반올림하고 싶습니다.
지금까지 소수점 x 자리로 반올림하는 방법 만 찾을 수 있지만 숫자에 0이 있으면 정밀도가 제거됩니다.
예를 들어 0.086에서 소수점 1 자리까지 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을 소수점 하나가 아닌 하나의 유효 자릿수로 반올림하려고합니다.
이제 double을 사용하고 유효 자릿수로 반올림하는 것은 double이 저장되는 방식으로 인해 시작하는 데 문제가 있습니다. 예를 들어 0.12를 0.1에 가까운 값 으로 반올림 할 수 있지만 0.1은 정확히 double로 표현할 수 없습니다. 실제로 소수점을 사용해서는 안 되나요? 또는 이것은 실제로 표시 목적입니까? 표시 목적의 경우 실제로 double을 관련 유효 숫자가있는 문자열로 직접 변환해야한다고 생각합니다.
그 점에 답할 수 있다면 적절한 코드를 생각해 볼 수 있습니다. 끔찍하게 들리 겠지만, 숫자를 "전체"문자열로 변환하여 문자열과 같은 유효 숫자로 변환 한 다음 첫 번째 유효 숫자를 찾은 다음 적절한 반올림 작업을 수행하는 것이 가장 좋은 방법 일 수 있습니다. .
표시 목적인 경우 (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입니다. 반올림은 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이 언급했듯이 텍스트 영역에서 더 잘 처리하십시오. 일반적으로 표시 목적을 위해 부동 소수점 값을 반올림하거나 변경하지 마십시오. 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;
}
}
}
복식에 대한 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));
}
}
}
이 질문은 당신이 묻는 질문과 유사합니다.
따라서 다음을 수행 할 수 있습니다.
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
}
나는 Jon의 평가 정신에 동의합니다 .
끔찍하게 들리 겠지만, 숫자를 "전체"문자열로 변환하여 문자열과 같은 유효 숫자로 변환 한 다음 첫 번째 유효 숫자를 찾은 다음 적절한 반올림 작업을 수행하는 것이 가장 좋은 방법 일 수 있습니다. .
대략 적이고 성능이 중요하지 않은 계산 목적을 위해 유효 자릿수 반올림이 필요 했으며 "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;
}
다행히도 형식을 변경하지 않았 으면합니다.
참고URL : https://stackoverflow.com/questions/374316/round-a-double-to-x-significant-figures
'Development Tip' 카테고리의 다른 글
dplyr 행의 하위 집합에서 여러 열을 변경 / 바꾸기 (0) | 2020.11.23 |
---|---|
스크롤 뷰 내부의 동적 크기 컨트롤러로 컨테이너 뷰 크기 조정 (0) | 2020.11.23 |
배열 이름을 복수 또는 단수로 지정합니까? (0) | 2020.11.23 |
Python에서 읽기 전용 클래스 속성을 만드는 방법은 무엇입니까? (0) | 2020.11.23 |
IntelliJ가 "새"표현식에서 생성자 매개 변수를 자동 완성 할 수 있습니까? (0) | 2020.11.23 |