104

異なるカルチャ情報で安全に使用できる小数値 (int として) の小数点以下の桁数を引き出すための簡潔で正確な方法があるかどうか疑問に思っています。

例:
19.0 は 1 を返し、
27.5999 は 4 を返し、
19.12 は 2 を返す必要があり
ます。

小数点以下の桁数を見つけるために、ピリオドで文字列を分割するクエリを作成しました。

int priceDecimalPlaces = price.ToString().Split('.').Count() > 1 
                  ? price.ToString().Split('.').ToList().ElementAt(1).Length 
                  : 0;

しかし、これは「。」を使用する地域でのみ機能することがわかりました。小数点として使用されるため、異なるシステム間で非常に脆弱です。

4

18 に答える 18

184

私はこの問題を解決するためにジョーの方法を使用しました:)

decimal argument = 123.456m;
int count = BitConverter.GetBytes(decimal.GetBits(argument)[3])[2];
于 2012-11-21T12:55:54.320 に答える
31

提供された回答のどれもが、10 進数に変換されたマジック ナンバー「-0.01f」に十分なものではなかったので.. つまりGetDecimal((decimal)-0.01f);
、3 年前に巨大な心おならウイルスが全員を攻撃したとしか
思えません:)この邪悪で巨大な問題、ポイントの後の小数点以下の桁数を数えるという非常に複雑な問題への実装 - 文字列も文化も、ビットを数える必要も、数学フォーラムを読む必要もありません.. 単純な3年生の数学.

public static class MathDecimals
{
    public static int GetDecimalPlaces(decimal n)
    {
        n = Math.Abs(n); //make sure it is positive.
        n -= (int)n;     //remove the integer part of the number.
        var decimalPlaces = 0;
        while (n > 0)
        {
            decimalPlaces++;
            n *= 10;
            n -= (int)n;
        }
        return decimalPlaces;
    }
}

private static void Main(string[] args)
{
    Console.WriteLine(1/3m); //this is 0.3333333333333333333333333333
    Console.WriteLine(1/3f); //this is 0.3333333

    Console.WriteLine(MathDecimals.GetDecimalPlaces(0.0m));                  //0
    Console.WriteLine(MathDecimals.GetDecimalPlaces(1/3m));                  //28
    Console.WriteLine(MathDecimals.GetDecimalPlaces((decimal)(1 / 3f)));     //7
    Console.WriteLine(MathDecimals.GetDecimalPlaces(-1.123m));               //3
    Console.WriteLine(MathDecimals.GetDecimalPlaces(43.12345m));             //5
    Console.WriteLine(MathDecimals.GetDecimalPlaces(0));                     //0
    Console.WriteLine(MathDecimals.GetDecimalPlaces(0.01m));                 //2
    Console.WriteLine(MathDecimals.GetDecimalPlaces(-0.001m));               //3
    Console.WriteLine(MathDecimals.GetDecimalPlaces((decimal)-0.00000001f)); //8
    Console.WriteLine(MathDecimals.GetDecimalPlaces((decimal)0.0001234f));   //7
    Console.WriteLine(MathDecimals.GetDecimalPlaces((decimal)0.01f));        //2
    Console.WriteLine(MathDecimals.GetDecimalPlaces((decimal)-0.01f));       //2
}
于 2015-05-13T03:27:55.353 に答える
26

@fixagon's answerのソリューションを使用すると思います。

ただし、Decimal 構造体には小数点以下の桁数を取得するメソッドがありませんが、Decimal.GetBitsを呼び出してバイナリ表現を抽出し、整数値とスケールを使用して小数点以下の桁数を計算することができます。

これはおそらく文字列としてフォーマットするよりも高速ですが、違いに気付くには非常に多くの小数を処理する必要があります。

実装は演習として残しておきます。

于 2012-11-20T16:46:35.407 に答える
19

小数点以下の桁数を見つけるための最良の解決策の 1 つは、burning_LEGION の投稿に示されています。

