Система СИ и взаимосвязи между единицами физических величин.
Система СИ и взаимосвязи между единицами физических величин.

Библиотека по работе с единицами системы СИ KotUniL, разработанная изначально на Kotlin, недавно сделана мультиплатформенной. В частности, она доступна теперь и на JavaScript, о чём написано здесь.

А зачем нужна эта библиотека?

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

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

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

Одна из таких библиотек - KotUniL (si-units).

Использование технологии Kotlin Multiplatform позволяет из кода на Kotlin получить код для целого ряда платформ, в том числе для JVM.
А это означает, что тем самым реализованную в библиотеке функциональность можно использовать из Java-программ. (Строго говоря, этого можно было достичь и не используя Kotlin Multiplatform, но в данном случае мы получаем JVM-библиотеки автоматически вместе с другими вариантами, что экономит наши усилия).

Как её использовать?

А как использовать библиотеку из Java?
Подключить библиотеку в ваш проект можно как dependency:

repositories {
    mavenCentral()
}

dependencies {
    implementation("eu.sirotin.kotunil:kotunil-jvm:<version>")
}

На момент написания статьи последняя версия была 4.1.1

Какие различия между вариантами на Kotlin и Java существуют?

А дальше всё тоже самое, как в Kotlin? К сожалению, есть два важных отличия.

Во первых, Java не поддерживает operator overloading и поэтому код смотрится менее элегантно, чем на Котлин.

Рассмотри, например, такую задачку: Маша протирала снаружи стекло аквариума, задела стоявшую рядом вазу, в результате чего стекло аквариума разбилось и вода вытекла на пол. В аквариуме до этой неприятности было 32 литра воды. Комната Маши имеет длину 4 метра и ширину 4,3 метра. На какой высоте в мм. находится сейчас вода в комнате, при условии, что она осталась там и не вытекла?

На Котлине решение выглядит таким образом:

val s = 4.m * 4.3.m
val h = 32.l/s 

А на Java нам приходится вместо знаков арифметических операций над размерными единицами использовать функции:

Expression s = m.times(4).times(m.times(4.3));
Expression v = L.times(32);
Expression h = v.div(s)

Однако все остальные прелести KotUniL сохранены. Амперы с секундами складывать нельзя, а перемножать и делить можно.

Во-вторых, трансформируя код Kotlin в код на Java, транспилер создаёт для классов библиотеки из одного Kotlin-класса два Java-класса. Поэтому, например, работая с метрами мы должны в Java-импорты добавлять строку:

import static eu.sirotin.kotunil.base.MetreKt.*;

Дальнейшую информацию о KotUniL вы надаете в репозитории на GitHub ( см. подробный пример работы с единицами системы СИ в Java в модуле app/jvm/java-console).

Про теоретические основы и особенности использования KotUniL вы можете прочитать и в этой серии стаей: 

  1. Магия размерностей и магия Котлина. Часть первая: Введение в KotUniL  

  2. Магия размерностей и магия Котлина. Часть вторая: Продвинутые возможности  KotUniL

  3. Магия размерностей и магия Котлина. Часть третья: Смешение магий

А есть ли альтернативы?

Я разработал KotUniL, поскольку ни одна из существовавших на тот момент библиотек меня не удовлетворяла. В Java сообществе существует JSR 385. Очевидным образом у разработчиков этой спецификации были схожие с моими мотивации. Но там разработчики пошли по пути выделения размерностей. У них получились Quantity<Volume>Unit<Length> и так далее. Разумеется, всех возможных комбинаций таким образом не покрыть, но множеству приложений этого делать и не придётся.

Вот статья на Baeldung про общий вид работы с библиотекой.

Дизайн библиотеки мне не понравился. Чего только стоит подобный пример конвертирования метров в километры, взятый мной из рекомендованной выше статьи!:

double distanceInMeters = 50.0;
UnitConverter metreToKilometre = METRE.getConverterTo(MetricPrefix.KILO(METRE));
double distanceInKilometers = metreToKilometre.convert(distanceInMeters );

Используя KotUniL вы это запишите так:

