获取小数点前的位数
我有一个decimal
types的variables,我想检查小数点之前的位数。 我该怎么办? 例如, 467.45
应该返回3
。
解决scheme不转换为string
(这可能是危险的情况下,异国文化):
static int GetNumberOfDigits(decimal d) { decimal abs = Math.Abs(d); return abs < 1 ? 0 : (int)(Math.Log10(decimal.ToDouble(abs)) + 1); }
请注意,此解决scheme对所有小数值均有效
UPDATE
事实上,这个解决scheme不适用于一些大的值,例如: 9999999999999939
…
显然, double
math运算对于这个任务来说是不够精确的。
在search错误的值时,我倾向于使用本主题中提出的基于string
的替代方法。 对我来说,这是certificate他们更可靠,更易于使用(但要注意文化)的证据。 基于循环的解决scheme虽然可以更快。
感谢评论员,对我感到羞耻,给你的教训。
有趣的是,对小数的math运算比将小数转换为string并返回长度要慢得多(见下面的基准),而不是将其转换为string。
此解决scheme不使用将double作为input的Math-methods; 所以所有的操作都是以小数完成的 ,不涉及任何投射。
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
。 要运行此示例,请访问此链接 。
附注:一些基准testing显示出令人惊讶的结果(10k运行):
-
while ((d = decimal.Floor(d / 10m)) != 0m)
:25ms -
while ((d = d / 10m) > 1m)
:32ms - ToString与math双操作:3ms
- 带有十进制运算的ToString:3ms
- BigInt(查看@Heinzi的答案 ):2ms
也使用随机数而不是总是相同的值(以避免可能的十进制caching到string转换)表明,基于string的方法要快得多。
我会试试这个:
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
这是一个recursion的例子(主要是为了好玩)。
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 }
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()
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();
是坏的,因为它可能包括数千个分离器。
如果你对小数字有偏见,你可以使用更简单的东西。
它被分成两个方法,所以第一个方法更小,可以内联。
性能与Log10的解决scheme大致相同,但没有舍入错误。 使用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 } } }
var sep = Convert.ToChar(CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator); var count = d.ToString().TakeWhile(c => c != sep).Count();
如果你真的不想使用Log方法(IMO是最好的方法),这将会执行。 这是关于使用ToString()我可以想到的最清晰的方式:
Math.Abs(val).ToString("f0", CultureInfo.InvariantCulture).Length
或者,如果您不想将0.123M
计为一位:
Math.Abs(val).ToString("#", CultureInfo.InvariantCulture).Length
您可以使用自定义格式的ToString函数。
Decimal value = 467.45m; int count = Math.Abs(value).ToString("#", System.Globalization.CultureInfo.InvariantCulture).Length;
#
指定你只想要之前的字符.
System.Globalization.CultureInfo.InvariantCulture
确保您不会从Region选项中获得任何格式化。
这样做(也可能是最快的)的math方法是获得这个数字的绝对值的10的基数,并将其舍入。
Math.Floor(Math.Log10(Math.Abs(val)) + 1);
TLDR所有其他的答案。 我用PHP写这个,math也是一样的。 (如果我知道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);
所以,我已经遇到了这个问题,并解决了这个代码:
SqlDecimal d = new SqlDecimal(467.45M); int digits = d.Precision - d.Scale;
SqlDecimal
是System.Data.SqlTypes
命名空间的一部分。 “精度”是有效位数的总数,而“标度”是小数点后的位数。
现在,我知道有一种反对意见,那就是SqlDecimal
是SQL Server特定代码的一部分。 这是一个有效的观点,但是我也要指出,它是.NET框架本身的一部分,至less从1.1版本开始,所以无论代码在做什么,它似乎仍然适用。
我用反编译器( JetBrains的dotPeek在这个例子中)看了一下,看看是否可以很容易地提取和使用计算精度和规模的代码,而无需拉动SqlDecimal
。 计算比例尺的代码非常简单,但计算精度的方法并不重要,所以如果是我,我只需要通过SqlDecimal
。
这将是Java解决scheme
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); } }
如果你把0或0表示为1,那么这是OK的。 如果你想零返回零或缺乏零返回零,那么有几个边缘情况下工作,不应该太难添加。 另外,应该绝对值来处理负数。 添加了testing用例。
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
这里有一个最终的解决scheme,如果你发现一个不起作用的testing用例,请告诉我。 它应该返回3,0,0,0,3提供的testing用例。
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; }
这个答案是从计算System.Decimal精确度和规模相当多的提升,但有一个小的改变,以适应问题。
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对同一个问题的答案。
使用modulo,我不是一个C#程序员,但我很确定这个解决scheme的工作:
double i = 1; int numberOfDecimals = 0; while (varDouble % i != varDouble) { numberOfDecimals++; i*=10; } return numberOfDecimals;
如果数字太大,其他解决scheme将丢失数字。
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; }
这里是我的优化版本的代码启发格雷的答案:
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之前,一定要在参数的函数之外使用它。
我决定删除其他代码,以减less在我的答案混乱,即使他们帮助我到这一点…
如果有任何需要改进的地方,请告诉我,我会更新它:)。
为了得到一个准确和文化不可知的答案,我做了以下几点:
- 使用
System.Numerics.BigInteger
,其构造函数接受一个小数,似乎不会产生任何舍入错误。 - 使用
BigInteger.Abs()
删除任何标志。 - 使用带“#”格式的
BigInteger.ToString()
来禁止可能发生的任何分隔符。
码
decimal num = 123213123.123123M; int length = BigInteger.Abs((BigInteger)num).ToString("#").Length;
你可以通过四舍五入的数字,然后得到新号码的长度。 你可以这样做:
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);
algorithm:
- 转换
|decimal|
string。 - 如果
"."
存在于小数点之前,否则考虑整数。 - 返回string长度。
例:
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; }
我没有testing过,但我会保持直接,并做:
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语句来检查它。
简单:
string value = "467.45"; int count = value.split('.')[0] == "0" ? 0 : value.split('.')[0].ToString().Replace("-","").Count();
使用:
var value=467.45; var length=((int)value).ToString().Length