문화에 관계없이 소수점 이하 자릿수 찾기


91

다른 문화 정보에서 안전하게 사용할 수있는 소수 값 (정수)에서 소수 자릿수를 추출하는 간결하고 정확한 방법이 있는지 궁금합니다.

예를 들어
19.0은 1을,
27.5999는 4를,
19.12는 2를 반환해야합니다
.

소수점 이하 자릿수를 찾기 위해 마침표로 문자열을 분할하는 쿼리를 작성했습니다.

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

그러나 이것이 '.'를 사용하는 지역에서만 작동한다는 생각이 듭니다. 소수 구분 기호로 사용되므로 다른 시스템에서 매우 취약합니다.


질문 제목에 따른 소수
Jesse Carter

분할 이전에 패턴 매칭은 어떻습니까?. 기본적으로 \ d + (\ D) \ d + 여기서 \ D는 구분 기호 (. 등)를 반환합니다.
Anshul

7
이것은 처음에 얼굴이 붉어 질 수 있기 때문에 닫힌 질문이 아닙니다. 요구가 19.0돌아가려면 1이다 구현 세부 값의 내부 저장 장치에 관한 19.0. 사실은 프로그램이 이것을 190×10⁻¹또는 1900×10⁻²또는 로 저장하는 것이 완벽하게 합법적이라는 것입니다 19000×10⁻³. 모두 동일합니다. 값이 주어 졌을 때 첫 번째 표현을 19.0M사용 ToString하고 형식 지정자없이 사용할 때 노출 된다는 사실 은 우연의 일치이며 행복한 것입니다. 사람들이 지수에 의존해서는 안되는 경우를 제외하고는 행복하지 않습니다.
ErikE 2015-08-21

당신은 당신이 확실하게 구별 할 수 있도록 만들 때 "소수점 이하 자릿수 사용"수행 할 수있는 유형 싶다면 19M에서 19.0M에서를 19.00M, 당신은 하나 개의 속성과 기본 가치와 숫자의 번들 새로운 클래스를 만들어야합니다 소수점 이하 자릿수를 다른 속성으로 사용합니다.
ErikE 2015-08-21

1
Decimal 클래스는 19m, 19.0m와 19.00m를 "구분"할 수 있지만? 유효 숫자는 주요 사용 사례 중 하나와 같습니다. 19.0m * 1.0m는 무엇입니까? 19.00m라고 말하는 것 같습니다. 아마도 C # 개발자가 수학을 잘못하고있을 수도 있습니다. : P? 다시 유효 숫자는 실제입니다. 유효 숫자가 마음에 들지 않으면 Decimal 클래스를 사용하지 않아야합니다.
Nicholi

답변:


168

이 문제를 해결하기 위해 Joe의 방법 을 사용 했습니다. :)

decimal argument = 123.456m;
int count = BitConverter.GetBytes(decimal.GetBits(argument)[3])[2];

6
이것을 더 자세히 살펴보고 실제로 확인한 후 나는 이것을 답으로 표시하고 있습니다. 이것이 여기에서 본 가장 간결하고 우아한 소수점 반환 방법입니다. 내가 할 수 있다면 다시 +1 할 것입니다 : D
Jesse Carter

9
decimal이 "문제"를 발견 한 이유입니다. 수정을 위해 십진수를 두 배로, 십진수로 다시 변환해야합니다. BitConverter.GetBytes (decimal.GetBits ((decimal) (double) argument) [3]) [ 2];
burning_LEGION dec.

3
이것은 나를 위해 작동하지 않았습니다. SQL에서 반환되는 값은 21.17이며 4 자리 숫자입니다. 데이터 유형은 DECIMAL (12,4)로 정의되어 있으므로 Entity Framework를 사용하는 것입니다.
PeterX

11
@Nicholi - 아니,이는 매우 나쁜 방법은의 위치에 의존하기 때문에 , 소수의 기본 비트 가 무엇인가 - 여러 가지 대표하는 같은 수의 . 개인 필드의 상태를 기반으로 클래스를 테스트하지 않겠습니까?
m.edmondson