val d = 50.m
val x = d.km

Я понимаю, преимущество JSR 385 состоит в обнаружении ошибок типизации на этапе компиляции, а не в наглядности краткости кода. В этой статье я показываю, что любые формулы KotUniL проверяются на ошибки типизации ровно одним юнит-тестом при наглядности кода, схожей с наглядностью технических и научных статей.

Почему стандарт СИ не реализован в Kotlin?

Итак, в мире Java существует по крайней мере спецификация JSR 385, касающаяся реализации обработки единиц системы СИ, а как обстоят с эти дела в Kotlin?

К сожалению, в числе существующих и разрабатываемых стандартных библиотек Kotlin такой библиотеки пока нет.

Я предложил разработчикам языка включить эту функциональность (не обязательно KotUniL) в состав библиотек, поставляемых вместе с языком: (KT-55556). Но пока не убедил их. Но возможно, если вы, уважаемые читатели, активно проплюсуете это предложение ( а заодно и KotUniL на GitHub :-), то ситуация исправится.

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


  1. anonymous
    15.07.2023 19:20

    НЛО прилетело и опубликовало эту надпись здесь


  1. Nareloote
    15.07.2023 19:20

    Подскажите, а как обстоит дело с единицами измерения, которые имеют дополнительные аттрибуты:

    • давление. Бывает абсолютным и избыточным и 100 кПа изб. != 100 кПа абс. Более того, из избыточного невозможно однозначно перевести в абсолютное без знания параметров окружающей среды. Соответственно UnitConverter должен как-то это учитывать.

    • сила тока и напряжение. Бывает переменным и постоянным. Соответственно возникают сложности с переводом.

    • расход. Бывают м3/ч, а бывает нм3/ч (л/мин и нл/мин). Последние являются приведенным значением к нормальным условиям. Если в двух одинаковых трубах с газом расход 1 л/мин, но при этом первая труба находится под давлением в 0.1 МПа, а вторая труба находится под давлением в 1 МПа, то очевидно, что через вторую протечет почти в 10 раз больше объема газа.

    Также еще есть проблемы с концентрациями, процентами (это больше касается метрологии).

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


    1. visirok Автор
      15.07.2023 19:20

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

      Присмотритесь к KotUniL, может это решение Вам поможет. Если будут вопросы или проблемы, свяжитесь со мной.


  1. Andy_U
    15.07.2023 19:20
    -7

    Не должно быть "внутри компьютера" никаких килограммов и метров. Уравнения - обезразмерить, входные данные - туда же. Лишь при выводе - обратно.


    1. dopusteam
      15.07.2023 19:20
      +3

      Скажите, пожалуйста, сколько килограммов будет, если сложить 5 и 7?


      1. visirok Автор
        15.07.2023 19:20
        +2

        Особенно, если это 5 вольт и 7 ватт.


      1. Andy_U
        15.07.2023 19:20
        -1

        5 и 7 чего? И мы еще обсуждаем облегчение решение физических задач (см. тег публикации) численными методами? Ну и как физик-теоретик по образованию, таки предпочитаю CGS, где, например, вязкость имет размерность в пуазах/poise. Да даже в СИ - в паскалях x секунды. И таких даже не внесистемных единиц в физике - миллионы. Они реализованы в данной библиотеке? Всякие парсеки, meV, keV, MeV, GeV, TeV, PeV and EeV и пр.? Ну и как припрет решать большую систему линейных уравнений с помощью GMRES, так пока от размерностей не избавитесь, фиг какой прекондициондер вам поможет.


        1. visirok Автор
          15.07.2023 19:20

          В библиотеке имплементированы все базовые и производные единицы системы СИ и префиксы к ним степеней от -30 до +30. Кроме того, из имеющихся единиц можно строить новые или создавать свои, совсем новые.


        1. dopusteam
          15.07.2023 19:20

          5 и 7 чего?

          Не должно быть "внутри компьютера" никаких килограммов и метров. Уравнения - обезразмерить, входные данные - туда же. Лишь при выводе - обратно.

          Вы ж сами сказали сказали обезразмерить.

          И мы еще обсуждаем облегчение решение физических задач (см. тег публикации) численными методами?

          Нет, публикация вообще про другое


          1. Andy_U
            15.07.2023 19:20

            Вы ж сами сказали сказали обезразмерить.

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

            Вот, кстати пример, что если воспользоваться той системой единиц, где скорость света равна единице, то интервал R^2-c^2*t^2 превратится в r^2-t^2. Т.е. вполне себе можно складывать времена и размеры/расстояния. Вполне законно.

            Другая задача: есть трехфазная электрическая сеть с резистивной нагрузкой, ток в каждой фазе (от фазы до нуля) 5 ампер. Какой ток течет по нулевому проводу?

            Нет, публикация вообще про другое

            Отлично! Тег/хаб "физика" у публикации все еще валиден?


            1. dopusteam
              15.07.2023 19:20
              +1

              Тег/хаб "физика" у публикации все еще валиден?

              Да, но если обратите внимание не только на один тег, но и на другие, а ещё на текст статьи, то обнаружите, что текст про разработку, а не про физику.

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

              Я так не считаю. Это вы сказали, цитирую "Не должно быть "внутри компьютера" никаких килограммов и метров. Уравнения - обезразмерить, входные данные - туда же".

              Вот, кстати пример, что если воспользоваться той системой единиц, где скорость света равна единице, то интервал R^2-c^2*t^2 превратится в r^2-t^2. Т.е. вполне себе можно складывать времена и размеры/расстояния. Вполне законно.

              Это слабо относится к тексту статьи.

              Другая задача: есть трехфазная электрическая сеть с резистивной нагрузкой, ток в каждой фазе (от фазы до нуля) 5 ампер. Какой ток течет по нулевому проводу?

              Это вообще куда то мимо. Я не сомневаюсь, что вы обладаете хорошими знаниями физики, но при чём тут это вообще?


              1. Andy_U
                15.07.2023 19:20
                -1

                Я:

                Тег/хаб "физика" у публикации все еще валиден?

                Вы:

                Да, но если обратите внимание не только на один тег, но и на другие, а ещё на текст статьи, то обнаружите, что текст про разработку, а не про физику.

                Bold мой, "логика" ваша.

                при чём тут это вообще?

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


                1. dopusteam
                  15.07.2023 19:20
                  +1

                  Bold мой, "логика" ваша.

                  Вы смотрели какие там теги ещё? Программирование. Давайте выделю, чтоб понятнее было, Программирование.

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


    1. BugM
      15.07.2023 19:20
      +1

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


      1. Andy_U
        15.07.2023 19:20
        -2

        Да не безразмерные метры или футы, а длины/расстояния в единицах, характерных для конкретной задачи. Если, например, в качестве такой характерной длины был выбран, например, диаметр планеты, то неважно, там метры или футы. Главное, чтобы и там и там были метры, сантиметры, футы и пр. Одно делим на другое, и получаем одну и ту же величину, независимо от выбора исходных единиц.


        1. BugM
          15.07.2023 19:20
          +1

          Хорошо жить в простом мире. Где можно о таких штуках договориться и всё будут следовать договоренности и никогда не ошибаться. В моей практике оно как-то плохо соблюдается. Всегда есть внешняя система или интеграция какая где есть свои договоренности.


          1. Andy_U
            15.07.2023 19:20
            -1

            А что помешает ввести в поле величину в футах, не обратив внимания, что там нужны были метры? И прибор вам размерность не отдаст. Или неправильную отдаст.


            1. BugM
              15.07.2023 19:20
              +2

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

              А пользователю может помочь хороший интерфейс с проверкой на адекватность того что хочет пользователь. Например, самолет не может встать на эшелон 30 тысяч метров с другой стороны и эшелон 10 тысяч футов вызывает вопросы и лучше переспросить подтверждения.


              1. Andy_U
                15.07.2023 19:20
                -3

                Я хочу еще раз обратить внимание на тег публикации "физика". То, о чем вы пишите - это техника.

                Например, самолет не может встать на эшелон 30 тысяч метров с другой стороны

                Какой там рекорд высоты (динамической)? https://ru.wikipedia.org/wiki/Список_рекордов_высоты_полёта


  1. quaer
    15.07.2023 19:20

    Результат от внедрения: программы с математикой станут больше, а работать медленнее :(

    Самый простой способ избежать проблем с разными единицами - это дать им суффикс:

    double car_speed_km_h = 100.;
    double input_current_noise_density_uA_sqHz = 1.3;

    Используя KotUniL вы это запишите так:

    val d = 50.m
    val x = d.km

    Как в вашем варианте читая код разобаться что такое d и х и как дальше их обрабатывать?


    1. dopusteam
      15.07.2023 19:20
      +1

      Самый простой способ избежать проблем с разными единицами - это дать им суффикс:

      Как это поможет не сложить часы и километры?

      val d = 50.m

      val x = d.km

      В этом варианте код не скомпилится, если попытаетесь сложить часы и километры.

      Как в вашем варианте читая код разобаться что такое d и х и как дальше их обрабатывать?

      Именовать переменные нормально, очевидно

      Результат от внедрения: программы с математикой станут больше, а работать медленнее :(

      Насколько больше и насколько медленнее? Вот прям ощутимо медленнее?


      1. quaer
        15.07.2023 19:20

        Как это поможет не сложить часы и километры?

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

        Именовать переменные нормально, очевидно

        Нормальное именование в большой степени и снимает саму проблему.

        Насколько больше и насколько медленнее? Вот прям ощутимо медленнее?

        Сколько в памяти занимает double и сколько Double ? А по скорости: тут чуть медленнее, там чуть медленнее, в результате имеем прожороливое и медленное приложение. Вы сами тест на скорость делали?


        1. BugM
          15.07.2023 19:20
          +4

          Правильно работающие программы полезнее быстрых программ.


          1. quaer
            15.07.2023 19:20

            Если время жизни пользователя превышает время, требующееся программе.

            Да и само понятие правильности в применении к решению задач физического мира относительно.

            Считаете вы прогноз погоды. Но считаете неделю на следующий день. И вот вы посчитали правильно, но уже не надо.


            1. BugM
              15.07.2023 19:20

              Конечно. В разумных пределах. Оверхед который дают типы даже в самом худшем случае точно будет разумным.

              А при чем тут мир? Мы про конкретное представление мира в виде чиселок и формул. Вычисления по ним могут быть произведены верно, а могут неверно. Если проблема именно с представлением мира, так это к ученым. Пусть делают новое, мы его запрограммируем.


              1. quaer
                15.07.2023 19:20

                double требует 8 байт. Double - 24 байта. int - 4 байта, Integer - 16 байт.

                3-4 раза не всегда разумно, правда?


                1. BugM
                  15.07.2023 19:20
                  +2

                  На самом деле меньше оно требует. Но не важно.

                  Скажите как часто у вас встречаются массивы из сотен миллионов объектов? На меньших масштабах оно совсем незаметно будет.

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


                  1. quaer
                    15.07.2023 19:20
                    -3

                    Скажите как часто у вас встречаются массивы из сотен миллионов объектов?

                    Если о приложениях для ПК - пару раз встретилось. Если для мобильника - там тоже приходится на доступную память внимание обращать, плюс скорость вычислений ниже.

                    Остальное писать как правильнее, там этот оверхед ни на одном приборе никто не заметит.

                    Правильно написанной IntelliJ пользоваться невозможно уже, все ресурсы умудряется сожрать и по памяти и по процессору.


        1. dopusteam
          15.07.2023 19:20
          +1

          Как у вас написан код не смотрел, но скорее всего занаследовано от какого-то одного родителя с плавающей точкой

          Нет, никакого наследования, достаточно сделать обертки, чтоб компилятор проверял всё.

          Сколько в памяти занимает double и сколько Double ? А по скорости: тут чуть медленнее, там чуть медленнее, в результате имеем прожороливое и медленное приложение. Вы сами тест на скорость делали?

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

          Нормальное именование в большой степени и снимает саму проблему

          Нет, не снимает. Глаз ещё больше замыливается только. Если мы про суффиксы вида _km, _cm, как вы предлагаете


      1. Andy_U
        15.07.2023 19:20
        -1

        Вот прям ощутимо медленнее?

        А вот тут бы должен бы быть тест от автора библиотеки. Со сравнением с известными пакетами.


  1. quaer
    15.07.2023 19:20
    -1

    достаточно сделать обертки, чтоб компилятор проверял всё.

    Что такое "обертки" и что именно проверяет компилятор?

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

    Можно узнать, что именно пишете?

    Глаз ещё больше замыливается только.

    Это его базовое свойство. Вам же надо как-то решать проблему умножения км/ч на часы или ватт на секунду.


    1. dopusteam
      15.07.2023 19:20
      +1

      Компилятор проверяет, что мы не складываем часы с метрами. Обёртка - обычный класс/структура, который имеет говорящее название (например, Metre) и хранит внутри себя значение.

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

      Ещё раз. Если я напишу var distance = speed * time, то у меня код не скомпилится, если у меня вдруг скорость в метрах в секунду, а время - в днях. Прошу не придираться к названиям только.


      1. quaer
        15.07.2023 19:20

        Заглянул в библиотеку. Везде наследование. Можно попробовать выполнить недопустимую операцию.


        1. dopusteam
          15.07.2023 19:20
          +1

          Заглянул в библиотеку. Везде наследование

          А поделитесь ссылкой, я что то не нашёл наследования, хотя может причина в том, что я с котлином не знаком

          upd.:
          Точнее насдедование есть, но не "от какого-то одного родителя с плавающей точкой"


          1. quaer
            15.07.2023 19:20
            -1

            https://github.com/vsirotin/si-units/blob/main/kotunil/src/commonMain/kotlin/eu/sirotin/kotunil/core/DerivedUnit.kt

            
            abstract class DerivedUnit(value: Number, formula: Expression)
                : Expression(value.toDouble()*formula.value, formula.dimensions)

            Это же оно?

            Upd:
            Не вникал в устройство библиотеки, но так как манипуляции с типами объектов возможны, то вероятно, можно и нарваться на неправильный результат.


      1. Andy_U
        15.07.2023 19:20

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

        Я бы предложил посоревноваться в умножении больших матриц (поищите в google по "optimized matrix multiplication"), только надо на одном компьютере сравнивать... Ну хотя бы matlab догоните с временем O(N^2.73).


        1. dopusteam
          15.07.2023 19:20
          +1

          Зачем? Как это связано с темой в статье и предметом обсуждения?


          1. Andy_U
            15.07.2023 19:20
            +1

            Затем, чтобы те, кто захочет применить предлагаемый/обсуждаемый подход, понимали цену - большую потерю скорости серьезных научных расчетов.


            1. dopusteam
              15.07.2023 19:20
              +1

              Кажется речи про "умножение больших матриц" изначально не было. Плюс, я б ещё не забывал про корректность расчётов, а не только про скорость.


              1. Andy_U
                15.07.2023 19:20
                -1

                Matlab - весьма известный в научных кругах программный пакет.


                1. dopusteam
                  15.07.2023 19:20
                  +1

                  А откуда Matlab вдруг взялся?


                  1. Andy_U
                    15.07.2023 19:20

                    Вы про корректность упомянули. А тут давно и широко используемая софтина...

                    Далее, я используемый тут язык не знаю, но думаю, что как и в других подобных языках при использовании классов возникает косвенная адресация, запросы памяти, сборка мусора, копирование при возврате и прочее и прочее. А серьезные численные алгоритмы какие-только хардверные трюки не используют. А тут при поиске увидел упоминание асимптотики времени умножения матриц O(N^2.73) и в очередной раз восхитился. Так то в лоб O(N^3) оценка.

                    Я к тому, что, может быть, если из котлина не вылезать, то и накладные расходы на проверку типов в runtime несущественны. Но вот если сравнивать с программами на фортране и C, то все может оказаться грустно.