C# Некорректно работает вывод символов из переменной string

c# почему скрипт ниже при вводе 10000 на выход выдает 97,0К? а при 100000 145К?

private string Output(int value)
{
        string text, lasttext = "";
        text = value.ToString();
        switch (text.Length)
        {
            case int n when n <= 3:
                lasttext = "";
                return text;
            case int n when n > 3 && n <= 6:
                lasttext = "K";
                break;
            case int n when n > 6 && n <= 9:
                lasttext = "M";
                break;
            case int n when n > 9 && n <= 12:
                lasttext = "B";
                break;
            case int n when n > 12 && n <= 15:
                lasttext = "T";
                break;
            case int n when n > 15 && n <= 18:
                lasttext = "Q";
                break;
        }
        switch (text.Length % 3)
        {
            case 1:
                text = text[0] + "," + text[1] + text[2] + lasttext;
                break;
            case 2:
                text = text[0] + text[1] + "," + text[2] + lasttext;
                break;
            case 0:
                text = text[0] + text[1] + text[2] + lasttext;
                break;
        }
        return text;
    }

Ответы (3 шт):

Автор решения: Yaroslav

Куда проще записывать эти ультра большие числа для Idle игр, структурой.

[Serializable]
public struct BigNumber
{
    public float Number;
    public int ZeroCount;

    ...
}

Если Number переваливает за 100, число делится на 10, а ZeroCount увеличивается и на оборот.

...

public BigNumber (float number, int zeroCount) 
{
    Number = number;
    ZeroCount = zeroCount;
    Optimize();
}

private void Optimize () 
{
    if (Number > 1000) 
    {
        Number /= 10f;
        ZeroCount++;
        Optimize();
    } 
    else if (Number < 100 && zeroCount > 0)
    {
        Number *= 10f;
        ZeroCount--;
        Optimize();
    }
}

И самое чудесное, это то что можно прописать математические операции + и - для этой структуры.

...

public static BigNumber operator + (BigNumber a, BigNumber b)
{
    if (a.ZeroCount == b.ZeroCount)
    {
        return new BigNumber(a.Number + b.Number, a.ZeroCount);
    } 
    else if (a.ZeroCount > b.ZeroCount)
    {
        int zeroDelta = a.ZeroCount - b.ZeroCount;
        if (zeroDelta > 6)
            return a; // b much less
        int ANumber = a.ZeroCount * 10f * zeroDelta;
        return new BigNumber(ANumber * b.Number, b.ZeroCount);
    }
    else
    {
        return b + a;
    }
}

public static BigNumber operator - (BigNumber a, BigNumber b)
{
    ...
}

Что бы получить суффикс достаточно ZeroCount / 3 и получишь индекс в массиве суфиксов, не нужен никакой стоэтажный switch. И выполнить это можно для удобства в перегрузке ToString().

private const string[] Suffix = new[] {"", "k", "m", "t" ...};
...

public override string ToString () 
{
    int dIndex = ZeroCount / 3;
    string suffix = Suffix[dIndex];
    return Number.ToString("F2") + suffix;
}

Если цифры совсем запредельные, ZeroCount можно поменять на long. Если нужна большая точьность для прибавления малых числел, Number можно поменять на double.

→ Ссылка
Автор решения: CrazyElf

Давайте посмотрим, что тут происходит. В вашем первом случае исполняется эта ветка кода:

case 2:
    text = text[0] + text[1] + "," + text[2] + lasttext;
           ^^^^^^^^^^^^^^^^^
    // 97,0K

Посмотрим первые два слагаемых:

  • text[0] - это тип char, значение '1', код символа 49
  • text[1] - это тип char, значение '0', код символа 48

Когда эти два значения складываются, они складываются именно как числа, равные коду символов и получается... число 97! Дальше следует прибавление строки. Вот в этот момент 97 преобразуется в строку "97" и дальше уже идёт обычная конкатенация строк, что бы там справа не прибавлялось.

"Лечится" такое элементарно - просто начинаем формирование переменной с пустой строки и всё тогда сразу преобразуется в строки и конкатенируется обычным образом:

    text = "" + text[0] + text[1] + "," + text[2] + lasttext;
           ^^^^^
    // 10,0K

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

    text = $"{text[0]}{text[1]},{text[2]}{lasttext}";
    // 10,0K
→ Ссылка
Автор решения: EvgeniyZ

Почему вы получаете такой результат, вам ответ уже дали. Тут я лишь чуть подробней распишу то, о чем написал в комментарии.

Вашу задачу можно (и мне кажется нужно) решать математическим путем, без преобразований числа в строку, без кучи if и switch, всего несколько строк кода с довольно простыми расчетами.

Смотрите, вот есть число 1 234 567, как видите, числа идут некими группами по 3 числа, по которым можно понять, что если группа одна, то это "тысячи", если две, то уже миллионы, три миллиарды, и так далее. Для подсчета этого добра математически, очень классно могут помочь логарифмы, а точнее log10. Если мы воспользуемся им, то увидим такую картину: log10(1) = 0, log10(12) = 1, log10(123) = 2 ... Как видите, это дает нам кол-во чисел после первого, что нам и надо. Теперь делим это число на 3 (log10(1000) = 3) и получаем кол-во групп (log10(1 000)/3 = 1, log10(1 000 000)/3 = 2, log10(1 000 000 000)/3 = 3). Если быть более точным, то так мы нашли с вами порядок числа.

