임의의 유효 자릿수로 반올림


83

모든 숫자 (정수> 0이 아닌)를 N 유효 숫자로 반올림하려면 어떻게해야 합니까?

예를 들어 유효 숫자 3 개로 반올림하려면 다음을 사용할 수있는 수식을 찾고 있습니다.

1,239,451 및 1,240,000 반환

12.1257 및 12.1 반환

.0681 및 .0681 반환

5 및 5 반환

당연히 알고리즘은 시작 일지라도 3 개 중 N 개만 처리하도록 하드 코딩되어서는 안됩니다.


질문이 너무 일반적인 것 같습니다. 다른 프로그래밍 언어는이를 수행하는 다른 표준 기능을 가지고 있습니다. 실제로 바퀴를 재발 명하기에 적절하지 않습니다.
Johnny Wong

답변:


106

다음은 다른 답변에있는 12.100000000000001 버그가없는 Java의 동일한 코드입니다.

또한 반복되는 코드를 제거 하고 완료 power시 부동 문제를 방지하기 위해 정수 유형으로 변경 n - d했으며 긴 중간을 더 명확하게했습니다.

이 버그는 큰 수에 작은 수를 곱하여 발생했습니다. 대신 비슷한 크기의 두 숫자를 나눕니다.

편집은
더 많은 버그가 수정되었습니다. NaN이 발생하므로 0에 대한 검사를 추가했습니다. 함수가 실제로 음수로 작동하도록 함 (음수의 로그는 복소수이므로 원래 코드는 음수를 처리하지 않음)

public static double roundToSignificantFigures(double num, int n) {
    if(num == 0) {
        return 0;
    }

    final double d = Math.ceil(Math.log10(num < 0 ? -num: num));
    final int power = n - (int) d;

    final double magnitude = Math.pow(10, power);
    final long shifted = Math.round(num*magnitude);
    return shifted/magnitude;
}

2
제 답변을 받아 주셔서 감사합니다. 나는 내 대답이 질문 후 1 년 이상이라는 것을 깨달았습니다. 이것이 stackoverflow가 멋진 이유 중 하나입니다. 유용한 정보를 찾을 수 있습니다!
Pyrolistical

2
반올림 한계에 가까운 값의 경우 약간 실패 할 수 있습니다. 예를 들어 1.255를 유효 자릿수 3 개로 반올림하면 1.26이 반환되지만 1.25가 반환됩니다. 왜냐하면 1.255 * 100.0이 125.499999이기 때문입니다 ...하지만 이것은 복식으로 작업 할 때 예상됩니다
cquezel

와,이게 오래된 건 알지만 사용하려고합니다. 3 개의 유효 숫자로 표시하고 싶은 플로트가 있습니다. float 값이 1.0이면 메서드를 호출하지만 float를 double로 캐스팅하더라도 여전히 1.0으로 반환됩니다. 1로 반환하고 싶습니다. 아이디어가 있습니까?
Steve W

3
이 자바 조각은 공식 안드로이드의 예에서 끝나는 android.googlesource.com/platform/development/+/fcf4286/samples/...
호기심 샘

1
완벽하지 않습니다. num = -7999999.999999992 및 n = 2의 경우 -7999999.999999999를 반환하지만 -8000000이어야합니다.
Duncan Calvert

16

다음은 짧고 멋진 JavaScript 구현입니다.

function sigFigs(n, sig) {
    var mult = Math.pow(10, sig - Math.floor(Math.log(n) / Math.LN10) - 1);
    return Math.round(n * mult) / mult;
}

alert(sigFigs(1234567, 3)); // Gives 1230000
alert(sigFigs(0.06805, 3)); // Gives 0.0681
alert(sigFigs(5, 3)); // Gives 5

하지만 12.1257은 12.126 제공
Pyrolistical

1
좋은 대답 Ates. 아마도 0 경우 반환하는 트리거를 추가 n==0:
sscirrus

Math.log(n) / Math.LN10대신 해야 할 이유 가 Math.log10(n)있습니까?

1
@Lee developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… "이것은 ECMAScript 2015 (ES6) 표준의 일부인 새로운 기술입니다." 따라서 기본적으로 호환성 문제입니다.
Ates Goral 2015-10-23

내가 뭔가를 놓치고 Math.floor(x) == Math.ceil(x) - 1있습니까 , 아니면이 대답이 가정 합니까? 언제 x가 정수가 아니기 때문 입니다. pow함수 의 두 번째 인수는 다음과 같아야합니다 sig - Math.ceil(Math.log(n) / Math.LN10)(또는 그냥 사용 Math.log10)
Paul

