Знайдіть кількість десяткових знаків у десятковому значенні незалежно від культури


91

Цікаво, чи є стислий і точний спосіб витягнути кількість десяткових знаків у десятковому значенні (як int), який буде безпечним для використання в різних відомостях про культуру?

Наприклад:
19.0 повинен повернути 1,
27.5999 повинен повернути 4,
19.12 повинен повернути 2 і
т.д.

Я написав запит, який зробив розбиття рядка на крапку для пошуку десяткових знаків:

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

Але мені здається, що це буде працювати лише в регіонах, які використовують "." як десятковий роздільник і тому дуже крихкий у різних системах.


Десятковий знак відповідно до назви запитання
Джессі Картер

А як щодо деяких збігів шаблонів до розділення? В основному \ d + (\ D) \ d + де \ D повертає роздільник (. Тощо)
Аншул

7
Це не закрите питання, оскільки воно може спочатку з’явитися червоним. Прохання 19.0повернути 1- це деталь реалізації щодо внутрішнього зберігання вартості 19.0. Справа в тому, що це абсолютно законно для програми , щоб зберегти це як 190×10⁻¹або 1900×10⁻²або 19000×10⁻³. Усі вони рівні. Той факт, що він використовує перше подання, коли йому надано значення, 19.0Mі це виявляється при використанні ToStringбез специфікатора формату, це просто випадковість і щаслива річ. Окрім того, що не радує, коли люди покладаються на показник показника у випадках, коли цього не слід робити.
ErikE

Якщо ви хочете , типу , який може нести «кількість знаків після коми використовується» , коли він буде створений, так що ви можете надійно відрізнити 19Mвід 19.0Mвід 19.00M, вам необхідно створити новий клас, який об'єднує основну цінність в якості одного властивості і кількість десяткові коми як інша властивість.
ErikE

1
Незважаючи на те, що десятковий клас може "відрізнити" 19 м, від 19,0 м від 19,00 м? Значущі цифри схожі на один із основних випадків його використання. Що таке 19,0 м * 1,0 м? Здається, кажуть, 19.00 м, можливо розробники C # все-таки неправильно роблять математику: P? Знову ж значні цифри - це справжня річ. Якщо вам не подобаються значні цифри, ви, мабуть, не повинні використовувати десятковий клас.
Nicholi

Відповіді:


169

Я використав спосіб Джо, щоб вирішити цю проблему :)

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

6
Подивившись на це ще раз і побачивши це в дії, я позначаю це як відповідь, тому що це, на мій погляд, найбільш стислий і елегантний метод повернення десяткових знаків, який я бачив тут. Знову +1, якби міг: D
Джессі Картер

9
decimalзберігає цифру підрахунку після коми, ось чому ви знайдете цю "проблему", вам доведеться привести десяткове до подвійного і ще раз до десяткового для виправлення: BitConverter.GetBytes (decimal.GetBits ((decimal) (double) аргумент) [3]) [ 2];
burning_LEGION

3
Це не спрацювало для мене. Значення, яке повертається з SQL, становить 21,17, тобто 4 цифри. Тип даних визначається як DECIMAL (12,4), тому, можливо, це все (за допомогою Entity Framework).
PeterX

11
@Nicholi - Ні, це надзвичайно погано, оскільки метод покладається на розміщення основних бітів десяткової коми - те, що має багато способів представити одне і те ж число . Ви б не перевірили клас на основі стану його приватних полів?
m.edmondson

14
Не впевнений, що в цьому має бути елегантним чи приємним. Це приблизно так само затуманено, наскільки це стає. Хто знає, чи працює це взагалі у всіх випадках. Переконатися в цьому неможливо.
usr

24

Оскільки жодна з поданих відповідей не була достатньо хорошою, щоб магічне число "-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,01 м * 2,0 м = 0,020 м. Має бути 3 цифри, ваш метод повертає 2. Здається, ви неправильно розумієте, що відбувається, коли ви додаєте 0,01f до десяткової. Плаваючі крапки за своєю суттю не є точними, тому фактичне двійкове значення, що зберігається для 0,01f, не є точним. Коли ви робите десяткове число (дуже структурована цифрова нотація), ви не можете отримати 0,01 м (насправді отримуєте 0,010 м). Рішення GetBits насправді є правильним для отримання кількості цифр з десяткової коми. Ключовим є те, як конвертувати в десяткову.
Nicholi

