Разрабатывая Idle игры часто сталкиваются с тем что нужно выразить число через его сокращенную форму. Если в игре используются маленькие числа (хотя бы до 20 знаков), то такие числа в принципе можно выражать в их существующем виде. Для примера в проекте unity создам отображение монет и кнопку, которая будет умножать число монет на некое значение:

Добавлю на кнопку скрипт который будет брать число из текстового поля с монетам и умножать его на число:

using System.Numerics;
using UnityEngine;
using UnityEngine.UI; 
public class MLMon : MonoBehaviour
{
    BigInteger money = 1;
    [SerializeField]
    GameObject TextMoney; 
    [SerializeField]
    int Multiplier; 
    public void ButtonClick()
    {
        money *= Multiplier;
        TextMoney.GetComponent<Text>().text = money.ToString();
    } 
}

Сам класс имеет 3 свойства – количество монет, ссылка по отображение монет и множитель. Свойства множителя и отображения монет отображены как сериализуемое поле. И 1 метод который будет умножать имеющееся у нас количество монет на множитель и записывать полученное число в отображение.

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

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

using System.Numerics;
using UnityEngine;
using UnityEngine.UI; 
public class MLMon : MonoBehaviour
{
    BigInteger money = 1;
    [SerializeField]
    GameObject TextMoney, TextMoneyE; 
    [SerializeField]
    int Multiplier; 
    public void ButtonClick()
    {
        money *= Multiplier;
        TextMoney.GetComponent<Text>().text = money.ToString( );
        TextMoneyE.GetComponent<Text>().text = money.ToString("E");
    } 
}

Таким образом выводимое число будет выглядеть так:

В целом количество знаков уменьшилось, но рассмотрим другой метод вывода информации.

Будем выводить числа в виде 1а,1b,1c,…,100xx. Для примера если наше число = 1000 то будем заменять назначение «1А», если число =10000 то заменяем его на «10А», если число 1000000 то заменяем на 1B и так далее.

Область вывода информации теперь имеет 3 поля вывода числа выглядит это так:

Итоговый код выглядит у нас таким вот образом:

using System;
using System.Numerics;
using UnityEngine;
using UnityEngine.UI; 
public class MLMon : MonoBehaviour
{
    BigInteger money = 1;
    [SerializeField]
    GameObject TextMoney, TextMoneyE, TextMoneyABC; 
    [SerializeField]
    int Multiplier; 
    public void ButtonClick()
    {
        money *= Multiplier;
        TextMoney.GetComponent<Text>().text = money.ToString( );
        TextMoneyE.GetComponent<Text>().text = money.ToString("E");
        TextMoneyABC.GetComponent<Text>().text = TO_abc(money.ToString());
    }
     
