[c#] 문화에 관계없이 소수점 이하 자릿수 찾기

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

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

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

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

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



답변

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

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


답변

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


답변

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

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

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

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


답변

소수점 이하 자릿수를 찾는 가장 좋은 솔루션 중 하나는 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);

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


답변

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

이건 어때:

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


답변

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


답변

여기에있는 대부분의 사람들은 소수점이 후행 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로 변경