14
이것에 대해 우아하거나 좋은 것이 무엇인지 확실하지 않습니다. 이것은 거의 난독 화되어 있습니다. 모든 경우에 작동하는지 누가 알겠습니까? 확인할 수 없습니다.
usr

24

제공된 답변 중 어느 것도 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
}

6
후행 0을 포함하고 숫자가 중요한 여러 경우에 대한 솔루션이 실패합니다. 0.01m * 2.0m = 0.020m. 3 자리 여야합니다. 메서드는 2를 반환합니다. 0.01f를 Decimal로 캐스트하면 어떤 일이 발생하는지 잘못 이해하는 것 같습니다. 부동 소수점은 본질적으로 정확하지 않으므로 0.01f에 대해 저장된 실제 이진 값은 정확하지 않습니다. Decimal (매우 구조화 된 숫자 표기법)으로 캐스트하면 0.01m (실제로는 0.010m)을 얻지 못할 수 있습니다. GetBits 솔루션은 Decimal에서 자릿수를 얻는 데 실제로 정확합니다. Decimal로 변환하는 방법이 핵심입니다.
Nicholi

2
@Nicholi 0.020m는 0.02m와 같습니다. 후행 0은 중요하지 않습니다. OP는 제목에 "regardless of culture"를 묻고 있으며보다 구체적으로 ".. 다른 문화 정보에서 안전하게 사용할 수있을 것입니다 .."라고 설명하고 있습니다. 따라서 제 대답은 다른 것보다 훨씬 더 유효하다고 생각합니다.
GY 2015 년

6
OP는 구체적으로 "19.0은 1을 반환해야합니다"라고 말했습니다. 이 코드는이 경우 실패합니다.
daniloquio

9
아마도 이것은 OP가 원했던 것이 아닐 수도 있지만이 답변은이 질문의 최상위 답변보다 내 필요에 더 적합합니다
Arsen Zahray

2
처음의 두 행은 교체해야 n = n % 1; if (n < 0) n = -n;보다 큰 값으로 인해 int.MaxValue발생할 OverflowException2147483648.12345.
혐오

23

나는 아마도 @fixagon의 대답 에서 해결책을 사용할 것입니다 .

그러나 Decimal 구조체에는 소수 자릿수를 가져 오는 메서드가 없지만 Decimal.GetBits 를 호출 하여 이진 표현을 추출한 다음 정수 값과 배율을 사용하여 소수 자릿수를 계산할 수 있습니다.

차이를 알아 차리려면 엄청난 양의 소수를 처리해야하지만 이것은 문자열로 포맷하는 것보다 빠를 것입니다.

구현은 연습으로 남겨 두겠습니다.


1
그것에 접근하는 정말 깔끔한 방법 인 @Joe에게 감사드립니다. 상사가 다른 솔루션을 사용하는 것에 대해 어떻게 생각하는지에 따라 아이디어를 구현하는 방법을 살펴볼 것입니다. 확실히 재미있는 운동 : 것
제시 카터

17

소수점 이하 자릿수를 찾는 가장 좋은 솔루션 중 하나는 burning_LEGION의 게시물에 나와 있습니다.

여기에 STSdb ​​포럼 기사의 일부를 사용하고 있습니다 . 소수점 이하 자릿수 .

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 };
}

반환 된 배열의 네 번째 요소에는 배율 인수와 부호가 포함됩니다. 그리고 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은 배율 인수가 Decimal 숫자의 후행 0도 유지한다고 말합니다. 후행 0은 산술 또는 비교 연산에서 십진수 값에 영향을주지 않습니다. 그러나 적절한 형식 문자열이 적용되면 ToString 메서드에서 후행 0이 표시 될 수 있습니다.

이 솔루션은 최고의 솔루션처럼 보이지만 더 많은 것이 있습니다. 으로 C #에서 개인 방법에 접근 우리는 플래그 필드에 직접 액세스를 구축하고 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);

소수점 이하 자릿수를 구하는 가장 빠른 방법입니다.


3
십진수 r = (십진수) -0.01f; 솔루션이 실패합니다. (이 페이지에서 본 모든 답변에 대해 ...) :)
GY

