任意の有効桁数への丸め


83

任意の数値(0より大きい整数だけでなく)を有効数字Nに丸めるにはどうすればよいですか?

たとえば、有効数字3桁に四捨五入したい場合は、次の式を使用できます。

1,239,451およびリターン1,240,000

12.1257および12.1を返します

.0681およびリターン.0681

5とリターン5

当然のことながら、アルゴリズムをハードコーディングしてN of 3のみを処理することはできませんが、それが出発点になります。


質問が一般的すぎるようです。プログラミング言語が異なれば、これを行うための標準機能も異なります。確かに車輪の再発明には適切ではありません。
ジョニーウォン

回答:


106

これは、他の回答にある12.100000000000001バグのないJavaの同じコードです。

また、繰り返されるコードを削除powerし、n - d完了時にフローティングの問題を防ぐために整数型に変更し、長い中間体をより明確にしました

このバグは、大きな数と小さな数を掛けることによって引き起こされました。代わりに、同じサイズの2つの数値を分割します。

編集
より多くのバグを修正しました。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が非常にクールである理由の1つです。役立つ情報が見つかります!
パイロリスティック2010年

2
ラウンド制限に近い値の場合、これはわずかに失敗する可能性があることに注意してください。たとえば、1.255を有効数字3桁に丸めると、1.26が返されますが、1.25が返されます。これは、1.255 * 100.0が125.499999であるためです...しかし、これは、doubleを使用する場合に予想されます
cquezel 2014年

うわー、これは古いことは知っていますが、私はそれを使おうとしています。有効数字3桁で表示したいフロートがあります。float値が1.0の場合、メソッドを呼び出しますが、floatをdoubleとしてキャストしても、1.0として返されます。1として返して欲しいです。何かアイデアはありますか?
スティーブW

3
このJavaスニペットは、公式のAndroidサンプルandroid.googlesource.com/platform/development/+/fcf4286/samples/…になります
Curious Sam

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
いい答えアテス。おそらく、次の場合に0を返すトリガーを追加しますn==0:)
sscirrus 2011年

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年

私は何かが足りないのですか、それともこの答えはそれを前提としていMath.floor(x) == Math.ceil(x) - 1ますか?x整数の場合はそうではないからです。pow関数の2番目の引数は次のようになります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);
}

したがって、最初のゼロ以外の桁の小数点以下の桁数を見つけ、次のN-1桁を保存してから、残りに基づいてN番目の桁を丸める必要があります。

最初にログを使用できます。

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

したがって、0より大きい数値の場合は、ログの天井を取ります。数値が0未満の場合は、ログのフロアを取ります。

これdで、最初のケースで7、2番目のケースで2、3番目のケースで-2の数字が得られます。

(d-N)3桁目を丸める必要があります。何かのようなもの:

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

だから、パイロの答えを使ってください!


1
このアルゴリズムは浮動小数点エラーを起こしやすいようです。JavaScriptで実装されている場合は、私が手:0.06805 - > 0.06810000000000001と12.1 - > 12.100000000000001
ATES Goral

12.1自体は、浮動小数点を使用して正確に表すことはできません。これは、このアルゴリズムの結果ではありません。
Claudiu

1
Javaのこのコードは12.100000000000001を生成し、これは12.1を正確に表すことができる64ビットのdoubleを使用しています。
パイロリスティック2009年

4
64ビットか128ビットかは関係ありません。2の累乗の有限和を使用して、分数1/10を表すことはできません。これが、浮動小数点数の表現方法です
Claudiu

2
基本的に、Pyrolisticalの答えは私の答えよりも正確であるため、浮動小数点数の印刷アルゴリズムは「12.100000000000001」ではなく「12.1」を出力します。彼の答えはより良いです、たとえあなたが「12.1」を正確に表すことができないという技術的に正しいとしても。
Claudiu 2011年

10

「短くて甘い」JavaScriptの実装ではありません

Number(n).toPrecision(sig)

例えば

alert(Number(12345).toPrecision(3)

申し訳ありませんが、ここでは面白くありません。Claudiuの「roundit」関数とJavaScriptの.toPrecisionを使用すると、結果が異なりますが、最後の桁が丸められるだけです。

JavaScript:

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

。ネット

roundit(8.14301,4) == 8.144

1
Number(814301).toPrecision(4) == "8.143e+5"。これをユーザーに表示する場合、通常は必要なものではありません。
ザーズ2015年

非常に本当のジョシュはい、私は一般的に10進数に対してのみ.toPrecision()をお勧めします、そして受け入れられた答え(編集あり)はあなたの個々の要件に従って使用/レビューされるべきです。
Justin Wignall 2015

8

Pyrolisticalの(非常に素晴らしい!)ソリューションにはまだ問題があります。Javaの最大double値は10 ^ 308のオーダーであり、最小値は10 ^ -324のオーダーです。したがって、roundToSignificantFigures10の10の累乗以内の何かに関数を適用すると、問題が発生する可能性がありDouble.MIN_VALUEます。たとえば、あなたが電話するとき

roundToSignificantFigures(1.234E-310, 3);

その場合、変数powerの値は3-(-309)= 312になります。その結果、変数magnitudeはになりInfinity、それ以降はすべてゴミになります。幸いなことに、これは克服できない問題ではありません。オーバーフローしているのは要因 だけmagnitudeです。本当に重要なのは製品 num * magnitudeであり、それはあふれません。これを解決する1つの方法は、因数による乗算magintudeを2つのステップに分割することです。


 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

このJavaソリューションはどうですか?

double roundToSignificantFigure(double num、intprecision){
 新しい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です。
マット

2

JavaScript:

Number( my_number.toPrecision(3) );

Numberこの関数は、フォームの出力を変更します"8.143e+5""814300"


1

手作業でコーディングする方法でコーディングしてみましたか?

  1. 数値を文字列に変換します
  2. 文字列の先頭から始めて、数字を数えます-先行ゼロは重要ではなく、他のすべては重要です。
  3. 「n番目」の桁に到達したら、次の桁を先に見て、5桁以上の場合は切り上げます。
  4. 末尾の数字をすべてゼロに置き換えます。

1

[修正、2009-10-26]

基本的に、N個の重要な数桁の場合:

•数値に10Nを掛け
ます•0.5を加算し
ます•小数桁を切り捨てます(つまり、結果を整数に切り捨て
ます)• 10Nで割ります

N個の重要な整数(非分数)桁の場合:

•数値を10Nで除算し
ます•0.5を加算し
ます•小数桁を切り捨てます(つまり、結果を整数に切り捨て
ます)• 10Nを掛けます

これは、たとえば、「INT」(整数の切り捨て)演算子を持つ任意の計算機で実行できます。


いいえ。もう一度質問を読んでください。アルゴリズムを使用して3つの有効数字を使用した1239451は、誤って123951を生成します
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

これは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
}

これは謎の反対票を得ました!しかし、エラーや問題は見つかりません。何が起きてる?
マイケルハンプトン

0

FloatToStrFを使用するだけで、10の累乗などでこれらすべての計算を行うことを回避できます。

FloatToStrFを使用すると、(とりわけ)出力値(文字列)の精度(有効数字の数)を選択できます。もちろん、StrToFloatをこれに適用して、丸められた値を浮動小数点数として取得することもできます。

ここを参照してください:

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.