2
@Nicholi 0,020m дорівнює 0,02m .. кінцеві нулі не є значущими. OP запитує в заголовку "незалежно від культури" і ще більш конкретно пояснює "..", що буде безпечним для використання в різних відомостях про культуру .. "- тому я думаю, що моя відповідь залишається навіть більш вагомою, ніж інші.
GY

6
OP конкретно сказав: "19.0 має повернути 1". У цьому випадку цей код не працює.
daniloquio

9
можливо, це не те, що хотів ОП, але ця відповідь більше відповідає моїм потребам, аніж головна відповідь на це питання
Арсен Загрей

2
Перші два рядки повинні бути замінені , n = n % 1; if (n < 0) n = -n;оскільки значення більше , ніж int.MaxValueбуде викликати OverflowException, наприклад 2147483648.12345.
Ненависть

24

Я б, мабуть, використав рішення у відповіді @ fixagon .

Однак, хоча в десятковій структурі немає методу для отримання кількості десяткових знаків, ви можете викликати Decimal.GetBits для вилучення двійкового подання, а потім використовувати ціле значення та масштаб для обчислення кількості десяткових знаків.

Це, мабуть, було б швидше, ніж форматування у вигляді рядка, хоча вам доведеться обробити дуже багато знаків після коми, щоб помітити різницю.

Я залишу реалізацію як вправу.


1
Дякую @Joe, що це дійсно акуратний спосіб підійти до цього. Залежно від того, як мій начальник ставиться до використання іншого рішення, я погляну на реалізацію вашої ідеї. Це точно було б веселою вправою :)
Джессі Картер

17

Одне з найкращих рішень для пошуку кількості цифр після десяткової коми показано в публікації burning_LEGION .

Тут я використовую частини статті на форумі STSdb: Кількість цифр після десяткової коми .

У MSDN ми можемо прочитати таке пояснення:

"Десяткове число - це значення з плаваючою точкою, яке складається із знака, числового значення, де кожна цифра у значенні коливається від 0 до 9, та масштабного коефіцієнта, що вказує на положення плаваючої десяткової коми, що розділяє інтеграл і дробове число частини числового значення. "

А також:

"Двійкове представлення десяткового значення складається з 1-бітового знака, 96-бітового цілого числа та масштабного коефіцієнта, що використовується для розділення 96-бітового цілого числа та вказівки, яка його частина є десятковою часткою. неявно число 10, підняте до показника в межах від 0 до 28. "

На внутрішньому рівні десяткове значення представлене чотирма цілими значеннями.

Десяткове внутрішнє подання

Існує загальнодоступна функція 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 також говорить, що коефіцієнт масштабування також зберігає будь-які кінцеві нулі в десятковому числі. Кінцеві нулі не впливають на значення десяткового числа в арифметичних або операціях порівняння. Однак кінцеві нулі можуть бути виявлені методом ToString, якщо застосовано відповідний рядок формату.

Це рішення виглядає як найкраще, але зачекайте, є й більше. Звертаючись до приватних методів у 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. Зверніть увагу, що функція отримує десяткове значення як ref, тому фактичне копіювання не виконується - лише посилання на значення. Використовувати функцію GetDigits із DecimalHelper просто:

decimal value = 3.14159m;
int digits = DecimalHelper.Instance.GetDigits(ref value);

Це найшвидший можливий метод отримання числа цифр після десяткової коми для десяткових значень.


3
десятковий r = (десятковий) -0,01f; і рішення не вдається. (щодо всіх відповідей, які я бачив на цій сторінці ...) :)
GY

5
ПРИМІТКА. Що стосується всього (десяткового) значення 0,01f, ви перекидаєте плаваючу крапку, по суті НЕ ТОЧНУ, на щось дуже структуроване, як десяткове число. Погляньте на результат Console.WriteLine ((десятковий) 0,01f). Десяткове число, яке формується у складі, НАСАД має 3 цифри, тому всі запропоновані рішення говорять 3, а не 2. Насправді все працює належним чином, "проблема" в тому, що ви очікуєте, що значення з плаваючою точкою будуть точними. Вони не.
Ніколі