5
참고 : 전체 (Decimal) 0.01f 항목에 대해 본질적으로 정확하지 않은 부동 소수점을 Decimal과 같은 매우 구조화 된 것으로 캐스팅합니다. Console.WriteLine ((Decimal) 0.01f)의 출력을 살펴보십시오. 캐스트에서 형성되는 Decimal은 실제로 3 자리 숫자를 가지고 있기 때문에 제공된 모든 솔루션이 2 대신 3이라고 말하는 이유입니다. 모든 것이 실제로 예상대로 작동합니다. "문제"는 부동 소수점 값이 정확할 것으로 예상한다는 것입니다. 그렇지 않습니다.
Nicholi

당신이 실현 될 때 @Nicholi 당신의 포인트는 실패 0.010.010정확하게 동일한 번호 . 더욱이 숫자 데이터 유형이 신뢰할 수있는 일종의 "사용 된 자릿수"의미 체계를 갖는다는 생각은 완전히 잘못되었습니다 ( "허용되는 자릿수"와 혼동하지 마십시오.). 특정 밑수에있는 숫자의 값, 예를 들어 이진 확장으로 표시된 값의 10 진수 확장 (111)을 기본 값으로! 반복하자면 숫자는 숫자가 아니며 숫자로 구성되지도 않습니다 .
ErikE

6
값은 동일하지만 유효 숫자는 아닙니다. Decimal 클래스의 큰 사용 사례입니다. 리터럴 0.010m에 몇 자릿수가 있는지 물으면 2 만 대답 하시겠습니까? 전 세계의 수학과 과학 교사들이 최종 0 점이 중요하다고 말할지라도? 우리가 언급하는 문제는 부동 소수점에서 Decimal로 캐스팅함으로써 나타납니다. GetBits 자체의 사용은 문서화 된대로 정확하게 수행되지 않습니다. 유효 숫자에 대해 신경 쓰지 않는다면 문제가 있으며 처음에 Decimal 클래스를 사용하지 않아야 할 것입니다.
Nicholi

1
@theberserker 내가 기억하는 한, 캐치가 없었습니다. 두 가지 방식으로 작동합니다.
Kristiyan Dimitrov 19.11.

13

소수의 내부 표현에 의존하는 것은 멋지지 않습니다.

이건 어때:

    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();
    }

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);
}

이것이 소수점 이하 자릿수를 찾는 데 어떻게 도움이됩니까? 십진수를 어떤 문화에서나 좋은 문자열로 변환하는 데 문제가 없습니다. 질문에 따라 나는 소수에 있던 소수 자릿수 찾기 위해 노력하고 있습니다
제시 카터

@JesseCarter : 그것은 당신이 항상 ..
Austin Salonen 2012

@AustinSalonen 정말? InvariantCulture를 사용하면 소수점 구분 기호로 마침표가 사용된다는 사실을 몰랐습니다
Jesse Carter

이전에했던 것처럼 항상 가격을. 소수점 구분 기호로. 하지만 내 생각에이 아니 가장 우아한 방법 ...
fixagon


8

여기에있는 대부분의 사람들은 소수점이 후행 0을 저장 및 인쇄에 중요한 것으로 간주한다는 사실을 모르고있는 것 같습니다.

따라서 0.1m, 0.10m 및 0.100m은 동일하게 비교할 수 있으며, 다르게 저장되며 (각각 값 / 스케일 1/1, 10/2 및 100/3) 각각 0.1, 0.10 및 0.100으로 인쇄됩니다. , 작성자 : ToString().

따라서 "너무 높은 정밀도"를보고하는 솔루션은 실제로 의 용어 에 따라 정확한 정밀도를 보고합니다 decimal.

또한 수학 기반 솔루션 (예 : 10의 거듭 제곱으로 곱하기)은 매우 느릴 수 있습니다 (10 진수는 산술의 경우 double보다 ~ 40 배 느리며 부정확성을 도입 할 가능성이 있기 때문에 부동 소수점에서 혼합하는 것을 원하지 않습니다.) ). 마찬가지로 자르기 수단 으로 int또는 long자르기 수단으로 캐스팅하는 것은 오류가 발생하기 쉽습니다 ( decimal둘 중 어느 것보다 훨씬 더 넓은 범위를 가지며, 약 96 비트 정수를 기반으로 함).