ここでは、STSdb フォーラムの記事Number of digits after decimal point の一部を使用しています。

MSDN では、次の説明を読むことができます。

「10 進数は、符号、値の各桁の範囲が 0 から 9 の数値、および整数と小数を区切る浮動小数点の位置を示すスケーリング係数で構成される浮動小数点値です。数値の一部です。」

また:

「Decimal 値のバイナリ表現は、1 ビットの符号、96 ビットの整数、および 96 ビットの整数を除算し、小数部分を指定するために使用されるスケーリング係数で構成されます。スケーリング係数は暗黙のうちに 10 を 0 から 28 までの指数で累乗したものです。」

内部レベルでは、10 進数値は 4 つの整数値で表されます。

10 進数の内部表現

内部表現を取得するための公開されている GetBits 関数があります。関数は int[] 配列を返します。

[__DynamicallyInvokable] 
public static int[] GetBits(decimal d)
{
    return new int[] { d.lo, d.mid, d.hi, d.flags };
}

返された配列の 4 番目の要素には、倍率と符号が含まれます。また、MSDN によると、倍率は暗黙的に 10 を 0 から 28 までの指数で累乗したものです。これはまさに私たちが必要としているものです。

したがって、上記のすべての調査に基づいて、メソッドを構築できます。

private const int SIGN_MASK = ~Int32.MinValue;

public static int GetDigits4(decimal value)
{
    return (Decimal.GetBits(value)[3] & SIGN_MASK) >> 16;
}

ここでは、符号を無視するために SIGN_MASK が使用されています。論理演算の後、結果を 16 ビットで右にシフトして、実際のスケール ファクターを受け取ります。最後に、この値は小数点以下の桁数を示します。

ここでMSDNは、スケーリング係数も10進数の末尾のゼロを保持すると述べていることに注意してください。末尾のゼロは、算術演算または比較演算の 10 進数の値には影響しません。ただし、適切な書式文字列が適用されている場合、ToString メソッドによって末尾のゼロが明らかになることがあります。

この解決策が最善の解決策のように見えますが、待ってください。他にもあります。C# でプライベート メソッドにアクセスすることにより、式を使用して flags フィールドへの直接アクセスを構築し、int 配列の構築を回避できます。

public delegate int GetDigitsDelegate(ref Decimal value);

public class DecimalHelper
{
    public static readonly DecimalHelper Instance = new DecimalHelper();

    public readonly GetDigitsDelegate GetDigits;
    public readonly Expression<GetDigitsDelegate> GetDigitsLambda;

    public DecimalHelper()
    {
        GetDigitsLambda = CreateGetDigitsMethod();
        GetDigits = GetDigitsLambda.Compile();
    }

    private Expression<GetDigitsDelegate> CreateGetDigitsMethod()
    {
        var value = Expression.Parameter(typeof(Decimal).MakeByRefType(), "value");

        var digits = Expression.RightShift(
            Expression.And(Expression.Field(value, "flags"), Expression.Constant(~Int32.MinValue, typeof(int))), 
            Expression.Constant(16, typeof(int)));

        //return (value.flags & ~Int32.MinValue) >> 16

        return Expression.Lambda<GetDigitsDelegate>(digits, value);
    }
}

このコンパイルされたコードは、GetDigits フィールドに割り当てられます。この関数は 10 進数値を ref として受け取るため、実際のコピーは実行されず、値への参照のみであることに注意してください。DecimalHelper から GetDigits 関数を使用するのは簡単です。

decimal value = 3.14159m;
int digits = DecimalHelper.Instance.GetDigits(ref value);

これは、10 進数値の小数点以下の桁数を取得するための最速の方法です。

于 2014-07-03T08:39:13.753 に答える
17

10 進数の内部表現に依存するのはクールではありません。