Теперь давайте число сделаем более компактным (масштрабируем число), то есть, если число 1 234 567, то мы знаем, что это 1 миллион с копейками, а если точнее, то 1,2. Вот чтобы число так сократить, нам надо поделить его на 1000 в степени, которую получили ранее, в итоге получаем такое: 1 234 567/1000^2 = 1,234567

Вот и вся математика, как видите, все довольно просто. Теперь напишем это на C#.
Вся математика находится в классе Math, из которого нам нужен метод Log10 и метод возведения в степень (Pow). Получаем тогда такой код:

var number = 1_234_567;
var exponent = (int)Math.Log10(number) / 3;     //2
var scaled = number / Math.Pow(1000, exponent); // 1.234567

Основное у нас есть, осталось сформировать нужный вид, а точнее "обрезать" число и добавить суффикс (букву в конец).

  • Суффиксы - с ними все до безобразия просто. Делаем массив из всех нужных обозначений, допустим var suffixes = new[] { "K", "M", "B", "T", "Qa", "Qi" };, а дальше берем по индексу нужное, например так: suffixes[exponent - 1] (индекс с 0 идет).

  • Формат - Можете делать как вы, "склеивая" строки, ну а я сделаю что-то такое $"{scaled:0.#}{suffix}". Тут используется интерполяция строк с форматированием чисел. Результатом будет 1,2M

Весь конечный код будет таким:

var number = 1_234_567;
var exponent = (int)Math.Log10(number) / 3;
var scaled = number / Math.Pow(1000, exponent);
var suffixes = new[] { "K", "M", "B", "T", "Qa", "Qi" };
var suffix = suffixes[exponent - 1];
var result = $"{scaled:0.#}{suffix}";

Тут нету ряда проверок, из-за чего он будет падать, если число будет выше Qi, ну или ниже K (например 1), моей задачей было лишь показать принцип, а дальше уже крутите как вам нужно.


Теперь давайте поговорим про удобство. Я сейчас абстрагируюсь от Unity, ибо буду использовать современные аспекты языка, которых в Unity нету, но в конце я скажу как можно это обойти.

Обычно для решения таких задач пишут методы расширения, которые расширяют определенный тип, и которые мы можем вызвать в любом месте. Собственно, давайте сделаем такой метод, да еще и сделаем его универсальным, чтобы он подходил к любому числовому типу.

public static class FormatExtensions
{
    public static string ToHuman<T>(this T number) where T : INumber<T>
    {
        var doubleValue = double.CreateChecked(number);
        var suffixes = new[] { "K", "M", "B", "T", "Qa", "Qi" };
        var exponent = doubleValue < 1000 ? 0 : (int)(Math.Log10(doubleValue) / 3);
        var divisor = Math.Pow(1000, exponent);
        return exponent == 0
            ? doubleValue.ToString("0.#") // Число меньше 1000
            : exponent - 1 < suffixes.Length
                ? $"{doubleValue / divisor:0.#}{suffixes[exponent - 1]}"
                : $"{doubleValue / Math.Pow(1000, suffixes.Length):0.#}{suffixes[^1]}"; // Вышли за пределы массива суффиксов
    }
}
  • T - Это Generic тип, который позволяет не привязываться к чему-то одному.
  • this T - через this мы получаем конкретное значение, к которому будет применен этот метод.
  • where T : INumber<T> - в .NET 7 появился данный интерфейс, который реализуют все стандартные числовые типы. В более ранних версиях тут можно попробовать задать IComparable.
  • CreateChecked() - Тоже нововведение .NET 7. Метод создает конкретный тип (в нашем случае double, чтобы не терять дробную часть, если есть) из INumber<>. В более ранних версиях можно попробовать воспользоваться Convert.ToDouble().
  • doubleValue < 1000 ? 0 - Тут проверка, если число меньше 1000, то отдаем 0, по которому дальше мы понимаем, что суффиксы и прочие приблуды добавлять нет смысла.
  • exponent - 1 < suffixes.Length - Не даем выйти за пределы длины массива суффиксов.
  • $"{doubleValue / Math.Pow(1000, suffixes.Length):0.#}{suffixes[^1]}"; - Если подходящего суффикса нет, то масштабируем число под последний доступный и выводим его. (Как по мне, тут лучше уже научную нотацию использовать ($"{doubleValue:e2}"; например), тогда будет и компактней и понятней).

Использование будет таким:

var result = 1_234_567.ToHuman(); // "1,2M"

Простые тесты:

1.ToHuman();                // 1
1_000.ToHuman();            // 1K
1_000_000.ToHuman();        // 1M
1_000_000_000.ToHuman();    // 1B
1.281.ToHuman();            // 1,3
572_353.286.ToHuman();      // 572,4K
1_751.92f.ToHuman();        // 1,8K
74_626_224L.ToHuman();      // 74,6M
int.MaxValue.ToHuman();     // 2,1B
double.MaxValue.ToHuman();  // 1797693134862320000000000000000000000000000000000000..(куча нулей)..Qi ... (нет подходящего суффикса в массиве)
float.MaxValue.ToHuman();   // 340282346638529000000Qi (нет подходящего суффикса в массиве)
long.MaxValue.ToHuman();    // 9,2Qi
→ Ссылка