우아하지는 않지만 다음은 정밀도를 얻는 가장 빠른 방법 중 하나 일 것입니다 ( "후행 0을 제외한 소수 자릿수"로 정의 된 경우).

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;
}

불변 문화는 '.'를 보장합니다. 소수점으로 후행 0이 잘리고 소수점 뒤에 몇 자리가 남아 있는지 확인하는 문제입니다 (하나라도있는 경우).

편집 : 반환 유형을 int로 변경


1
@mvmorten 반환 유형을 int로 변경해야하는 이유가 확실하지 않습니다. 바이트는 반환 된 값을 더 정확하게 나타냅니다 : 부호없는 작은 범위 (실제로는 0-29).
Zastai

1
반복 및 계산 기반 솔루션이 느리다는 데 동의합니다 (후행 0을 고려하지 않고). 그러나이를 위해 문자열을 할당하고 대신 작동하는 것은 특히 성능이 중요한 컨텍스트와 느린 GC에서 수행 할 가장 성능이 좋은 일이 아닙니다. 포인터 로직을 통해 스케일에 액세스하는 것이 훨씬 빠르고 할당이 필요 없습니다.
Martin Tilo Schmitz

예, 스케일을 얻는 것이 훨씬 더 효율적으로 수행 될 수 있지만 여기에는 후행 0이 포함됩니다. 그리고 그것들을 제거하려면 정수 부분에서 산술을해야합니다.
Zastai

6

그리고 여기에 또 다른 방법이 있습니다. 소수의 오른쪽 자릿수와 함께 scale 속성이있는 SqlDecimal 유형을 사용합니다. 10 진수 값을 SqlDecimal로 캐스팅 한 다음 Scale에 액세스합니다.

((SqlDecimal)(decimal)yourValue).Scale

1
Microsoft 참조 코드를 살펴보면 SqlDecimal로 캐스팅하면 내부적으로를 사용 GetBytes하므로 안전하지 않은 컨텍스트에서 바이트에 액세스하는 대신 Byte 배열을 할당합니다. 참조 코드에 메모와 주석 처리 된 코드가 있으며 대신 어떻게 할 수 있는지 설명합니다. 그들이 왜 안했는지는 나에게 미스터리 다. 나는이 캐스트에서 GC Alloc을 숨기는 대신에 직접 스케일 비트에 액세스 할 것입니다. 왜냐하면 내부에서 무엇을하는지 명확하지 않기 때문입니다.
Martin Tilo Schmitz

4

지금까지 나열된 거의 모든 솔루션은 GC 메모리를 할당하고 있습니다. 이는 작업을 수행하는 C # 방식이지만 성능이 중요한 환경에서는 이상적이지 않습니다. (사용 루프를 할당하지 않고 후행 0을 고려하지 않는 것.)

따라서 GC 할당을 피하기 위해 안전하지 않은 컨텍스트에서 스케일 비트에 액세스 할 수 있습니다. 깨지기 쉬운 것처럼 들릴 수 있지만 Microsoft의 참조 소스 에 따라 decimal의 구조체 레이아웃은 Sequential이며 필드의 순서를 변경하지 않는 주석이 있습니다.

    // NOTE: Do not change the order in which these fields are declared. The
    // native methods in this class rely on this particular order.
    private int flags;
    private int hi;
    private int lo;
    private int mid;

보시다시피 첫 번째 int는 flags 필드입니다. 문서와 여기 다른 주석에서 언급했듯이 16-24의 비트 만 스케일을 인코딩하고 부호를 인코딩하는 31 번째 비트를 피해야한다는 것을 알고 있습니다. int는 4 바이트 크기이므로 안전하게 다음과 같이 할 수 있습니다.

internal static class DecimalExtensions
{
  public static byte GetScale(this decimal value)
  {
    unsafe
    {
      byte* v = (byte*)&value;
      return v[2];
    }
  }
}