@Nicholi Ваша точка не вдається, коли ви це усвідомлюєте 0.01і 0.010є рівно рівними числами . Крім того, ідея про те, що числовий тип даних має якусь семантику "кількість використаних цифр", на яку можна покластися, є абсолютно помилковою (не плутати з "дозволеною кількістю цифр". Не плутати презентацію (відображення значення числа в певній основі, наприклад, десяткове розширення значення, зазначеного двійковим розширенням 111), з базовим значенням! Щоб повторити, цифри не є цифрами і не складаються з цифр .
ErikE

6
Вони еквівалентні за значенням, але не значними цифрами. Що є великим випадком використання класу Decimal. Якби я запитав, скільки цифр у буквальному значенні 0,010м, ви б сказали лише 2? Незважаючи на те, що десятки вчителів математики / природничих наук по всьому світу сказали б вам, що остаточне значення 0 є значним? Проблема, на яку ми маємо на увазі, проявляється переведенням з плаваючих крапок у десяткові. Не використання самого GetBits, який робить саме так, як це задокументовано. Якщо ви не дбаєте про значні цифри, тоді у вас є проблема, і, ймовірно, не слід використовувати клас Decimal в першу чергу.
Nicholi

1
@theberserker Наскільки я пам’ятаю, не було ніякої злови - вона повинна працювати в обидві сторони.
Крістіян Димитров

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: Це означає, що ти завжди можеш поділитися ..
Остін Салонен

@AustinSalonen Справді? Я не знав, що використання InvariantCulture примусить використовувати крапку як десятковий роздільник
Джессі Картер

як ви робили раніше, це завжди буде визначати ціну, встановлюючи символ. як десятковий роздільник. але це, на мій погляд, не найелегантніший спосіб ...
fixagon


8

Більшість людей тут, здається, не усвідомлюють, що десяткові розглядають нульові нулі як важливі для зберігання та друку.

Отже, 0,1 м, 0,10 м та 0,100 м можуть порівнюватися як рівні, вони зберігаються по-різному (як значення / масштаб 1/1, 10/2 та 100/3 відповідно) і будуть надруковані як 0,1, 0,10 та 0,100 відповідно , автор ToString().

Таким чином, рішення, які повідомляють про "занадто високу точність", насправді повідомляють про правильну точність, на decimalумовах Росії.

Крім того, математичні рішення (наприклад, множення на степені 10), швидше за все, будуть дуже повільними (десяткове значення ~ 40 разів повільніше, ніж подвійне для арифметики, і ви не хочете мішати в плаваючій точці, оскільки це, ймовірно, може внести неточність ). Подібним чином, кастинг intабо longяк засіб скорочення схильний до помилок ( decimalмає набагато більший діапазон, ніж будь-який із них - він базується на 96-бітному цілому числу).

Незважаючи на те, що це не елегантно, наступне, швидше за все, буде одним із найшвидших способів отримати точність (коли воно визначається як "десяткові коми, крім нульових нулів"):

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

Інваріантна культура гарантує "." як десяткову крапку обрізаються кінцеві нулі, і тоді залишається лише побачити, скільки позицій залишається після десяткової коми (якщо вона взагалі є).

Редагувати: змінено тип повернення на int


1
@mvmorten Не впевнений, чому ти вважаєш необхідним змінити тип повернення на int; байт точніше представляє повернене значення: без знака та малий діапазон (0-29, на практиці).
Застай

1
Я погоджуюсь з тим, що ітераційні рішення на основі розрахунків є повільними (крім того, що не враховують кінцеві нулі). Однак виділення рядка для цього та робота на цьому замість цього також не є найефективнішою справою, особливо у критично важливих контекстах та з повільним GC. Доступ до шкали за допомогою логіки вказівника є набагато швидшим та без розподілу.
Мартін Тіло Шміц

Так, отримати масштаб можна набагато ефективніше - але це включало б кінцеві нулі. А для їх видалення потрібно робити арифметику на цілочисельній частині.
Застай

6

І ось ще один спосіб, використовуйте тип SqlDecimal, який має властивість масштабу з підрахунком цифр праворуч від десяткової. Приведіть своє десяткове значення до SqlDecimal, а потім отримайте доступ до Scale.

((SqlDecimal)(decimal)yourValue).Scale

1
Дивлячись на довідковий код Microsoft , приведення в SqlDecimal внутрішньо використовує, GetBytesтому він виділяє масив Byte замість доступу до байтів у небезпечному контексті. У посилальному коді є навіть примітка та коментований код, де зазначено, що і як вони можуть це зробити. Чому вони цього не зробили, для мене загадка. Я не хотів би цього розуміти і отримувати доступ до біт масштабу безпосередньо, замість того, щоб приховувати GC Alloc у цьому складі, оскільки просто не дуже очевидно, що він робить під капотом.
Мартін Тіло Шміц

4

На сьогоднішній день майже всі перераховані рішення виділяють пам’ять GC, що в значній мірі є способом C #, але далеко не ідеальним у критичних робочих середовищах. (Ті, які не виділяють, використовують цикли, а також не беруть до уваги кінцеві нулі.)

Тому, щоб уникнути GC Allocs, ви можете просто отримати доступ до бітів масштабу в небезпечному контексті. Це може здатися крихким, але згідно з джерелом посилань Microsoft , структурна схема розмітки десяткової є послідовною і навіть містить коментар, щоб не змінювати порядок полів:

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

Це має бути найефективнішим рішенням, оскільки немає розподілу GC масиву байтів або перетворення ToString. Я протестував його на .Net 4.x та .Net 3.5 в Unity 2019.1. Якщо є якісь версії, де це не вдається, повідомте мене про це.

Редагувати:

Дякую @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;
    }
}