15

요약:

double roundit(double num, double N)
{
    double d = log10(num);
    double power;
    if (num > 0)
    {
        d = ceil(d);
        power = -(d-N);
    }
    else
    {
        d = floor(d); 
        power = -(d-N);
    }

    return (int)(num * pow(10.0, power) + 0.5) * pow(10.0, -power);
}

따라서 0이 아닌 첫 번째 숫자의 소수점 자리를 찾은 다음 다음 N-1 숫자를 저장 한 다음 나머지를 기준으로 N 번째 숫자를 반올림해야합니다.

로그를 사용하여 첫 번째 작업을 수행 할 수 있습니다.

log 1239451 = 6.09
log 12.1257 = 1.08
log 0.0681  = -1.16

따라서 숫자가 0보다 크면 로그의 천장을 취하십시오. 숫자가 0 미만인 경우 로그의 바닥을 차지합니다.

이제 숫자가 있습니다 d. 첫 번째 경우 7, ​​두 번째 경우 2, 세 번째 경우 -2입니다.

우리는 세 (d-N)번째 자리 를 반올림해야합니다 . 다음과 같은 것 :

double roundedrest = num * pow(10, -(d-N));

pow(1239451, -4) = 123.9451
pow(12.1257, 1)  = 121.257
pow(0.0681, 4)   = 681

그런 다음 표준 반올림 작업을 수행하십시오.

roundedrest = (int)(roundedrest + 0.5);

그리고 포로를 취소하십시오.

roundednum = pow(roundedrest, -(power))

전력은 위에서 계산 된 전력입니다.


정확성에 관하여 : Pyrolistical의 대답은 실제로 실제 결과에 더 가깝습니다. 그러나 어떤 경우에도 12.1을 정확하게 나타낼 수는 없습니다. 다음과 같이 답변을 인쇄하는 경우 :

System.out.println(new BigDecimal(n));

대답은 다음과 같습니다.

Pyro's: 12.0999999999999996447286321199499070644378662109375
Mine: 12.10000000000000142108547152020037174224853515625
Printing 12.1 directly: 12.0999999999999996447286321199499070644378662109375

따라서 Pyro의 대답을 사용하십시오!


1
이 알고리즘은 부동 소수점 오류가 발생하기 쉽습니다. JavaScript로 구현하면 0.06805-> 0.06810000000000001 및 12.1-> 12.100000000000001
Ates Goral

12.1 자체는 부동 소수점을 사용하여 정확하게 표현할 수 없습니다. 이것은이 알고리즘의 결과가 아닙니다.
Claudiu

1
Java의이 코드는 12.100000000000001을 생성하며 이는 12.1을 정확하게 나타낼 수있는 64 비트 double을 사용합니다.
Pyrolistical

4
64 비트인지 128 비트인지는 중요하지 않습니다. 유한 한 2 거듭 제곱 합을 사용하여 분수 1/10을 나타낼 수 없습니다. 이것이 부동 소수점 숫자가 표현되는 방식입니다
Claudiu

2
사람들을 위해 기본적으로 Pyrolistical의 대답은 내 것보다 더 정확하므로 부동 소수점 숫자 인쇄 알고리즘은 '12 .100000000000001 '대신 '12 .1'을 인쇄합니다. 그의 대답은 더 낫습니다. 심지어 '12 .1 '을 정확하게 표현할 수 없다는 기술적으로 정확했습니다.
Claudiu 2011 년

10

"짧고 달콤한"자바 스크립트 구현이 아님

Number(n).toPrecision(sig)

예 :