これはどう:

    int CountDecimalDigits(decimal n)
    {
        return n.ToString(System.Globalization.CultureInfo.InvariantCulture)
                //.TrimEnd('0') uncomment if you don't want to count trailing zeroes
                .SkipWhile(c => c != '.')
                .Skip(1)
                .Count();
    }
于 2015-09-29T07:08:16.183 に答える
11

InvariantCulture を使用できます

string priceSameInAllCultures = price.ToString(System.Globalization.CultureInfo.InvariantCulture);

別の可能性は、そのようなことをすることです:

private int GetDecimals(decimal d, int i = 0)
{
    decimal multiplied = (decimal)((double)d * Math.Pow(10, i));
    if (Math.Round(multiplied) == multiplied)
        return i;
    return GetDecimals(d, i+1);
}
于 2012-11-20T16:35:06.197 に答える
9

ここにいるほとんどの人は、decimal が末尾のゼロをストレージと印刷にとって重要であると見なしていることに気付いていないようです。

したがって、0.1m、0.10m、および 0.100m は等しいと比較される場合があり、それらは異なる方法で格納され (それぞれ値/スケール 1/1、10/2、および 100/3 として)、それぞれ 0.1、0.10、および 0.100 として出力されます。 、によってToString()

そのため、「精度が高すぎる」と報告するソリューションは、実際には正しい精度を報告していますdecimal

さらに、数学ベースのソリューション (10 のべき乗など) は非常に遅くなる可能性があります (算術では、10 進数は倍精度よりも 40 倍遅く、不正確になる可能性があるため、浮動小数点を混在させたくない場合もあります)。 )。int同様に、切り捨ての手段として、または切り捨ての手段としてキャストするとlong、エラーが発生しやすくなります (decimalこれらのいずれよりもはるかに広い範囲を持ちます - 96 ビット整数に基づいています)。

それ自体はエレガントではありませんが、精度を取得するための最速の方法の 1 つとして、次の方法が考えられます (「末尾のゼロを除く小数点以下の桁数」として定義されている場合)。

public static int PrecisionOf(decimal d) {
  var text = d.ToString(System.Globalization.CultureInfo.InvariantCulture).TrimEnd('0');
  var decpoint = text.IndexOf('.');
  if (decpoint < 0)
    return 0;
  return text.Length - decpoint - 1;
}

インバリアント カルチャは、'.' を保証します。小数点として、末尾のゼロが削除され、小数点の後に残っている桁数を確認するだけです (1 つでもある場合)。

編集:戻り値の型を int に変更

于 2015-08-06T08:32:59.827 に答える
2

昨日、理想的な文字列の分割やカルチャに依存することなく、小数点以下の桁数を返す簡潔で小さなメソッドを作成しました。

public int GetDecimalPlaces(decimal decimalNumber) { // 
try {
    // PRESERVE:BEGIN
        int decimalPlaces = 1;
        decimal powers = 10.0m;
        if (decimalNumber > 0.0m) {
            while ((decimalNumber * powers) % 1 != 0.0m) {
                powers *= 10.0m;
                ++decimalPlaces;
            }
        }
return decimalPlaces;
于 2012-11-21T14:14:17.207 に答える
1

コードで次のメカニズムを使用します

  public static int GetDecimalLength(string tempValue)
    {
        int decimalLength = 0;
        if (tempValue.Contains('.') || tempValue.Contains(','))
        {
            char[] separator = new char[] { '.', ',' };
            string[] tempstring = tempValue.Split(separator);

            decimalLength = tempstring[1].Length;
        }
        return decimalLength;
    }

10 進入力 = 3.376; var instring=input.ToString();

GetDecimalLength(instring) を呼び出す

于 2014-02-10T06:06:09.813 に答える
0

あなたが試すことができます:

int priceDecimalPlaces =
        price.ToString(System.Globalization.CultureInfo.InvariantCulture)
              .Split('.')[1].Length;
于 2012-11-20T16:42:25.813 に答える