    int[] arrayCountDigit = new int[6] { 26, 702, 18278, 475254, 12356630, 321272406 };
    char[] lit = new char[26] { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z' }; 
    public string TO_abc(string biStr)
    { 
        int lengthNumb = biStr.ToString().Length;
        if (lengthNumb > 3)
        {
            int countLiderNumber = lengthNumb % 3;
            if (countLiderNumber == 0) countLiderNumber = 3;
            int CountNumberForTransformation = (lengthNumb - countLiderNumber) / 3;
            string NameLiterIsNmber = "";
            byte level = 1;
            for (int i = 0; i < arrayCountDigit.Length; i++)
            {
                if (CountNumberForTransformation > arrayCountDigit[i]) { level++; } else { break; }
            }
            for (int i = level; i > 0; i--)
            {
                int del = i > 1 ? arrayCountDigit[i - 2] : 0;
                int currentindex = (int)(Math.Ceiling((CountNumberForTransformation - del) / Math.Pow(26, i - 1)));
                if (currentindex > 26) currentindex = currentindex % 26;
                currentindex--;
                if (currentindex < 0) { currentindex = 25; }
                NameLiterIsNmber += lit[currentindex];
            }
            string first3number = biStr.ToString().Substring(0, countLiderNumber + 1).Insert(countLiderNumber, ",");
            return first3number + NameLiterIsNmber;
        }
        return biStr.ToString();
    }

Поясню по коду:

int[] arrayCountDigit = new int[6] { 26, 702, 18278, 475254, 12356630, 321272406 };

Массив который сообщает сколько существует значений в определенном диапазоне чисел.

В первом у нас 26 значений a,b,c,…,z

Во втором 702 = a,b,c,…,z + комбинации значений aa,ab,ac,…,az,ba,bb,…,zz

В третьем 18278 = предыдущий диапазон + комбинации значений aaa,aab,aac,…,zzz

char[] lit = new char[26] { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z' }; 

Буквы которыми будем заменять цифры храним в виде массива

  int lengthNumb = biStr.ToString().Length;
        if (lengthNumb > 3)
        {код} 
      return biStr.ToString();

считаем длину числа и если оно меньше 4 то нет смысла с ним что либо делать и просто вернем само число. Если же число в длину 4 и более то заменяем цифры буквами:

  int countLiderNumber = lengthNumb % 3;
  if (countLiderNumber == 0) countLiderNumber = 3;

Считаем количество цифр которые мы будем выводить перед буквами, это может быть от 1 до 3 знаков так как высшие разряды заменяются буквами (999 - это ещё число 999, а вот при 1000 значение становится 1А) .Соответственно если число знаков делится на 3 то считаем что выводится 3 цифры.

1 = 1

1000 = 1А (000=А)

10000 = 10А (000=А)

1000000 =1B (000000=B)

 int CountNumberForTransformation = (lengthNumb - countLiderNumber) / 3;

Считаем количество знаков которые нам нужно преобразовать в буквы, это общее количество знаков за исключением лидирующих цифр деленное на 3 ( так как преобразовываем тыщи).


 byte level = 1;
for (int i = 0; i < arrayCountDigit.Length; i++)
{
      if (CountNumberForTransformation > arrayCountDigit[i]) 
      {
        level++;
      } 
        else 
      {
         break; 
      }
}

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

string NameLiterIsNmber = "";

создаем переменную в которую и будем записывать преобразованные числа, и уровень числа для подсчета группы разрядов. А вот дальше начинаем преобразовывать.

for (int i = level; i > 0; i--)
            {
                int del = i > 1 ? arrayCountDigit[i - 2] : 0;
                int currentindex = (int)(Math.Ceiling((CountNumberForTransformation - del) / Math.Pow(26, i - 1)));
                if (currentindex > 26) currentindex = currentindex % 26;
                currentindex--;
                if (currentindex < 0) { currentindex = 25; }
                NameLiterIsNmber += lit[currentindex];
            }

Проходим по каждому уровню преобразовываемых данных и рассчитываем букву которую будем заменять в данном разряде(делим число знаков за исключением предыдущего уровня на 26 в степени текущего уровня). Получив индекс текущей буквы дописываем эту букву в строковое представление числа.

string first3number = biStr.ToString().Substring(0, countLiderNumber + 1).Insert(countLiderNumber, ",");
  return first3number + NameLiterIsNmber;

Для большей детальности выводим десятую долю нашего числа

1100 = 1,1А

25500=25,5А

И возвращаем полученное число.

Ну и собственно итоговый вариант:

Доклад закончил!

Комментарии (17)


  1. Myclass
    07.04.2023 21:57
    +9

    Не уверен, что это кому-нибудь подойдёт, потому что уже есть такие стандартные значения как кило-, мега, гига итд. Не совсем понял, почему вам не подходит цифра с фактором в виде степени? Вроде бщепринятая вещь. И сама по себе задача не совсем понятна, где такое будет надо. Но сама идея неплохая.


  1. sdy
    07.04.2023 21:57
    +9

    100000 => 1e5

    • на мой взгляд общепринятый метод намного проще и даже более краток


    1. s207883
      07.04.2023 21:57
      +3

      Есть еще один принятый (во всяком случае встречал его в ММО): 100 000 = 100к, 1 000 000 = 1кк и т.д. И это намного более понятно, чем 1.23aBeT


      1. acsent1
        07.04.2023 21:57
        +2

        есть офф сокращения: k, M, G(B), T
        А вот дальше уже кто во что горазд


        1. Arhammon
          07.04.2023 21:57

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


  1. lorc
    07.04.2023 21:57
    +11

    Ничего ж себе вы кода написали...

    Вообще задача сводится к тому, чтобы взять экспоненту из экспоненциальной записи, поделить на три, потом записать полученное число в 26-ричной системе счисления, где в качестве цифр используются буквы. Таким образом вы получите свои буквы в суффиксе.

    Собственно, вы это и сделали, но в процессе изобрели свою собственную терминологию, написали кучу кода и ограничились всего тремя цифрами.

    На пайтоне это может выглядеть приблизительно вот так: https://ideone.com/WLhvxq


  1. ReadOnlySadUser
    07.04.2023 21:57
    +1

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

    Используя вашу буквенную схему запишите мне число, ну скажем 838295772233444310003.


    1. StjarnornasFred
      07.04.2023 21:57

      В играх-кликерах его бы записали как 838F. Или, маловероятно, как 0,8G.


      1. ReadOnlySadUser
        07.04.2023 21:57
        +2

        Ну, записать-то записали, а как эти числа между собой сравнивать? Вот когда у меня два числа 838e+18 и 838е+16, мне легко быстро понять, которое их них больше и насколько.

        А вот в записи 838F и 838H как-то с ходу не разберемся. Можно предположить, что я хорошо помню порядок букв в алфавите, и могу с ходу распознать кто больше/меньше, но как понять насколько порядков? Это ж надо вспомнить на каком месте стоят буквы F и H, а потом вычесть номера этих позиций. Очень-очень неудобно) А когда начнутся двухбуквенные обозначеничя, то всё - приехали.


  1. biff_33
    07.04.2023 21:57
    +1

    А может, не надо изобретать велосипед?
    image


  1. randomsimplenumber
    07.04.2023 21:57

    Что то у вас там совсем не сбалансировано, если для количества монет необходима форма записи с экспонентой.


  1. staticmain
    07.04.2023 21:57

    В вашей финальной анимации не отобразилась форма АА


  1. Evengard
    07.04.2023 21:57
    +1

    А ещё в игровом сообществе (особенно в среде ММОРПГ) есть запись, где суффикс "К" означает умножение на тысячу, и их можно добавлять неограниченое количество раз (например 1к = 10000, 1кк = 1 000 000, 1ккк = 1 000 000 000, ну и тд). Если уже принимать какую-то систему множителей денежных знаков в игре, то скорее такую, знакомую игрокам...


    1. DistortNeo
      07.04.2023 21:57

      Можно — не значит нужно. Больше трёх "ккк" я ни разу не видел на практике, да и выглядит монструозно.


  1. unibasil
    07.04.2023 21:57
    +1

    Вы явно не играли ни в одну игру из жанра кликеров. Там это всё давным давно решено наиболее понятным и интуитивным способом (см. Приставки СИ), с помощью всем известных букв K(ило), M(ега), G(ига), T(ера), P(ета) и так далее.


  1. AxelLx
    07.04.2023 21:57
    +2

    Для вопрошающих "Зачем такое нужно?" — ответ дан в самом начале статьи. Речь идёт об игре жанра Idle/Incremental. Для таких игр экспоненциальный/сверхэкспоненциальный/гиперэкспоненциальный рост чисел — обычное дело.


    Согласен, что изобретать велосипед в данном случае не стоит. Стандартно, для записи чисел в таких случаях применяется научная E-нотация.


    Основная сложность при работе с огромными числами — это операции с ними. В инкрементальных играх скорость обработки намного важнее итогового результата. Опять-таки, не стоит изобретать велосипед, лучше посмотреть в сторону готовых библиотек.


  1. xtotdam
    07.04.2023 21:57

    Ну можно ж было погуглить сначала, нашли бы страничку с примерами нотаций из Antimatter Dimensions, очень популярной среди idle-игр. Конечно, большинство из них шуточные, но ваша задача там решена.