alert(Number(12345).toPrecision(3)

?

죄송합니다. 저는 여기서 우스꽝스럽지 않습니다. Claudiu의 "roundit"함수와 JavaScript의 .toPrecision을 사용하면 다른 결과를 얻을 수 있지만 마지막 숫자 만 반올림 할 수 있습니다.

자바 스크립트 :

Number(8.14301).toPrecision(4) == 8.143

.그물

roundit(8.14301,4) == 8.144

1
Number(814301).toPrecision(4) == "8.143e+5". 일반적으로 이것을 사용자에게 보여 주면 원하는 것이 아닙니다.
Zaz

매우 사실 Josh 예, 일반적으로 십진수에 대해서만 .toPrecision ()을 권장하고 허용되는 답변 (편집 포함)은 개별 요구 사항에 따라 사용 / 검토해야합니다.
Justin Wignall

8

Pyrolistical의 (매우 좋은!) 솔루션에는 여전히 문제가 있습니다. Java의 최대 double 값은 10 ^ 308 정도이고 최소값은 10 ^ -324 정도입니다. 따라서 roundToSignificantFigures.NET의 몇 제곱 이내 인 것에 함수 를 적용 할 때 문제가 발생할 수 있습니다 Double.MIN_VALUE. 예를 들어, 전화 할 때

roundToSignificantFigures(1.234E-310, 3);

그러면 변수 power의 값은 3-(-309) = 312가됩니다. 결과적으로 변수 magnitudeInfinity이고 그 이후부터는 모두 쓰레기가됩니다. 다행히도 이것은 극복 할 수없는 문제가 아닙니다 . 넘쳐나 는 요인 일뿐 magnitude입니다. 정말 중요한 것은 제품 num * magnitude 이며 넘치지 않습니다. 이 문제를 해결하는 한 가지 방법은 곱셈 magintude을 두 단계로 나누는 것입니다.


 public static double roundToNumberOfSignificantDigits(double num, int n) {

    final double maxPowerOfTen = Math.floor(Math.log10(Double.MAX_VALUE));

    if(num == 0) {
        return 0;
    }

    final double d = Math.ceil(Math.log10(num < 0 ? -num: num));
    final int power = n - (int) d;

    double firstMagnitudeFactor = 1.0;
    double secondMagnitudeFactor = 1.0;
    if (power > maxPowerOfTen) {
        firstMagnitudeFactor = Math.pow(10.0, maxPowerOfTen);
        secondMagnitudeFactor = Math.pow(10.0, (double) power - maxPowerOfTen);
    } else {
        firstMagnitudeFactor = Math.pow(10.0, (double) power);
    }

    double toBeRounded = num * firstMagnitudeFactor;
    toBeRounded *= secondMagnitudeFactor;

    final long shifted = Math.round(toBeRounded);
    double rounded = ((double) shifted) / firstMagnitudeFactor;
    rounded /= secondMagnitudeFactor;
    return rounded;
}


6

이 자바 솔루션은 어떻습니까?

double roundToSignificantFigure (double num, int precision) {
 new BigDecimal (num) 반환
            .round (new MathContext (precision, RoundingMode.HALF_EVEN))
            .doubleValue (); 
}

3

다음은 음수를 처리하는 Ates의 JavaScript 수정 버전입니다.

function sigFigs(n, sig) {
    if ( n === 0 )
        return 0
    var mult = Math.pow(10,
        sig - Math.floor(Math.log(n < 0 ? -n: n) / Math.LN10) - 1);
    return Math.round(n * mult) / mult;
 }

2

이것은 5 년 늦었지만 여전히 같은 문제가있는 다른 사람들과 공유하겠습니다. 간단하고 코드 측면에서 계산이 없기 때문에 좋아합니다. 자세한 내용은 중요 수치표시하기위한 기본 제공 방법을 참조하세요 .

이것은 당신이 그것을 인쇄하려는 경우입니다.

public String toSignificantFiguresString(BigDecimal bd, int significantFigures){
    return String.format("%."+significantFigures+"G", bd);
}

변환하려는 경우입니다.

public BigDecimal toSignificantFigures(BigDecimal bd, int significantFigures){
    String s = String.format("%."+significantFigures+"G", bd);
    BigDecimal result = new BigDecimal(s);
    return result;
}

다음은 실제 작동의 예입니다.

BigDecimal bd = toSignificantFigures(BigDecimal.valueOf(0.0681), 2);

과학적 표기법으로 "큰"숫자를 표시합니다 (예 : 15k를 1.5e04로).
matt

2

자바 스크립트 :

Number( my_number.toPrecision(3) );

Number함수는 양식의 출력 "8.143e+5""814300".


1

손으로하는 것처럼 코딩을 해보 셨나요?

  1. 숫자를 문자열로 변환
  2. 문자열의 시작 부분에서 시작하여 숫자를 계산합니다. 선행 0은 중요하지 않으며 나머지는 모두 중요합니다.
  3. "n 번째"자리에 도달하면 다음 자리를 미리보고 5 이상이면 반올림합니다.
  4. 후행 숫자를 모두 0으로 바꿉니다.

1

[2009 년 10 월 26 일 수정]

기본적으로 N 개의 유효 소수 자릿수 :

• 숫자에 10 N을 곱합니다.
• 0.5를 더합니다.
• 분수 자릿수를 자릅니다 (즉, 결과를 정수로 자름).
• 10 N 으로 나눕니다.

N 개의 유효 정수 (비 분수) 자릿수 :

• 숫자를 10 N 으로 나눕니다. •
0.5를 더합니다.
• 분수 자릿수를 자릅니다 (즉, 결과를 정수로 자름).
• 10 N을 곱합니다.

예를 들어 "INT"(정수 잘림) 연산자가있는 모든 계산기에서이 작업을 수행 할 수 있습니다.


아니. 질문을 다시 읽으십시오. 잘못 123,951 얻을 것이다 당신의 알고리즘을 사용하여 3 시그 무화과 1239451
Pyrolistical

네, 소수 자릿수 (소수점 오른쪽)와 정수 자릿수 (왼쪽)로 반올림하는 것을 구별하기 위해 수정했습니다 .
David R Tribble

1
/**
 * Set Significant Digits.
 * @param value value
 * @param digits digits
 * @return
 */
public static BigDecimal setSignificantDigits(BigDecimal value, int digits) {
    //# Start with the leftmost non-zero digit (e.g. the "1" in 1200, or the "2" in 0.0256).
    //# Keep n digits. Replace the rest with zeros.
    //# Round up by one if appropriate.
    int p = value.precision();
    int s = value.scale();
    if (p < digits) {
        value = value.setScale(s + digits - p); //, RoundingMode.HALF_UP
    }
    value = value.movePointRight(s).movePointLeft(p - digits).setScale(0, RoundingMode.HALF_UP)
        .movePointRight(p - digits).movePointLeft(s);
    s = (s > (p - digits)) ? (s - (p - digits)) : 0;
    return value.setScale(s);
}

1

다음은 Visual Basic.NET의 Pyrolistical (현재 최고 답변) 코드입니다.

Public Shared Function roundToSignificantDigits(ByVal num As Double, ByVal n As Integer) As Double
    If (num = 0) Then
        Return 0
    End If

    Dim d As Double = Math.Ceiling(Math.Log10(If(num < 0, -num, num)))
    Dim power As Integer = n - CInt(d)
    Dim magnitude As Double = Math.Pow(10, power)
    Dim shifted As Double = Math.Round(num * magnitude)
    Return shifted / magnitude
End Function

0

이것은 내가 VB에서 생각 해낸 것입니다.

Function SF(n As Double, SigFigs As Integer)
    Dim l As Integer = n.ToString.Length
    n = n / 10 ^ (l - SigFigs)
    n = Math.Round(n)
    n = n * 10 ^ (l - SigFigs)
    Return n
End Function

0

return new BigDecimal(value, new MathContext(significantFigures, RoundingMode.HALF_UP)).doubleValue();


0

Go에서 필요했습니다. Go 표준 라이브러리의 부족으로 인해 약간 복잡했습니다 math.Round()(go1.10 이전). 그래서 나도 그것을 채찍질해야했다. 다음은 Pyrolistical의 탁월한 답변에 대한 번역입니다 .

// TODO: replace in go1.10 with math.Round()
func round(x float64) float64 {
    return float64(int64(x + 0.5))
}

// SignificantDigits rounds a float64 to digits significant digits.
// Translated from Java at https://stackoverflow.com/a/1581007/1068283
func SignificantDigits(x float64, digits int) float64 {
    if x == 0 {
        return 0
    }

    power := digits - int(math.Ceil(math.Log10(math.Abs(x))))
    magnitude := math.Pow(10, float64(power))
    shifted := round(x * magnitude)
    return shifted / magnitude
}

이것은 수수께끼의 반대표를 얻었습니다! 하지만 오류나 문제를 찾을 수 없습니다. 여기서 무슨 일이 일어나고 있습니까?
Michael Hampton

0

FloatToStrF를 사용하여 10의 거듭 제곱 등으로 이러한 모든 계산을 피할 수 있습니다.

FloatToStrF를 사용하면 출력 된 값 (문자열이 됨)에서 정밀도 (유효 숫자 수)를 선택할 수 있습니다. 물론 여기에 StrToFloat를 적용하여 반올림 된 값을 float로 얻을 수 있습니다.

여길 봐:

http://docs.embarcadero.com/products/rad_studio/delphiAndcpp2009/HelpUpdate2/EN/html/delphivclwin32/SysUtils_FloatToStrF@Extended@TFloatFormat@Integer@Integer.html


-1
public static double roundToSignificantDigits(double num, int n) {
    return Double.parseDouble(new java.util.Formatter().format("%." + (n - 1) + "e", num).toString());
}

이 코드는 반올림 기능으로 전환되는 내장 서식 지정 기능을 사용합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.