double (234.004223) などがある場合、これを C# で有効桁数 x に丸めたいと思います。
これまでのところ、小数点以下 x 桁に丸める方法しか見つけることができませんが、数値に 0 がある場合、これは単純に精度を削除します。
例えば、0.086~小数点以下1桁は0.1になりますが、0.08のままでお願いしたいです。
double (234.004223) などがある場合、これを C# で有効桁数 x に丸めたいと思います。
これまでのところ、小数点以下 x 桁に丸める方法しか見つけることができませんが、数値に 0 がある場合、これは単純に精度を削除します。
例えば、0.086~小数点以下1桁は0.1になりますが、0.08のままでお願いしたいです。
フレームワークには、有効数字の数に丸める(または例のように切り捨てる)組み込み関数がありません。ただし、これを行う1つの方法は、最初の有効数字が小数点の直後になるように数値をスケーリングし、丸め(または切り捨て)してから、スケールバックすることです。次のコードでうまくいくはずです。
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を小数点以下1桁ではなく、有効数字1桁に丸めたいと考えています。
現在、doubleを使用し、有効数字の数に丸めることは、doubleの格納方法が原因で、最初は問題があります。たとえば、0.12を0.1に近い値に丸めることはできますが、0.1はdoubleとして正確に表すことはできません。実際に小数を使用するべきではありませんか?あるいは、これは実際に表示目的ですか?表示目的の場合は、実際にdoubleを有効数字の適切な数の文字列に直接変換する必要があると思います。
あなたがそれらの点に答えることができれば、私はいくつかの適切なコードを考え出すことを試みることができます。ひどいことに聞こえますが、数値を「完全な」文字列に変換して有効数字を文字列として変換し、最初の有効数字を見つける(その後、適切な丸めアクションを実行する)のが最善の方法です。 。
表示目的の場合(Jon Skeetの回答へのコメントで述べているように)、 Gn format specifierを使用する必要があります。どこで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 のメソッドに 2 つのバグが見つかりました。これにより、たとえば、この Q&A で Andrew Hancox によって提示された精度エラーが解決されます。ラウンド方向にも問題がありました。有効数字 2 桁の 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;
}
}
}
doubleのMath.Round()に欠陥があります(ドキュメントの呼び出し元への注意を参照)。丸められた数値をその10進数の指数で乗算する後のステップでは、末尾の桁にさらに浮動小数点エラーが発生します。@Rowantoのように別のRound()を使用しても、確実に役立つわけではなく、他の問題が発生します。ただし、10進数を使用する場合は、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));
}
}
}
小数点以下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
}
この質問は、あなたが尋ねているものと似ています:
したがって、次のことができます。
double Input2 = 234.004223;
string Result2 = Math.Floor(Input2) + Convert.ToDouble(String.Format("{0:G1}", Input2 - Math.Floor(Input2))).ToString("R6");
有効数字1桁に丸めます。
私にとって、これはかなりうまく機能し、負の数にも有効です:
public static double RoundToSignificantDigits(double number, int digits)
{
int sign = Math.Sign(number);
if (sign < 0)
number *= -1;
if (number == 0)
return 0;
double scale = Math.Pow(10, Math.Floor(Math.Log10(Math.Abs(number))) + 1);
return sign * scale * Math.Round(number / scale, digits);
}
今やりました:
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;
}
フォーマットを変更していないことを願っています。