任意の有効桁数への丸め
-
03-07-2019 - |
質問
任意の数字(整数<!> gt; 0だけでなく)を有効数字N桁に丸めるにはどうすればよいですか?
たとえば、有効数字3桁に丸める場合は、次のような数式を探しています。
1,239,451および1,240,000を返します
12.1257および12.1を返す
.0681および.0681を返します
5および5を返す
当然、アルゴリズムは、3のNのみを処理するようにハードコーディングされるべきではありませんが、それは開始になります。
解決
他の回答にある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;
}
他のヒント
これは短くて便利な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
要約:
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
だから数字の<!> gt; 0、ログの一部を取得します。数字の場合<!> lt; 0、ログのフロアを取得します。
これで、数字はd
になります。最初のケースでは7、2番目のケースでは2、3番目のケースでは-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
だから、パイロの答えを使用してください!
<!> quot;短くて甘い<!> quotではありません; JavaScriptの実装
Number(n).toPrecision(sig)
e.g。
alert(Number(12345).toPrecision(3)
?
申し訳ありませんが、私はここでは面白くありません。それは、<!> quot; roundit <!> quot; Claudiuの関数とJavaScriptの.toPrecisionは異なる結果を与えますが、最後の桁の丸めのみです。
JavaScript:
Number(8.14301).toPrecision(4) == 8.143
.NET
roundit(8.14301,4) == 8.144
Pyrolisticalの(非常に素晴らしい!)ソリューションにはまだ問題があります。 Javaの最大double値は10 ^ 308のオーダーであり、最小値は10 ^ -324のオーダーです。したがって、関数roundToSignificantFigures
をDouble.MIN_VALUE
の10の累乗の範囲内にあるものに適用すると、問題が発生する可能性があります。たとえば、電話をかけるとき
roundToSignificantFigures(1.234E-310, 3);
その後、変数power
の値は3-(-309)= 312になります。その結果、変数magnitude
はInfinity
になり、それ以降はすべてゴミになります。幸いなことに、これは克服できない問題ではありません。オーバーフローしているのは factor num * magnitude
だけです。本当に重要なのは product magintude
であり、それはオーバーフローしません。これを解決する1つの方法は、係数<=>による乗算を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;
}
このJavaソリューションはどうですか:
double roundToSignificantFigure(double num, int precision){ return new BigDecimal(num) .round(new MathContext(precision, RoundingMode.HALF_EVEN)) .doubleValue(); }
これは、負の数を処理する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;
}
これは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);
手動でコーディングする方法でコーディングしてみましたか?
- 数値を文字列に変換する
- の始まりから 文字列、カウント桁-先行ゼロはありません 重要なこと、それ以外はすべてです。
- <!> quot; nth <!> quot;に達したとき;桁、 次の数字を先読みし、 5以上です。切り上げます。
- すべての末尾の数字をゼロに置き換えます。
[修正、2009年10月26日]
本質的に、N個の有効な小数部桁の場合:
<!>#8226;数に10 N
を掛けます
<!>#8226; 0.5を追加
<!>#8226;小数桁を切り捨てます(つまり、結果を整数に切り捨てます)
<!>#8226; 10で割る N
有効なN桁の整数(非分数)桁:
<!>#8226;数を10 N
で割ります
<!>#8226; 0.5を追加
<!>#8226;小数桁を切り捨てます(つまり、結果を整数に切り捨てます)
<!>#8226; 10 N
たとえば、<!> quot; INT <!> quot;を持つ任意の計算機でこれを実行できます。 (整数の切り捨て)演算子。
/**
* 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);
}
これが必要な場合、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
JavaScript:
Number( my_number.toPrecision(3) );
Number
関数は、"8.143e+5"
の形式の出力を"814300"
に変更します。
これは私が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
return new BigDecimal(value, new MathContext(significantFigures, RoundingMode.HALF_UP)).doubleValue();
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
}
public static double roundToSignificantDigits(double num, int n) {
return Double.parseDouble(new java.util.Formatter().format("%." + (n - 1) + "e", num).toString());
}
このコードは、組み込み関数を使用しますが、これは丸め関数になります