![](/img/trans.png)
[英]Get number of significant digits to the right of decimal point in c# Decimal
[英]Get number of digits before decimal point
我有一個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);
}
請注意,此解決方案適用於所有十進制值
UPDATE
事實上,這個解決方案不與一些大的值正常工作,例如: 999999999999998
, 999999999999999
, 9999999999999939
...
顯然,使用double
的數學運算對於此任務來說不夠准確。
在搜索錯誤的值時,我傾向於使用本主題中提出的基於string
的替代方法。 至於我,這證明它們更可靠和易於使用(但要注意文化)。 但是,基於循環的解決方案可以更快。
感謝評論員,羞辱我,給你上課。
您也可以將數字除以10,直到它等於0.有趣的是,小數的數學運算比將小數轉換為字符串並返回長度要慢得多(參見下面的基准)。
此解決方案不使用以double作為輸入的Math方法; 所以所有操作都是在小數上完成的 ,不涉及任何轉換。
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
。 要運行此示例,請訪問此鏈接 。
旁注:一些基准測試顯示出令人驚訝的結果(10k運行):
while ((d = decimal.Floor(d / 10m)) != 0m)
:25ms while ((d = d / 10m) > 1m)
:32ms 同樣使用隨機數而不是總是相同的值(以避免可能的十進制緩存到字符串轉換)表明基於字符串的方法要快得多。
我會試試這個:
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
}
Math.Floor(Math.Log10((double)n) + 1);
是要走的路。
轉換為int
是BAD因為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的解決方案大致相同,但沒有舍入錯誤。 使用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 Option獲得任何格式。
所以,我之前遇到過這個問題,並使用以下代碼解決了這個問題:
SqlDecimal d = new SqlDecimal(467.45M);
int digits = d.Precision - d.Scale;
SqlDecimal
是System.Data.SqlTypes
命名空間的一部分。 “精度”是有效位的總數,而“縮放”是小數點后的位數。
現在,我知道對此路由的一個反對意見是SqlDecimal
是SQL Server特定代碼的一部分。 這是一個有效的觀點,但我還要指出它是.NET框架本身的一部分,至少從1.1版開始,所以看起來無論它周圍的代碼是什么,它仍然適用。
我用一個反編譯器(在這個實例中是JetBrains的dotPeek )查看引擎蓋,看看是否可以輕松提取和使用計算精度和比例的代碼,而不需要拉入SqlDecimal
。 計算比例的代碼非常簡單,但計算精度的方法並不重要,所以如果是我,我只需要通過SqlDecimal
。
這個答案幾乎可以從Calculate System.Decimal Precision和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對同一問題的答案。
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
編輯:
我在我的評論中提到了我提到的代碼,但取消了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);
這樣做的數學方法(可能是最快的)是獲得該數字的絕對值的基數10的對數並將其四舍五入。
Math.Floor(Math.Log10(Math.Abs(val)) + 1);
使用modulo,我不是C#程序員,但我很確定這個解決方案有效:
double i = 1;
int numberOfDecimals = 0;
while (varDouble % i != varDouble)
{
numberOfDecimals++;
i*=10;
}
return numberOfDecimals;
這將是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);
}
}
如果將零或缺少零視為1,則可以。 如果你想要零返回零或缺少零來返回零,那么有一些邊緣情況要解決,這不應該太難添加。 另外,應該使用絕對值來處理負數。 還添加了測試用例。
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之前在參數的函數外部使用它。
我決定刪除其他代碼以減少我的答案中的混亂,即使他們幫助我達到這一點......
如果需要任何改進,請告訴我,我會更新它:)。
為了獲得准確且文化上不可知的答案,我執行以下操作:
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);
如果數字太大,其他解決方案將丟失數字。
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;
}
算法:
|decimal|
到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;
}
我沒有對此進行過測試,但我會保持簡單並做到:
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();
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.