decimal
型の変数があり、小数点の前の桁数を確認したい。私は何をすべきか?たとえば、467.45
は3
を返す必要があります。
string
に変換しないソリューション(異国の文化の場合は危険な場合があります):
static int GetNumberOfDigits(decimal d)
{
decimal abs = Math.Abs(d);
return abs < 1 ? 0 : (int)(Math.Log10(decimal.ToDouble(abs)) + 1);
}
このソリューションはすべての10進数値に有効であることに注意してください
[〜#〜] update [〜#〜]
実際、このソリューションはいくつかの大きな値では機能しません。例えば:999999999999998
、999999999999999
、9999999999999939
...
明らかに、double
を使用した数学的操作は、このタスクに対して十分に正確ではありません。
間違った値を検索するとき、このトピックで提案されているstring
ベースの代替を使用する傾向があります。私にとっては、それが信頼性が高く使いやすいという証拠です(ただし、文化に注意してください)。ただし、ループベースのソリューションはより高速です。
コメンテーターのおかげで、私に恥をかき、あなたへのレッスン。
文字列に変換する代わりに、0になるまで数値を10で除算することもできます。興味深いのは、小数の数学演算は、小数を文字列に変換して長さを返すよりもはるかに遅いことです(以下のベンチマークを参照)。
このソリューションは、入力としてdoubleを使用するMathメソッドを使用しません。そのため、すべての演算は10進数で行われ、キャストは含まれません。
_using System;
public class Test
{
public static void Main()
{
decimal dec = -12345678912345678912345678912.456m;
int digits = GetDigits(dec);
Console.WriteLine(digits.ToString());
}
static int GetDigits(decimal dec)
{
decimal d = decimal.Floor(dec < 0 ? decimal.Negate(dec) : dec);
// As stated in the comments of the question,
// 0.xyz should return 0, therefore a special case
if (d == 0m)
return 0;
int cnt = 1;
while ((d = decimal.Floor(d / 10m)) != 0m)
cnt++;
return cnt;
}
}
_
出力は_29
_です。このサンプルを実行するには、この link にアクセスしてください。
サイドノート:いくつかのベンチマークは驚くべき結果を示しています(1万回実行):
while ((d = decimal.Floor(d / 10m)) != 0m)
:25mswhile ((d = d / 10m) > 1m)
:32msまた、常に同じ値の代わりに乱数を使用すると(10進数から文字列への変換のキャッシュを回避するため)、文字列ベースの方法がはるかに高速であることがわかりました。
私はこれを試してみます:
Math.Truncate(467.45).ToString().Length
異なる文化や負の小数に対して奇妙な結果が出ないようにしたい場合は、これを使用した方が良いでしょう:
var myDecimal = 467.45m;
Math.Truncate(Math.Abs(myDecimal)).ToString(CultureInfo.InvariantCulture).Length
int
にキャストするのではなく、次の方法を使用して、大きな数値も処理できるようにします(例:decimal.MaxValue
):
Math.Truncate ( Math.Abs ( decValue ) ).ToString( "####" ).Length
decimal d = 467.45M;
int i = (int)d;
Console.WriteLine(i.ToString(CultureInfo.InvariantCulture).Length); //3
メソッドとして;
public static int GetDigitsLength(decimal d)
{
int i = int(d);
return i.ToString(CultureInfo.InvariantCulture).Length;
}
注:もちろん、最初に小数部全体が Int32.MaxValue
かどうか。なぜなら、 OverflowException
を取得するからです。
そのような場合、long
の代わりにint
を使用する方がより良い方法です。 ただし、long
(System.Int64
)は、可能なすべてのdecimal
値を保持するのに十分な大きさではありません。
Rawling言及のように、あなたの完全な部分は桁区切り記号を保持でき、そのような場合、私のコードは壊れます。このように、私の番号に含まれる NumberFormatInfo.NumberGroupSeparator
かどうか。
そのため、数字だけを取得する方が優れたアプローチです。のような;
i.ToString().Where(c => Char.IsDigit(c)).ToArray()
ここに再帰的な例があります(主に楽しみのため)。
void Main()
{
digitCount(0.123M); //0
digitCount(493854289.543354345M); //10
digitCount(4937854345454545435549.543354345M); //22
digitCount(-4937854345454545435549.543354345M); //22
digitCount(1.0M); //1
//approximately the biggest number you can pass to the function that works.
digitCount(Decimal.MaxValue + 0.4999999M); //29
}
int digitCount(decimal num, int count = 0)
{
//divided down to last digit, return how many times that happened
if(Math.Abs(num) < 1)
return count;
return digitCount(num/10, ++count); //increment the count and divide by 10 to 'remove' a digit
}
小さい数字に偏っている場合は、このようなもっと単純なものを使用できます。
2つのメソッドに分割されているため、最初のメソッドは小さく、インライン化できます。
パフォーマンスはLog10を使用したソリューションとほぼ同じですが、丸め誤差はありません。 Log10を使用する方法は、特に100万を超える数については最速(少し)です。
public static int CountNrOfDigitsIfs(decimal d) {
var absD = Math.Abs(d);
// 1
if (absD < 10M) return 1;
// 2
if (absD < 100M) return 2;
// 3
if (absD < 1000M) return 3;
// 4
if (absD < 10000M) return 4;
return CountNrOfDigitsIfsLarge(d);
}
private static int CountNrOfDigitsIfsLarge(decimal d) {
// 5
if (d < 100000M) return 5;
// 6
if (d < 1000000M) return 6;
// 7
if (d < 10000000M) return 7;
// 8
if (d < 100000000M) return 8;
// 9
if (d < 1000000000M) return 9;
// 10
if (d < 10000000000M) return 10;
// 11
if (d < 100000000000M) return 11;
// 12
if (d < 1000000000000M) return 12;
// 13
if (d < 10000000000000M) return 13;
// 14
if (d < 100000000000000M) return 14;
// 15
if (d < 1000000000000000M) return 15;
// 16
if (d < 10000000000000000M) return 16;
// 17
if (d < 100000000000000000M) return 17;
// 18
if (d < 1000000000000000000M) return 18;
// 19
if (d < 10000000000000000000M) return 19;
// 20
if (d < 100000000000000000000M) return 20;
// 21
if (d < 1000000000000000000000M) return 21;
// 22
if (d < 10000000000000000000000M) return 22;
// 23
if (d < 100000000000000000000000M) return 23;
// 24
if (d < 1000000000000000000000000M) return 24;
// 25
if (d < 10000000000000000000000000M) return 25;
// 26
if (d < 100000000000000000000000000M) return 26;
// 27
if (d < 1000000000000000000000000000M) return 27;
// 28
if (d < 10000000000000000000000000000M) return 28;
return 29; // Max nr of digits in decimal
}
このコードは、次のT4テンプレートを使用して生成されます。
<#
const int SIGNIFICANT_DECIMALS = 29;
const int SPLIT = 5;
#>
namespace Study.NrOfDigits {
static partial class DigitCounter {
public static int CountNrOfDigitsIfs(decimal d) {
var absD = Math.Abs(d);
<#
for (int i = 1; i < SPLIT; i++) { // Only 29 significant digits
var zeroes = new String('0', i);
#>
// <#= i #>
if (absD < 1<#= zeroes #>M) return <#= i #>;
<#
}
#>
return CountNrOfDigitsIfsLarge(d);
}
private static int CountNrOfDigitsIfsLarge(decimal d) {
<#
for (int i = SPLIT; i < SIGNIFICANT_DECIMALS; i++) { // Only 29 significant digits
var zeroes = new String('0', i);
#>
// <#= i #>
if (d < 1<#= zeroes #>M) return <#= i #>;
<#
}
#>
return <#= SIGNIFICANT_DECIMALS #>; // Max nr of digits in decimal
}
}
}
Math.Floor(Math.Log10((double)n) + 1);
が道です。
int
はdecimal
よりも大きい場合があるため、int
への変換は悪いです。
_Decimal.MaxValue = 79,228,162,514,264,337,593,543,950,335;
Int32.MaxValue = 2,147,483,647; //that is, hexadecimal 0x7FFFFFFF;
_
Math.Floor(n).ToString().Count();
は、数千個の区切り文字が含まれている可能性があるため、不良です。
これは、Logメソッド(これはIMOが最良の方法です)を使用したくない場合に行います。これは、ToString()を使用してこれを行うことについて考えることができる最も明確な方法です:
Math.Abs(val).ToString("f0", CultureInfo.InvariantCulture).Length
あるいは、0.123M
を1桁としてカウントしたくない場合:
Math.Abs(val).ToString("#", CultureInfo.InvariantCulture).Length
var sep = Convert.ToChar(CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator);
var count = d.ToString().TakeWhile(c => c != sep).Count();
だから、私は以前にこれに遭遇し、このコードで解決しました:
SqlDecimal d = new SqlDecimal(467.45M);
int digits = d.Precision - d.Scale;
SqlDecimal
はSystem.Data.SqlTypes
名前空間の一部です。 「精度」は有効数字の合計数であり、「スケール」は小数点以下の桁数です。
さて、このルートへの反対の1つは、SqlDecimal
がSQL Server固有のコードの一部であるということです。これは有効なポイントですが、.NETフレームワーク自体の一部であり、少なくともバージョン1.1以降のものであるため、周囲のコードが何をしていても適用できるようです。 。
SqlDecimal
。スケールを計算するコードは非常に簡単ですが、精度を計算する方法は簡単ではないので、私なら、SqlDecimal
を実行するだけです。
この答えは Calculate System.Decimal Precision and Scale からかなり引き上げられていますが、質問に合わせて少し変更されています。
class Program
{
static void Main()
{
decimal dec = 467.45m;
Console.WriteLine(dec.GetNumberOfDigitsBeforeDecimalPlace());
}
}
public static class DecimalEx
{
public static int GetNumberOfDigitsBeforeDecimalPlace(this decimal dec)
{
var x = new System.Data.SqlTypes.SqlDecimal(dec);
return x.Precision - x.Scale;
}
}
また、SqlDecimalクラスを使用せずに実行したい場合は、同じ質問に対するJon Skeetの回答を確認してください。
カスタム形式でToString関数を使用できます。
Decimal value = 467.45m;
int count = Math.Abs(value).ToString("#", System.Globalization.CultureInfo.InvariantCulture).Length;
#
は、.
の前の文字のみが必要であることを指定します
System.Globalization.CultureInfo.InvariantCulture
は、Region Optionからフォーマットを取得しないようにします。
これを行う数学的な方法(おそらく最速)は、この数値の絶対値の10を底とする対数を取得し、それを切り上げることです。
Math.Floor(Math.Log10(Math.Abs(val)) + 1);
TLDR他のすべての答え。これをPHPで作成しましたが、数学は同じになります。 (C#を知っていれば、その言語で書いていたでしょう。)
$input=21689584.999;
$input=abs($input);
$exp=0;
do{
$test=pow(10,$exp);
if($test > $input){
$digits=$exp;
}
if($test == $input){
$digits=$exp+1;
}
$exp++;
}while(!$digits);
if($input < 1){$digits=0;}
echo $digits;
もっと良い方法があるのは間違いないが、$。02を投入したかった
編集:
コメントで言及したコードをphp化しましたが、int変換は廃止しました。
function digitCount($input){
$digits=0;
$input=abs($input);
while ($input >= 1) {
$digits++;
$input=$input/10;
//echo $input."<br>";
}
return $digits;
}
$big=(float)(PHP_INT_MAX * 1.1);
echo digitCount($big);
モジュロを使用してください。私はC#プログラマではありませんが、このソリューションが機能すると確信しています。
double i = 1;
int numberOfDecimals = 0;
while (varDouble % i != varDouble)
{
numberOfDecimals++;
i*=10;
}
return numberOfDecimals;
これを行うには、数値を丸めてから、新しい数値の長さを取得します。次のようにできます:
var number = 476.43;
var newNumber = Math.round(number);
//get the length of the rounded number, and subtract 1 if the
//number is negative (remove the negative sign from the count)
int digits = newNumber.ToString().Length - (number < 0 ? 1 : 0);
ゼロまたはゼロの欠如を1つの数値として扱う場合、これは問題ありません。ゼロを返すにはゼロを、ゼロを返すにはゼロがない場合は、追加するのが難しくないはずのいくつかのEdgeケースがあります。また、絶対値は負の数を処理する必要があります。そのテストケースも追加しました。
const decimal d = 123.45m;
const decimal d1 = 0.123m;
const decimal d2 = .567m;
const decimal d3 = .333m;
const decimal d4 = -123.45m;
NumberFormatInfo currentProvider = NumberFormatInfo.InvariantInfo;
var newProvider = (NumberFormatInfo) currentProvider.Clone();
newProvider.NumberDecimalDigits = 0;
string number = d.ToString("N", newProvider); //returns 123 = .Length = 3
string number1 = d1.ToString("N", newProvider); //returns 0 = .Length = 1
string number2 = d2.ToString("N", newProvider); //returns 1 = .Length = 1
string number3 = d3.ToString("N", newProvider); //returns 0 = .Length = 1
string number4 = Math.Abs(d4).ToString("N", newProvider); //returns 123 = .Length = 3
これがやや最終的な解決策です。機能しないテストケースを見つけた場合は、お知らせください。提供されたテストケースでは、3,0,0,0,3を返します。
public static int NumbersInFrontOfDecimal(decimal input)
{
NumberFormatInfo currentProvider = NumberFormatInfo.InvariantInfo;
var newProvider = (NumberFormatInfo)currentProvider.Clone();
newProvider.NumberDecimalDigits = 0;
var absInput = Math.Abs(input);
var numbers = absInput.ToString("N", newProvider);
//Handle Zero and < 1
if (numbers.Length == 1 && input < 1.0m)
{
return 0;
}
return numbers.Length;
}
グレイの答えに触発されたコードの最適化バージョンは次のとおりです。
static int GetNumOfDigits(decimal dTest)
{
int nAnswer = 0;
dTest = Math.Abs(dTest);
//For loop version
for (nAnswer = 0; nAnswer < 29 && dTest > 1; ++nAnswer)
{
dTest *= 0.1M;
}
//While loop version
//while (dTest > 1)
//{
// nAnswer++;
// dTest *= 0.1M;
//}
return (nAnswer);
}
この関数内でMath.Absを呼び出したくない場合は、GetNumOfDigitsを呼び出す前に、パラメーターの関数外で必ず使用してください。
他のコードを削除して、答えの混乱を減らすことにしましたが、それらはこの点に到達するのに役立ちましたが...
改善が必要な場合はお知らせください。更新します:)。
これはJavaソリューション
public class test {
public static void main(String args[]) {
float f = 1.123f;
int a = (int) f;
int digits = 0;
while (a > 0) {
digits++;
a=a/10;
}
System.out.println("No Of digits before decimal="+digits);
}
}
正確で文化にとらわれない答えを得るために、次のことを行います。
System.Numerics.BigInteger
_を使用します。コンストラクターは小数を受け入れ、seemを受け入れないため、丸めエラーが発生します。BigInteger.Abs()
を使用して、記号を削除します。BigInteger.ToString()
を "#"形式で使用して、発生する可能性のあるセパレータを抑制します。_decimal num = 123213123.123123M;
int length = BigInteger.Abs((BigInteger)num).ToString("#").Length;
_
私はこれをテストしていませんが、私はそれを簡単に保ち、実行します:
decimal value = 467.45;
string str = Convert.toString(value); // convert your decimal type to a string
string[] splitStr = str.split('.'); // split it into an array (use comma separator assuming you know your cultural context)
Console.WriteLine(splitStr[0].Count); // get the first element. You can also get the number of figures after the point by indexing the next value in the array.
これは負の数を処理しません。それらを気にする場合は、絶対値を取ることを検討してください。さらに、小数点の前の0をカウントしない場合は、単純なifステートメントを使用してチェックできます。
アルゴリズム:
|decimal|
から文字列へ。"."
は小数で存在し、その前にカットされます。それ以外の場合は整数を考慮します。例:
3.14 --> 3.14 --> "3.14" --> "3.14".Substring(0,1) --> "3".Length --> 1
-1024 --> 1024 --> "1024" --> IndexOf(".") = -1 --> "1024" --> 4
コード:
static int getNumOfDigits (decimal num)
{
string d = Math.Abs(num).ToString();
if (d.IndexOf(".") > -1)
{
d = d.Substring(0, d.IndexOf("."));
}
return d.Length;
}
他のソリューションでは、数値が大きすぎると数字が失われます。
public int Digits(Decimal i)
{
NumberFormatInfo format = CultureInfo.CurrentCulture.NumberFormat;
var str = Math.Abs(i).ToString().Replace(format.NumberGroupSeparator, "");
var index = str.IndexOf(format.NumberDecimalSeparator);
var digits = index == -1 ? str.Length : index;
}