Для того, щоб вирішити вихідну задачу, ви могли б стирають все поля , крім Valueі , Scaleале , можливо , це може бути корисним для кого - то , щоб мати їх все.


1
Ви також можете уникнути небезпечного коду, закодувавши власну структуру з макетом експлікта - поставте десятковий знак у позицію 0, потім байти / ints у відповідних місцях. Щось на кшталт:[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, хороший момент. Я також застосував цей підхід. :)
Мартін Тіло Шміц

1
Зауважимо одне: встановлення шкали поза діапазоном 0-28 спричиняє поломку. ToString (), як правило, працює, але арифметика не вдається.
Застай

Ще раз спасибі @Zastai, я додав чек на це :)
Мартін Тіло Шміц

Інша справа: кілька людей тут не хотіли брати до уваги кінцеві десяткові нулі. Якщо ви визначите const decimal Foo = 1.0000000000000000000000000000m;тоді ділення десяткового на, що буде масштабувати його до найнижчого можливого масштабу (тобто більше не включаючи кінцеві десяткові нулі). Я не тестував це, щоб перевірити, чи це швидше, ніж підхід на основі рядків, який я запропонував деінде.
Застай

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 схожий на вашу другу відповідь, хоча для чогось подібного я віддаю перевагу ітераційному підходу, а не використанню рекурсії
Джессі Картер

Оригінал тексту свідчить , що: 19.0 should return 1. Це рішення завжди передбачатиме мінімальну кількість 1 десяткової коми і ігноруватиме кінцеві нулі. десяткові можуть мати такі, оскільки використовують масштабний коефіцієнт. Коефіцієнт масштабування можна отримати, як у байтах 16-24 елемента з індексом 3 у масиві, отриманому з Decimal.GetBytes()використанням логіки покажчика.
Мартін Тіло Шміц

2

Я використовую щось дуже схоже на відповідь Клімента:

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

Не забудьте використовувати System.Windows.Forms, щоб отримати доступ до Application.CurrentCulture


1

Ви можете спробувати:

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

7
Чи не провалиться це, коли десяткове число - це ціле число? [1]
Silvermind

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
Це не працює для мене, оскільки представлення ToString () десяткового значення додає "00" у кінець моїх даних - я використовую тип даних Decimal (12,4) із SQL Server.
PeterX

Чи можете ви передати свої дані в десятковий тип c # і спробувати рішення. Для мене, коли я використовую Tostring () для десяткового значення c #, я ніколи не бачу "00".
Шрікант

1

За допомогою рекурсії ви можете зробити:

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

Оригінал тексту свідчить , що: 19.0 should return 1. Це рішення буде ігнорувати кінцеві нулі. десяткові можуть мати такі, оскільки використовують масштабний коефіцієнт. Коефіцієнт масштабування можна отримати як у байтах 16-24 елемента з індексом 3 у Decimal.GetBytes()масиві або за допомогою логіки покажчика.
Мартін Тіло Шміц

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

Як метод десяткового розширення, який враховує:

  • Різні культури
  • Цілі числа
  • Негативні числа
  • Закінчені задані нулі на десятковій точці (наприклад, 1.2300M поверне 2, а не 4)
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;
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.