이것은 바이트 배열 또는 ToString 변환의 GC 할당이 없기 때문에 가장 성능이 좋은 솔루션입니다. Unity 2019.1에서 .Net 4.x 및 .Net 3.5에 대해 테스트했습니다. 이것이 실패하는 버전이 있으면 알려주십시오.

편집하다:

안전하지 않은 코드 외부에서 동일한 포인터 논리를 실제로 달성하기 위해 명시 적 구조체 레이아웃을 사용할 수 있음을 상기시켜 준 @Zastai에게 감사드립니다.

[StructLayout(LayoutKind.Explicit)]
public struct DecimalHelper
{
    const byte k_SignBit = 1 << 7;

    [FieldOffset(0)]
    public decimal Value;

    [FieldOffset(0)]
    public readonly uint Flags;
    [FieldOffset(0)]
    public readonly ushort Reserved;
    [FieldOffset(2)]
    byte m_Scale;
    public byte Scale
    {
        get
        {
            return m_Scale;
        }
        set
        {
            if(value > 28)
                throw new System.ArgumentOutOfRangeException("value", "Scale can't be bigger than 28!")
            m_Scale = value;
        }
    }
    [FieldOffset(3)]
    byte m_SignByte;
    public int Sign
    {
        get
        {
            return m_SignByte > 0 ? -1 : 1;
        }
    }
    public bool Positive
    {
        get
        {
            return (m_SignByte & k_SignBit) > 0 ;
        }
        set
        {
            m_SignByte = value ? (byte)0 : k_SignBit;
        }
    }
    [FieldOffset(4)]
    public uint Hi;
    [FieldOffset(8)]
    public uint Lo;
    [FieldOffset(12)]
    public uint Mid;

    public DecimalHelper(decimal value) : this()
    {
        Value = value;
    }

    public static implicit operator DecimalHelper(decimal value)
    {
        return new DecimalHelper(value);
    }

    public static implicit operator decimal(DecimalHelper value)
    {
        return value.Value;
    }
}

원래 문제를 해결하려면 이외의 모든 필드를 벗겨 수 ValueScale하지만 누군가가 그들 모두를 가지고 어쩌면 그것은 도움이 될 수 있습니다.


1
또한 명시 적 레이아웃으로 자신의 구조체를 코딩하여 안전하지 않은 코드를 피할 수 있습니다. 소수점은 위치 0에 놓은 다음 바이트 / 정수는 적절한 위치에 놓습니다. 같은 것 :[StructLayout(LayoutKind.Explicit)] public struct DecimalHelper { [FieldOffset(0)] public decimal Value; [FieldOffset(0)] public uint Flags; [FieldOffset(0)] public ushort Reserved; [FieldOffset(2)] public byte Scale; [FieldOffset(3)] public DecimalSign Sign; [FieldOffset(4)] public uint ValuePart1; [FieldOffset(8)] public ulong ValuePart2; }
Zastai

감사합니다 @Zastai, 좋은 지적. 나는 그 접근 방식도 통합했습니다. :)
Martin Tilo Schmitz

1
한 가지주의 할 점은 0-28 범위를 벗어나면 스케일을 설정하면 파손됩니다. ToString ()은 작동하는 경향이 있지만 산술은 실패합니다.
Zastai

다시 한번 감사 @Zastai는, 그 :)에 대한 검사를 추가 한
마틴 틸로 슈미츠

또 다른 한가지 : 여기에있는 몇몇 사람들은 후행 십진수 0을 고려하고 싶지 않았습니다. const decimal Foo = 1.0000000000000000000000000000m;소수점 을 정의한 다음 소수점을 나누면 가능한 가장 낮은 스케일로 다시 조정됩니다 (즉, 더 이상 후행 소수점 0을 포함하지 않음). 나는 이것이 내가 다른 곳에서 제안한 문자열 기반 접근 방식보다 빠른지 여부를 확인하기 위해 이것을 벤치마킹하지 않았습니다.
Zastai

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;

