20_20 — год, в котором подчеркивание в числовых литералах победило

    Вдруг вы не знали, но в языке, на котором вы пишите, вы можете использовать _ в числах. Например, следующий код на PHP:


    <?php
    
    print(1_00);
    print(100);

    Выведет 100100 (проверить онлайн). Этот синтаксический сахар появился в Ada в 1980 году, и он имел переменный успех последние 40 лет. Но за последний год его добавили в javascript, PHP, Go, Scala и даже консервативный Erlang. Я не могу объяснить, что послужило всплеском популярности, поэтому в статье просто опишу историю разделителей в цифрах.


    19 68 Algol


    На заре программирования не особенно заморачивались с пробелами, а поэтому в Algol в именах переменных и цифрах можно было вставлять пробел. Например, a 1 st var — корректное название переменной, и синоним a1stvar. Код:


    BEGIN
        INT  a 1 st var = 1 234 567;
        REAL a 2 nd var  = 3      .    1 4159 26 5 359;
        print((a1stvar, newline, a2ndvar))
    END

    Выведет (проверить):


       +1234567
    +3.14159265359000e  +0

    Я не смог найти более ранние версии интерпретатора, но очевидно, так вели себя и предыдущие версии Algol. Также, пишут, что FORTRAN до версии 77 года вел себя аналогично.


    UPD: В фортране пробелы в числах и именах можно использовать до сих пор (в режиме fixed-form source files). Но fixed-form source files депрекейтед в стандарте 2018 года.


    19_80 Ada


    Похоже, что впервые использовать подчеркивание как разделитель в цифрах придумали в 1980 году в Ada. Кроме этого, в Ada можно было указывать числа в системах с любым основанием от 2 до 16. 11 в пятеричной системе (6 в десятеричной) можно было записать 5#11#. Что в свою очередь позволяло делать, такие неочевидные вещи, как I: Float := 3#0.1# — переменная со значением 1/3 (одна треть):


    with Ada.Float_Text_IO; use Ada.Float_Text_IO;
    procedure hello is
        I: Float := 3#0.1#;
        J: Float := 1_00.0;
    begin
        Put(I);
        Put(J);
    end hello;

    Выведет 3.33333E-01 1.00000E+02 (онлайн).


    19_85 Caml


    19_87 Perl


    Позже, похоже независимо друг от друга, подчеркивание появилось в Caml и Perl. Perl стал первым языком, где (по непонятой мне причине) можно использовать несколько подчеркиваний подряд:


    $x = 1_0_0;
    $y = 2__5________0;
    $z = $x+$y;
    
    print "Sum of $x + $y = $z";

    Выведет Sum of 100 + 250 = 350 (проверить).


    19_93 Ruby


    19_96 OCaml


    Это первые два языка, где можно проследить, откуда пришло подчеркивание. OCaml (неожиданно) перенял подчеркивание у Caml, а Ruby создавался под влиянием Perl, откуда и перенял подчеркивание (правда, без возможности ставить несколько подряд).


    20_03 D (v0.69)


    D стал первым языком, в котором изначально в спецификации подчеркиваний не было, а их добавили позже (через два года после создания).


    20_10 Rust


    20_11 Java (SE 7)


    20_12 Julia


    Rust создавался под влиянием OCaml, а Julia под влиянием Ruby. В Java как и в Perl можно ставить несколько подчеркиваний подряд.


    20_14 Swift


    Под влиянием Rust.


    20'14 C++ (v14)


    У плюсовиков проблемы всегда отличаются от других. Дело в том, что идентификатор в C++ может состоять из подчеркивания и цифры. То есть, _1 правильное название переменной. И код:


    #include <iostream>
    
    int main()
    {
      for (int _1 = 0; _1 < 5; ++_1) {
        std::cout << _1 << " ";
      }
    }

    Выведет 0 1 2 3 4 (проверить). И чтобы не путать название переменных и цифры в C++ разделителем сделали одинарную кавычку 1'00.


    UPD: Более опытные объяснили, что подчеркивание конфликтовало с пользовательскими литералами (User-defined literals). Следующий код корректный и выведет 1:


    #include <iostream>
    
    constexpr unsigned long long operator"" _0(unsigned long long i)
    {
        return 1;
    }
    
    int main()
    {
        int x = 0_0;
        std::cout << x;
    
        return 0;
    }

    20_16 Python (3.6)


    20_17 C# (7.0), Kotlin (v1.1)


    20_18 Haskell (GHC 8.6.1)


    20_19 JS (V8 v7.5, Chrome 75), PHP (v7.4), Go (v1.13), Scala (v2.13)


    Тут что-то произошло, и подчеркивание добавили во все основные языки. Здесь бы и хотел сделать замечание, что подчеркивание хоть и не сильно, но усложняет интерпретаторы или компиляторы. Например, из мануала в PHP регулярные выражения для цифровых литералов:


    // PHP 7.3
    decimal : [1–9][0–9]* | 0
    hexadecimal : 0[xX][0–9a–fA–F]+
    octal : 0[0–7]+
    binary : 0[bB][01]+
    
    // PHP 7.4
    decimal : [1–9][0–9]*(_[0–9]+)* | 0
    hexadecimal : 0[xX][0–9a–fA–F]+(_[0–9a–fA–F]+)*
    octal : 0[0–7]+(_[0–7]+)*
    binary : 0[bB][01]+(_[01]+)*

    В добавок, теперь в PHP появилось еще больше способов записать одно и тоже число:



    20_20 Erlang (v23)


    Дольше всех держался Erlang. Но, что удивительно, с самого начала в Erlang можно было, как и в Ada (даже с похожим синтаксисом), записывать числа в произвольной системе исчисления (до 36). Например, следующий код печатает (онлайн) 3z в системе с основанием 36 (=143).


    -module(main).
    -export([start/0]).
    
    start() ->
      io:fwrite("~w", [36#3z]).

    Но подчеркивание в цифрах добавили только спустя 33 года после создания языка.

    Похожие публикации

    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 36

      0
      Вдруг вы не знали, но в языке, на котором вы пишите, вы можете использовать _ в цифрах. Например, следующий код на PHP

      Выведет ошибку, потому что не все (увы) перешли на новые версии.
      И да, до сих пор не понимаю, зачем это. Для большинства приложений достаточно создать нужные константы, что будет всяко понятнее записи 3_153_600, если вдруг понадобилось число секунд в году. Кстати, в прошлой записи ошибка, число должно быть на порядок больше. Поэтому

      protected const YEAR_SECONDS = 60 * 60 * 24 * 365;
      


      Что впрочем можно улучшить, используя другие константы. И с совместимостью чуть получше.
        +7
        И да, до сих пор не понимаю, зачем это.
        Разделитель тысяч в больших числах.
        10000000 => 10_000_000
          –3

          10 * 1000 * 1000 ?

            +7
            Это подходит только для круглых чисел.
          +1

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

            +1
            а у кого-то виртуальные машины и такие константы придется на ходу вычислять.

            Например? До версии 7.0 PHP оптимизировал это на стадии OpCache, сейчас же оно встроено в сам «компилятор». Нулевые накладные расходы. Думаю и в остальных мейнстримных языках должна быть подобная оптимизация.
              +1

              JS в зависимости от скоупа константы может сделать оптимизацию и вычислить единожды, но скажем, перезагрузка страницы заставит его выполнить вычисление заново ибо контекст снова будет пересоздан. Есть нюансы, но идея я думаю понятна. Его сосед WASM же вычислит число еще на этапе компиляции и ее можно сразу читать.
              Питон вычислит единожды при старте контекста и закэширует в .pyc т.е. и в отличие от JS перезапуск контекста не должен приводить к пересчету. PHP, Ruby та же история.
              Ну, а в целом да, вычисления констант обычно супер дешевые и это скорее перфекционизм.

            +5
            главное чтобы был принят единый стандарт в проекте для форматирования чисел. Например индус запишет данное число как 31_53_600 и ему будет норм :)
              +1

              Даже в голову не пришло, что кто-то может так извратиться

                +6
                Это в греко-римской культуре мы делим на тысячи: кило, мега, гига, а например японцы делят манами (10000).
              0

              Про вычисляемые константы типа числа секунд в году хорошая идея, но вот, например записывать пределы вида миллиард = 100010001000 не очень, имхо. 1_000_000_000 куда читаемей, особенно если это не функция приобразования гигабайт в байты и обратно, а, например, лимиты операций по счету

                +2
                protected const YEAR_SECONDS = 60 * 60 * 24 * 365;

                Все верно… ну а если год високосный? А астрономы и вовсе верят, что средний тропический год — это 31 556 925 с… Так что простое умножение не всегда спасает. Иногда приходится и константы писать…
                Впрочем, с астрономами вообще лучше не связываться, у них даже сутки не всегда равны 60*60*24=86 400 с, т.к. Земля не совсем равномерно вращается ;-)
                  +4

                  Ну так заведите константу с названием типа LEAP_DIFFERENCE, AVG_TROPICAL_DIFFERENCE и вычисляйте от базового значения 60 х 60 х 24…
                  Всяко лучше 31566925 как magic number.

                    0
                    Само собой, в коде надо писать мнемонику, а не цифру. Но при инициализации этой константы все равно приходится цифры расписывать. То есть, вопрос о разделителях внутри числа не снимается, а только отодвигается в специальное место…
                      0
                      А это разве проблема? Иногда мне кажется, что человечество в конец деградировало, раз не может воспринять пусть даже 10 цифр подряд без разделителей. И да, в одном месте однократно можно и потерпеть, набрать и перепроверить. В общем всё ещё не понимаю, какую проблему решают эти разделители.
                        +1
                        Ну во-первых, человечество действительно деградирует ;-)
                        Например, в моей молодости фортран очень медленно работал со строками или с посимвольным выводом на экран (а нам хотелось интерфейс типа Norton Commander-а). Так у нас тогда было нормой некоторые функции на ассемблере переписать. И это практически каждый фортранист в какой-то степени мог. А сейчас вставки на ассемблере хорошо, если каждый сотый напишет. Вот и я уже давно не пишу.
                        Впрочем, возможно я зря обобщаю на все человечество, и деградация только лично у меня происходит ;-))

                        Но вообще, когда констант много, то просто для удобства чтения кода гораздо лучше числа писать с разделителями. Чтобы хотя бы в этом месте не терпеть и не напрягаться. В фортране это всегда было доступно (а для кого-то и нормой). Сейчас общая тенденция — чтобы всякие подобные мелкие удобства были доступны в любом языке. Чего в этом плохого? Тем более, что синтаксис в разных языках почти единообразный и даже новичку в языке такая запись понятна?
                        Ну а не если хочется — так никто не заставляет, можно и не использовать.
                          +4
                          Впрочем, возможно я зря обобщаю на все человечество, и деградация только лично у меня происходит ;-))

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


                          Цепляться за подобные навыки — всё равно, что расписаться в том, что способность адаптироваться под ситуацию сильно ослабла.


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

                            0
                            Но если ты не сможешь передать его для поддержки и развития следующим поколениям — он умрет.

                            Но ведь раньше как-то справлялись. Не это ли признак деградации?
                              0
                              И сейчас справляются. Просто на это нужно время. Много времени. Особенно — когда предыдущего сопровождающего уже нет. Ушёл уже куда-то.

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

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

                                Опять таки, раньше успевали? Можно попытаться сказать, что мол раньше софт выпускали реже, но блин, почему-то мне кажется, что сроки поджимали всегда и везде, уж так устроен бизнес.
                                  0

                                  Проще: и сейчас успевают. Просто вы говорите об ошибке выжившего, а не о деградации.

                            +2

                            Во времена вашей молодости и ассемблер наверняка попроще был, не то что современный x86-64 да с расширениями всякими.

                  +1

                  Жаль в C такого нет.

                    +11
                    Ну C уже довольно взрослый ребенок, не тащит в рот любую бяку которую вокруг себя находит :)
                      +1

                      Почему бяку? При написании каких-то длинных констант — вполне себе удобно, добавляет читабельности, особенно когда где-то константа с кучей нулей подряд.

                        +1
                        Вы цепляетесь. Я минуты две придумывал слово которое передает иронию но при этом никого сильно не оскорбляет. Я не считаю что разделители бесполезны. Просто стандарт С КРАЙНЕ консервативен. Я не думаю есть ли более консервативный развиваемый сейчас язык. Поэтому туда не добавляют все что «было бы полезно».
                      0
                      #define _

                      Спойлер

                      Шутка

                        0

                        А вообще можно малюсенький препроцессор написать для внедрения такой возможности.

                      0
                      Также, пишут, что FORTRAN до версии 77 года вел себя аналогично.

                      Возможно, я не совсем понял текст по ссылке, но мне непонятно, откуда там взялась цифра Ф-77? Насколько я знаю, по умолчанию фортран до сих пор игнорирует пробелы. Проверил сейчас в компиляторе 2013г (стандарт языка Fortran 2008). И в нем две эти формы записи эквивалентны:
                      integer :: int_var = 1000000
                      и
                      integer :: i nt_v ar = 1 000 000

                      Возможно, в новых компиляторах есть ключи, позволяющие ограничить такие вольности, но с ходу я что-то ничего не нашел…

                      А символ подчеркивания в фортране используется, чтобы указать разрядность константы в байтах, например:
                      13_2! 16-битное целое
                      -1_8! 64-битное целое

                      Ну и с системами исчисления все стандартно и лаконично:

                      int_var=36#3z! присваивает int_var значение 143

                      Буквы A..Z обозначают цифры от 10 до 35 (соответственно основание может быть не более 36). Ну и упрощенная запись для наиболее частых случаев:

                      i=B'011'; j='010'B! присваивает i=3, j=2
                      i=O'011'; j='010'O! присваивает i=9, j=8
                      i=X'011'; j='010'X; k=Z'0A'! присваивает i=17, j=16, k=15

                      Даже не знаю, считать ли такую вариативность записи достоинством языка или его недостатком… Если речь идет о клавиатурных комбинациях-синонимах, то вроде бы мы их обычно приветствуем: хочешь, жми Ctrl+C, а хочешь — Ctrl+Ins. А в коде?
                        +1
                        Я проверял на FORTRAN-95 Online (GNU Fortran, GCC v7.1.1) — и там нельзя пробелы ни в названиях переменных ни в цифрах.
                          0
                          В свободной форме нельзя, а в фиксированной можно. И Intel Fortran, и GNU Fortran себя именно так ведут.
                            0
                            Исправил в посте на fixed-form source files, фортран не родной язык для меня.
                        +2
                        Да. И это полностью уберет обратную совместимость с предыдущими версиями.
                          +1
                          Еще в котлине есть.
                            +1
                            Добавил в пост!
                            0
                            В Python строки вида /_[0-9]+/ тоже имеют свой смысл, что иногда дает трудно обнаруживаемые ошибки в больших массивах использующих подчерк как разделитель.

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

                            Самое читаемое