@ fix-like-codings 두 번째 답변과 유사하지만 이와 같은 경우 재귀를 사용하는 것보다 반복적 인 접근 방식을 선호합니다
Jesse Carter

원래 게시물에는 다음과 같이 명시되어 19.0 should return 1있습니다.. 이 솔루션은 항상 최소 소수점 이하 1 자리를 가정하고 후행 0을 무시합니다. 십진수는 축척 비율을 사용하므로이를 가질 수 있습니다. 스케일 팩터는 Decimal.GetBytes()포인터 논리를 사용하거나 가져온 배열에서 인덱스 3을 가진 요소의 16-24 바이트에서와 같이 액세스 할 수 있습니다 .
Martin Tilo Schmitz

2

Clement의 답변과 매우 유사한 것을 사용하고 있습니다.

private int GetSignificantDecimalPlaces(decimal number, bool trimTrailingZeros = true)
{
  string stemp = Convert.ToString(number);

  if (trimTrailingZeros)
    stemp = stemp.TrimEnd('0');

  return stemp.Length - 1 - stemp.IndexOf(
         Application.CurrentCulture.NumberFormat.NumberDecimalSeparator);
}

Application.CurrentCulture에 액세스하려면 System.Windows.Forms를 사용해야합니다.


1

당신은 시도 할 수 있습니다:

int priceDecimalPlaces =
        price.ToString(System.Globalization.CultureInfo.InvariantCulture)
              .Split('.')[1].Length;

7
소수가 정수일 때 실패하지 않을까요? [1]
Silvermind 2011

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;
    }

십진수 입력 = 3.376; var instring = input.ToString ();

GetDecimalLength (instring) 호출


1
decmial 값의 ToString () 표현이 내 데이터 끝에 "00"을 추가하기 때문에이 방법이 작동하지 않습니다. SQL Server에서 Decimal (12,4) 데이터 유형을 사용하고 있습니다.
PeterX

데이터를 C # 형식 십진수로 캐스팅하고 솔루션을 시도 할 수 있습니까? 나를 위해 C # 십진수 값에 Tostring ()을 사용할 때 "00"이 표시되지 않습니다.
Srikanth

1

재귀를 사용하면 다음을 수행 할 수 있습니다.

private int GetDecimals(decimal n, int decimals = 0)  
{  
    return n % 1 != 0 ? GetDecimals(n * 10, decimals + 1) : decimals;  
}

원래 게시물에는 다음과 같이 명시되어 19.0 should return 1있습니다.. 이 솔루션은 후행 0을 무시합니다. 십진수는 축척 비율을 사용하므로이를 가질 수 있습니다. 스케일 팩터는 Decimal.GetBytes()배열에 인덱스 3이있는 요소의 16-24 바이트에서 또는 포인터 논리를 사용하여 액세스 할 수 있습니다 .
Martin Tilo Schmitz

1
string number = "123.456789"; // Convert to string
int length = number.Substring(number.IndexOf(".") + 1).Length;  // 6

0

이 방법을 사용하는 것이 좋습니다.

    public static int GetNumberOfDecimalPlaces(decimal value, int maxNumber)
    {
        if (maxNumber == 0)
            return 0;

        if (maxNumber > 28)
            maxNumber = 28;

        bool isEqual = false;
        int placeCount = maxNumber;
        while (placeCount > 0)
        {
            decimal vl = Math.Round(value, placeCount - 1);
            decimal vh = Math.Round(value, placeCount);
            isEqual = (vl == vh);

            if (isEqual == false)
                break;

            placeCount--;
        }
        return Math.Min(placeCount, maxNumber); 
    }

0

다음을 고려하는 십진 확장 방법으로 :

  • 다른 문화
  • 정수
  • 음수
  • 소수점 자리에 후행 설정 0 (예 : 1.2300M은 4가 아닌 2를 반환 함)
public static class DecimalExtensions
{
    public static int GetNumberDecimalPlaces(this decimal source)
    {
        var parts = source.ToString(CultureInfo.InvariantCulture).Split('.');

        if (parts.Length < 2)
            return 0;

        return parts[1].TrimEnd('0').Length;
    }
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.