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

Почему тонкостям вычислений в условиях такой арифметики разработчики прикладных программ не уделяют внимание, вопрос. Рискну только предположить, что, по всей вероятности, сказывается привычка производить вычисления на калькуляторе… Во всяком случае, с завидной регулярностью «имею счастье» лицезреть, как коллеги по цеху наступают на одни и те же грабли. Этот материал нацелен на то, чтобы те самые «грабли» нейтрализовать.

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

Реализуя вычисления с дробями, этот момент частенько упускают из вида, а, упустив, получают потери в точности вычислений. Причем точность вычислений падает с ростом величины делителя. К примеру, что 53/13, что 64/13 дадут в результате 4, хотя, по факту, частное от деления второй дроби существенно ближе к 5.
На самом деле, округление результата до ближайшего целого организовать элементарно. Для этого достаточно удвоить остаток деления, просуммировав его сам с собою, а затем вновь поделить его на то же число, на которое делили первоначально, и частное от этого деления сложить с частным, полученным от первоначальной операции деления.
В первом простеньком примере продемонстрирую, как такое округление реализуется программно на примере вычисления соотношения двух величин

$Y=k=A/B $


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

Для корректного выполнения необходимых для этого промежуточных вычислений понадобится массив из пяти регистров, обозначим его условно TEMP[0..4]. Почему пять и не меньше, поясню чуть ниже.

Алгоритм действий:

1.  TEMP[2]= A
2.  TEMP[3]= B
-----
3.  TEMP[0,1]= TEMP[2]/TEMP[3]
4.  TEMP[1,2]= TEMP[1]*2
5.  TEMP[4]= 0
6.  TEMP[1..4]= TEMP[1,2]/TEMP[3,4]
7.  TEMP[0]= TEMP[0]+TEMP[1]
-----
8.  Y= TEMP[0]

Шаги с 3-го по 7-й могут быть вынесены в подпрограмму.

При желании, запись результата может быть произведена непосредственно суммированием TEMP[0] c TEMP[1] за пределами подпрограммы расчета. Это непринципиально. Единственное, следует иметь в виду, что при множестве однотипных расчетов вынос операции сложения в основное тело программы способен привести к возрастанию задействованного ею объема программной памяти.

Так почему же для промежуточных вычислений потребовалось целых 5 регистров? А операция суммирования остатка деления самого с собой, о чем говорилось ранее, заменена умножением остатка на два? Очень просто — для того, чтобы оперировать с неограниченным набором целых чисел.

Поясню: если поделить, к примеру, число 32767 на -32768 в остатке получим 32767, и результат его сложения несомненно выйдет за пределы диапазона integer.
То бишь, удвоенный остаток от целочисленного деления дроби в интересах округления результата такого деления всегда должен быть представлен в формате double integer.
Продолжение следует...

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


  1. ultrinfaern
    29.05.2018 22:59
    +1

    У вас деление заменяется на целую процедуру с дополнительным делением и умножением — а это ресурсоемкие операции.
    Во всех языках есть команда деления с остатком, которая соответствует одной операции, возьмите остаток и сравните с делителем…


    1. AssemblerKing Автор
      29.05.2018 23:08

      Пожалуйста, обратите внимание на комментарий, оставленный ниже.


  1. AssemblerKing Автор
    29.05.2018 23:04

    Чтобы сравнивать остаток с делителем, их, во-первых, нужно сравнивать по модулю, а, во-вторых, потом как-то учесть их знаки.


    1. AssemblerKing Автор
      29.05.2018 23:15

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


      1. ultrinfaern
        30.05.2018 00:26
        +1

        Операции умножения и деления технически реализованы как умножение и деление в столбик, как в школе учили (утрирую немного). Самая убийственная операция это деление. А сравнение это всего-навсего вычитание. Поэтому, иногда лучше сравнить несколько раз чем делить.
        Флаг — это ноль или один это и нужно прибавить\вычесть к результату для округления.

        ЗЫ: Я не раз встречал алгоритмы выглядящие прекрасно с математической точки зрения, но физическая реализация ужасна — как и тут.


        1. AssemblerKing Автор
          30.05.2018 00:40

          Простите, но «флаг», то есть битовое/логическое значение, невозможно вычесть из числа или прибавить к нему.
          Операция деления не самая убийственная — она по времени занимает столько же, сколько и операция умножения. А операция умножения, столько же, сколько в сумме операция обнуления регистра TEMP[2] и сложения. В случае того оборудования, с которым доводилось иметь дело мне, и которое не поддерживало арифметику с плавающей запятой, — 31мкс.


          1. wormball
            30.05.2018 09:13

            Ну вот, например, у ARM Cortex-M3 умножение занимает один цикл, а деление до двенадцати.

            infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0337e/BABBCJII.html


            1. AssemblerKing Автор
              30.05.2018 10:09

              Да, вижу — архитектура рулит. В свою очередь, Вам предложу взглянуть на время выполнения таких команд у современных ПЛК Mitsubishi dl.mitsubishielectric.com/dl/fa/document/manual/plc_fx/jy997d16601/jy997d16601q.pdf стр930. Команда циклического сдвига вправо ROR, команда умножения MUL, деления — DIV. Третий столбец — 16 битные операции, четвертый 32-битные. Siemens такие данные на современные модели с некоторых пор упорно замалчивает, но по производительности пытается состязаться с Mitsubishi. Пройдя по этой ссылке есть шанс оценить возможность использования команд сдвига в разных старых моделях и время их выполнения в сравнении с умножением и делением dl.mitsubishielectric.com/dl/fa/document/manual/plc_fx/jy992d88101/jy992d88101e.pdf стр.378, наименования команд те же самые


          1. Griboks
            30.05.2018 10:16

            Флаг является числом. Почему его нельзя вычитать и складывать с другими числами? Он какой-то особенный? Комплексный? Векторный? Всегда флаги использовали как числа, а числа как флаги.


            1. AssemblerKing Автор
              30.05.2018 10:23

              Флаг — это битовое/логическое значение — «истина/ложь», складывать и вычитать бит с 16-ти или 32-битным числом нельзя. Эти операции выполняются только для чисел.

              Если так раздражает деление, а операция сдвига не выполняется совсем или выполняется медленно, возможен иной алгоритм: (A+A+B)/(B+B). Этот алгоритм сразу на выходе даст округленный результат.


              1. Griboks
                30.05.2018 10:32

                Я просто не могу понять, почему нельзя сделать так: 1+1010110001=1010110010.
                Следовательно, получается формула ещё проще A/B+Остаток>4


                1. AssemblerKing Автор
                  30.05.2018 10:59

                  Потому что устройство так не умеет делать, если единица представлена не 4-х, или 8-ми, или 16-ти, или 24-х, или 28-ми, или 32-битным числом, а единственным битом.
                  Формула, приведенная Вами, какая-то непонятная…


                  1. Griboks
                    30.05.2018 11:46

                    Почему бы не дописать нули спереди? Это любое устройство умеет делать.


                    1. AssemblerKing Автор
                      30.05.2018 11:59

                      Дописать-то, принципиально, можно. Только хлопотно это, и время.


                      1. Griboks
                        30.05.2018 12:08

                        Думаете, дописывать дольше, чем проходить 8-шаговый алгоритм?


                        1. AssemblerKing Автор
                          30.05.2018 12:26

                          Так Вы сперва поделитесь хотя бы соображением, как тот флаг сформировать и как учесть знаки чисел в числителе и знаменателе. Только после этого можно посчитать, что дольше, что быстрее. Само заполнение 7 бит по условию — 1,7мкс.


                          1. Griboks
                            30.05.2018 12:40

                            Я сейчас занят, но первое, что пришло в голову:
                            a/b=x'(огруг.) = x +(d*(x+1)-a<=a-b*x), где x — частное с отсечённым остатком.
                            Это где-то 6 действий, но тут надо по времени смотреть. Теперь мне кажется, что алгоритм из поста даже быстрее будет.


                            1. AssemblerKing Автор
                              30.05.2018 13:59

                              Благодарю.
                              Конечно. И тот, который расписал в дважды заминусованном посте habr.com/post/412613/#comment_18709951, состязается с ним по времени, потребляя при этом чуть меньше ячеек для промежуточных вычислений. Там одна команда деления, остальные команды — команды сложения


                              1. Griboks
                                30.05.2018 21:21

                                Заминусовали ваше отношение к флагам. Как вам такой алгоритм?
                                ((A+A)/b+1)/2
                                4 операции, 1 ячейка памяти


                                1. AssemblerKing Автор
                                  30.05.2018 21:33

                                  Она знаки числителя и знаменателя не учитывает) И ячейка памяти никак не одна: числитель должен быть Double Integer, знаменатель, соответственно, Double Integer, результат первой операции деления 2хDouble Integer. Результат второй даст ещё +Double Integer


                                  1. Griboks
                                    30.05.2018 21:57

                                    Я имел ввиду одну переменную. Но если так рассуждать, то в подпрограмму передаётся две ячейки integer с параметрами A и B. Тогда, используя всего одну буферную ячейку integer, можно выполнить все операции. Так что ячейка формально всего одна.

                                    С учётом знака ставится аж 5 операций и один переход. Уже можно сравнивать производительность)
                                    ((A+A)/b+1 [if(sign1!=sign2) -2])/2


                                    1. AssemblerKing Автор
                                      30.05.2018 22:06

                                      Ещё разок: одну никак не получится — у Вас числителе Double Int, поэтому знаменатель тоже Double Int, до деления результат тоже может оказаться Double Int, так что делить его на 2 придется в формате Double Int, что в итоге даст +4 ячейки к тем двум, куда были переданы входные значения…
                                      А сравнение знаков в Cи смотрится классно)


                                      1. Griboks
                                        30.05.2018 22:23

                                        Результат деления на b всегда будет int. Вы говорите, что процессор не поддерживает int2/int? В таком случае придётся задействовать ещё одну дополнительную ячейку, но не более. Единственное исключение — когда результат первого деления равен max или min. Но раз уж мы используем сразу две ячейки, то это вообще не играет роли. С другой стороны можно просто наложить ограничения.


                                        1. AssemblerKing Автор
                                          31.05.2018 05:37

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


                    1. Alexeyslav
                      30.05.2018 14:06

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


                      1. AssemblerKing Автор
                        30.05.2018 15:09

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


                        1. Griboks
                          30.05.2018 21:23

                          Потому что кто-то не умеет складывать числа разной длины.


                          1. AssemblerKing Автор
                            30.05.2018 21:36

                            Да почему ж) Тетраду с байтом легко


                1. Alexeyslav
                  30.05.2018 11:03

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


                  1. AssemblerKing Автор
                    30.05.2018 11:30

                    Там проблема, Alexeyslav, даже не в том, что озвученные Вами операции выполняются долго, а в том, что сравнивать значения остатка с делителем, как предлагал ultrinfaern, совершенно бессмысленно: остаток по абсолютной величине всегда меньше делителя, к тому же остаток с делителем могут быть одного знака, а могут быть разных. Операция сложения остатка с самим собой вкупе с операцией деления решают вопрос и с величиной прибавки и с её знаком.


                  1. Griboks
                    30.05.2018 11:49

                    Так флаг это и есть число. Это числа для удобства превращают во флаги.


                    1. AssemblerKing Автор
                      30.05.2018 12:01

                      Уже говорил, что флаг это бит. Его не из чисел выдергивают, а формируют логической операцией.


                      1. Griboks
                        30.05.2018 12:07

                        Логическая операция, это тоже арифметика и с физической, и с математической точки зрения. Так операция сравнения это (b-a)/|b-a|. В физике, например, компаратор действует аналогично. На самом деле, все операции компьютер осуществляет арифметикой. Как никак в цифровом веке живём.


                        1. AssemblerKing Автор
                          30.05.2018 12:28

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


                        1. AssemblerKing Автор
                          30.05.2018 12:32

                          Плюс у Вас операция деления, супротив которой все протестуют.


                          1. Griboks
                            30.05.2018 21:27

                            Операция модуля выполняется обнулением знакового бита. А протестуют тут не против деления, а против его многократного использования в вашем алгоритме. Но это опять же надо смотреть по производительности.
                            p.s. я вам выше написал свой алгоритм в 4 операции и 1 ячейку памяти.


                            1. AssemblerKing Автор
                              30.05.2018 21:49

                              Это да, можно, командой WAND…
                              Команд деления у меня только две. Первый шум начался из-за команды умножения. Я ей заменил две команды — обнуления старшего слова и сложения — в моей технике суммарно они выполняются столько же времени, сколько одна команда умножения.


  1. AssemblerKing Автор
    29.05.2018 23:25

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


    1. aamonster
      30.05.2018 08:07

      Не минусовал, но всю жизнь писал, как yizraor, ваш вариант даже в кошмарном сне бы не приснился: вторая операция деления вместо сдвига — это ужасно.
      На Си это просто
      result = (x + (y>>1)/2) / y
      или даже
      result = (x + y/2)/y


      Операция сдвига практически бесплатна в отличие от деления.


      1. AssemblerKing Автор
        30.05.2018 08:53

        Но речь то идет не о ПК, а о ПЛК и микроконтроллерах, об их вычислительных возможностях. Во-первых, это «железо» Си напрямую не потребляет — то, что написано на Си, компилируется в язык тех команд, которые это железо поддерживает. А оно, вследствие особенностей архитектуры, может: а) совсем не поддерживать сдвиговые операции; б) поддерживать только операции циклического сдвига; в) поддерживать оба типа операций сдвига. Однако и во-втором, и в-третьем случаях, операция сдвига занимает времени существенно больше, чем сложение, умножение, деление. Например, у самого современного ПЛК время на выполнение операции циклического сдвига, как минимум, вдвое превышает время на выполнение операции деления сдвоенных целых чисел.
        Мне тут другой алгоритм пиарили, так время его выполнения на 20 с лишним процентов выше, чем описанного мной в публикации.
        Если программируете ПЛК или микроконтроллеры, то у меня к Вам вопрос: всегда ли анализируете скомпилированный код и оцениваете время выполнения написанной процедуры?


        1. Serge78rus
          30.05.2018 10:20

          Так Вы бы в статье прямо и написали, что речь идет о ПЛК, никто бы с Вами и не спорил — в АСУТП вечно все не как у людей. А то я, например, особенно глядя на Ваш ник, подумал что мне сейчас будет открыто нечто сокровенное, а тут ПЛК.

          ПЛК и микроконтроллерах
          Кстати, а что Вы понимаете под словом микроконтроллер? Полагаю, что большинство здесь присутствующих под этом термином подразумевают однокристалку, а Вы?
          Во-первых, это «железо» Си напрямую не потребляет — то, что написано на Си, компилируется в язык тех команд, которые это железо поддерживает.
          Никакое железо Си напрямую не «потребляет», так же, как и другие языки программирования, а исполняет программу в машинных кодах. Впрочем, подозреваю, что то «железо», с которым Вы работаете, вовсе не железо, а некая программная система, выполняющая Ваш код, и ее система команд к системе команд того, действительно «железного» процессора, на котором все это крутится, не имеет ни какого отношения. Если я не прав в своих подозрениях, то поправьте меня, но я не представляю себе архитектуру процессора, выполняющего сдвиг дольше умножения и, тем более, деления.


          1. AssemblerKing Автор
            30.05.2018 10:39

            Кстати, а что Вы понимаете под словом микроконтроллер? Полагаю, что большинство здесь присутствующих под этом термином подразумевают однокристалку, а Вы?

            И я про них же)
            Если я не прав в своих подозрениях, то поправьте меня

            Поправлю) Об архитектуре АЛУ процессора ПЛК я говорил в комментариях предыдущей своей заметке: «Анализ бита под «результирующие битовые операции» со словами не подпадает, как и операции модифицирующие значение отдельного бита. Под «результирующими битовыми» мной имелись в виду именно операции побитового сдвига слова, так как операции с отдельно взятыми битами выполняются в битовом аккумуляторе — стеке логических операций, а операции со словами уже в другом аккумуляторе, с пословной организацией.»
            Когда-то давно процессор ПЛК имел АЛУ только с битовым аккумулятором, и вот тогда все операции со словами выполнялись очень медленно, команды умножения/деления в особенности.


            1. Serge78rus
              30.05.2018 13:05

              В статье Вы пишете не об «архитектуре АЛУ процессора ПЛК», а об архитектуре того, что Вам, как АСУТПшнику, предоставили разработчики ПЛК Mitsubishi моделей FX3S/3G. Чтобы говорить аб архитектуре процессора, для начала нужно посмотреть документацию именно микропроцессора, на которой выполнен данный ПЛК.


              1. AssemblerKing Автор
                30.05.2018 13:17

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


                1. Serge78rus
                  30.05.2018 13:27

                  Тогда, наверное, архитектуру собственно процессора и не зачем обсуждать, можно говорить только о ПЛК, как «вещи в себе», но при этом четко обозначать, о чем идет речь.


                  1. AssemblerKing Автор
                    30.05.2018 14:05

                    Не совсем. С самой архитектурой АЛУ таких устройств я знаком из документации и практики. А вот что за кристалл, какова тактовая частота и MIPS, вот эта информация сохраняется в тайне.


                    1. Serge78rus
                      30.05.2018 16:10
                      +2

                      А Вы уверены, что то, что Вам предоставляют средства разработки и документация на ПЛК и есть физическая реализация, а не программная эмуляция системой исполнения некоего виртуального процессора? Если честно, то с Mitsubishi сталкиваться не приходилось, но приходилось с ПЛК Сименс — там именно так и сделано и физический процессор Infenion, на котором реализован контроллер не имеет ничего общего с архитектурой, представляемой системой разработки.


                      1. AssemblerKing Автор
                        30.05.2018 16:40

                        Уверен. Сам когда-то писал операционки под МК. Программист видит исполняемый макрокод. Он состоит из коротких инструкций известной длины. При считывании этот код идентичен тому, что был загружен в ПЛК. Если бы была «прослойка», трансформирующая написанную программу в иную систему команд, средство разработки не смогло бы восстановить оригинал. Тут полная корреляция с объемом кода. Преобразование только в отношении ST (Паскаль-подобного/Си-подобного языка) справедливо. Написанное в других IEC-языках, особенно в Melsec IL, «как слышится, так и пишется».


                        1. Serge78rus
                          30.05.2018 17:17

                          Если бы была «прослойка», трансформирующая написанную программу в иную систему команд, средство разработки не смогло бы восстановить оригинал.
                          Почему? Вы пишете на некоем «ассемблере» (в Сименсе — это STL, в большинстве других ПЛК — это IL, суть одна и та же), дальше Ваша программа транслируется в исполняемый байт-код, который Вы и загружаете в ПЛК. Но это исполняемый код для исполняющей системы, а не машинный код процессора. Именно из этого бай-кода средство разработки и восстанавливает оригинал. Как некий аналог, можно привести в пример исполняющую систему Java, там Вы тоже можете получить некое подобие ассемблера, описывающего исполняемый байт-код и даже восстановить из него исходный текст, но этот ассемблер не имеет ничего общего с системой команд реальной «железной» машины, на которой выполняется.


                          1. AssemblerKing Автор
                            30.05.2018 17:43

                            Всё верно. Но языковое представление этого массива чисел — ассемблера ПЛК- есть Макроассемблер, и они напрямую, а не косвенно взаимосвязаны друг с другом. Тогда как из кода, полученного в результате компиляции программы, написанной на Си, уже никогда не получить чего-либо хоть отдаленно напоминающего исходник.


          1. Alexeyslav
            30.05.2018 10:40

            А я, например, могу представить… особенно если это архитектура заточенная на ускорение вычислений, и операции сдвига там не в приоритете. Например, если умножение/деление выполняется за 1 маш.цикл(а чаще и вовсе по 8 чисел за раз), а сдвиг за два т.к. КОП слишком длинный.


            1. AssemblerKing Автор
              30.05.2018 10:52

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


              1. picul
                30.05.2018 19:46

                Ну видимо все таки не утрачивают, раз приходится два раза делить для простого округления.


                1. AssemblerKing Автор
                  30.05.2018 20:03

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


            1. Serge78rus
              30.05.2018 13:29

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


        1. roscomtheend
          30.05.2018 11:20

          Потому говорить о реализации в общем случае не имеет смысла, а имеет смысл говорить о реализации для чипа A1234, потому как в A1235 может быть оптимизация и сдвиг будет быстрым, от чего реализация через деление будет страшным тормозом.
          PS. Никакое железо Си напрямую не потребляет, потребляется машинный код, а уж во что Си[++] будет транслирован — зависит от компилятора и его флагов. Иногда они «умные», иногда не очень — когда нужна скорость приходится смотреть на результат и пробовать разные варианты.


          1. AssemblerKing Автор
            30.05.2018 11:39

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


            1. roscomtheend
              30.05.2018 16:46

              Современный компилятор иногда справляется лучше человека, особенно на больших объёмах кода, когда каждый цикл тюнить дорого. Но посмотреть узкие места и мочь их переписать завсегда полезно. Как и про переполнение (об этом вообще редко помнят).


              1. AssemblerKing Автор
                30.05.2018 17:30

                В частности, об этом, о переполнении, я и хотел поведать дальше. Но, видимо, сперва придется откорректировать этот текст. Во всяком случае, изъяв из него описание алгоритма, который всех раздражает больше всего тем, что в нем присутствует вторая команда деления. Хотя я проанализировал, и часть альтернативных алгоритмов, озвученных мне в комментариях, дают либо в корне неверный результат (при разнознаковых значениях числителя со знаменателем), либо ошибку округления…


        1. aamonster
          30.05.2018 11:38

          "О микроконтроллерах" — операция сдвига примерно всегда есть:


          • AVR: LSR для 8-битных беззнаковых, ASR для знаковых, для чисел 16+ бит — ROR для всех байтов кроме старшего, итого 1 однотактовая операция на байт
          • ARM — то же самое для 32-битных
          • 8051 — RRC; PIC — LSRF/ASRF/RRF
          • MSP430 — RRA/RRC

          Деление может вообще отсутствовать (AVR) или требовать больше тактов (Cortex M3 — от 2 до 12, и это ещё дёшево!)


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


          Что касается ПЛК — с ними не работал. Вероятно, там есть свои особенности (как я понимаю, прямого доступа к МК нет, всё довольно медленно и на фоне этого теряется разница между делением и сдвигом, если он есть — так?). Вот если бы вы акцентировались на специфике конкретного ПЛК (ну там, разработчики ПЛК сдвиг не дали) — другое дело. Но даже если нет операции сдвига — не вижу проблемы использовать стандартный алгоритм (прибавить половину делителя перед операцией), будет два деления, как и у вас. Разве что операций перемещения может оказаться больше, но это опять же случай, когда надо разобрать и показать, почему надо использовать ваш алгоритм вместо стандартного.


          1. AssemblerKing Автор
            30.05.2018 11:46

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


            1. aamonster
              30.05.2018 12:26

              Да, это, безусловно, так.
              А я в своём первом комменте пытался объяснить, за что могут рефлекторно минусовать. Сам был шокирован, но не настолько, чтобы минусовать (даже если бы была возможность) — скорее объяснить, как обычно делают и почему.


              Да, возвращаясь к вопросу "всегда ли анализируете скомпилированный код и оцениваете время выполнения написанной процедуры" — только "узкие места". Нет смысла оптимизировать всё.
              А делений избегаю рефлекторно. Привычка ещё с 8080.


              1. AssemblerKing Автор
                30.05.2018 13:47

                Понятно) У меня вот старые привычки постирались, какое железо со своим набором команд, такой и код. Всегда считаю его длину и почти всегда время выполнения. Потому как любимое занятие — написание драйверов и компактных программных процедур, которые могут быть использованы другими программистами.
                По поводу минусования… — как думаете, за что вот этот мой комментарий ажно дважды отминусовали habr.com/post/412613/#comment_18709951?


        1. Welran
          30.05.2018 14:55

          Почему бы не исправить статью и не написать что она для ПЛК, о существовании которых 90% прочитавших статью даже не знают, не говоря уж об особенностях их вычислительных возможностей?


          1. AssemblerKing Автор
            30.05.2018 15:01

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


            1. Welran
              30.05.2018 15:47
              +2

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


              1. AssemblerKing Автор
                30.05.2018 15:50

                Важнее первое.


        1. yizraor
          30.05.2018 21:20

          -


  1. yizraor
    29.05.2018 23:54

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


    Прошу простить, но считаю Ваш вариант неэффективным способом.
    Я в своё время придумал другой: прибавить к числу половину делителя, затем выполнить само целочисленное деление.
    Если делим в цикле на одно и то же число, то половину делителя можно вычислить один раз. В противном же случае: целочисленное деление пополам — это битовый сдвиг на 1 позицию вправо, что тоже очень быстрая операция :)
    Под работу с отрицательными числами данный способ расширяется без особого труда: добавится условный переход, но это лучше чем второе деление.


    Пример для дроби 64/13:
    (64 + (13 / 2)) / 13 = (64 + (13 >> 1)) / 13 = (64 + 6) / 13 = 70 / 13 = 5


    1. AssemblerKing Автор
      30.05.2018 00:24
      -1

      Благодарю. Ваш способ понятен, но его реализация в условиях расчета в ПЛК и ряде микроконтроллеров, неудачна. Во-первых, операции сдвига в таких устройствах, вследствие определенной организации АЛУ, или выполняются дольше операций сложения, деления, умножения, а кое-где и вовсе не поддерживаются (об этом я писал в комментариях к предыдущей своей заметке). Во-вторых, это предложенный Вами способ также завязан на деление, результат которого нужно хранить, а делить (помимо процедуры округления нужно единожды). В-третьих, предложенный Вами расчет явно оперирует с абсолютными величинами (по модулю) — контроллер не поддерживает операций взятия абсолютной величины. Ну и, в-четвертых, в продолжении изложения материала, я планировал показать, какие встречаются задачи и как они реализуются. Из этого материала, в частности, должно проясниться, с какими вычислениями приходится оперировать, в том числе экономя ресурсы, которые у контроллеров, в отличии от компьютеров, весьма ограничены.
      Код кажется длиннее лишь потому. что реализован в формате подпрограммы, которую можно вызвать сколь угодно раз.


      1. picul
        30.05.2018 01:40
        +1

        А что это за архитектура такая, в которой есть сложение/умножение, и нет побитового сдвига? Что-то не верится, что такие жертвы в принципе могут быть оправданы (для умножения по логике должен поддерживаться хоть какой-то сдвиг). Вам на самом деле приходится применять этих зверей на практике?


        1. AssemblerKing Автор
          30.05.2018 01:49

          Скажем так, приходилось. Например, ПЛК Mitsubishi серий FX1S и FX1N, которые до сих пор в ходу, ибо служат десятилетиями. Новые модели уже поддерживают и сдвиги (частично) и вычисления с плавающей запятой, но все эти операции выполняются гораздо дольше целочисленных операций со словами. В комментариях к предыдущей статье немного пояснял, почему.
          Отсутствие поддержки операций сдвига не самое ужасное. Как-то пришлось решать задачу с вычислениями на контроллере, который умножать два 16-битных числа умел, а поделить результат умножения — нет, и его пришлось такой арифметике программным способом обучать.


          1. aamonster
            30.05.2018 08:10

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


            1. AssemblerKing Автор
              30.05.2018 09:01

              Простите моё невежество, а какая есть общепринятая?
              Что касается экзотики, то никакой экзотики нет. Это норма для такого железа, когда время выполнения операции сдвига вдвое превышает время операции деления сдвоенных целых чисел.
              К слову, даже алгоритм, о котором говорит yizraor, с меньшим числом операций деления, отнимает по времени на 20 с лишним процентов больше, чем тот, который озвучил в публикации.


              1. GarryC
                30.05.2018 10:36

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


                1. AssemblerKing Автор
                  30.05.2018 10:42

                  Спасибо, учем-с) Они еще и живут в мире, где сама операция сдвига реализована в процессоре;)


                  1. picul
                    30.05.2018 11:24

                    Загуглил ПЛК Mitsubishi, средняя найденная мною цена — примерно $70. Почему бы не перейти на МК, которые и по цене намного дешевле, и по возможностям намного круче?


                    1. AssemblerKing Автор
                      30.05.2018 11:56

                      70USD это не цена ПЛК, столько разве что модули ввода-вывода к нему стоят. ПЛК разрабатываются и изготавливаются специально для промышленных применений, даже полуготовые игрушки типа Ардуин и Рапсберри в промышленности не годятся. Это раз. Два — они как модульный конструктор, специально заточенный для решения определенного класса задач. Если что-то внезапно вышло из строя, быстро снял модуль и поменял.


                      1. picul
                        30.05.2018 12:45

                        полуготовые игрушки типа Ардуин и Рапсберри в промышленности не годятся
                        Согласен, но как на счет МК типа STM, например? Они вроде тоже в промышленности применяются.
                        они как модульный конструктор
                        А зачем совмещено конструирование и программирование? Если идет ориентация на программирование — можно ведь запросто реализовать в железе все элементарные операции, это доказано на практике. Если на конструирование — берите да собирайте схемы из захардкоженных логических элементов, зачем в них еще что-то дописывать? Я что-то не так понимаю?


                        1. AssemblerKing Автор
                          30.05.2018 13:35

                          Во-первых, процессоры STM не сам по себе живут, им нужна печатная плата и внешняя обвязка. Нужны УСО с опторазвязкой и т.д. Для крупного производства вопрос цены не столь критичен. Критично другое — надежность.
                          Ни о каком конструировании при сборке ПЛК речь не идет — просто его конфигурация «нанизывается» из модулей — базового, с процессором внутри, и модулей расширения. Есть и другой конструктив — на шасси. Гибкость, универсальность и быстрый ремонт модульной заменой, вот что ставится во главу угла.
                          Те кто переходит от разработки электронных схем, печатных плат и Embedded Soft к микроконтроллерам, очень быстро ощущают разницу в скорости разработки, в суммарных затратах на разработку решения и эту самую гибкость в пользу ПЛК, после чего обычно напрочь отказываются от разработок и создания электроники под конкретную задачу.


                          1. Serge78rus
                            30.05.2018 16:46
                            +1

                            С первым абзацем трудно не согласиться, а вот во втором Вы опять путаете понятие «микроконтроллер», как его понимают большинство здесь присутствующих, и ПЛК. STM — это и есть микроконтроллер, а то, с чем работаете Вы — ПЛК. Насчет скорости разработки именно программной части — можно поспорить. Выше Вы сами пишете, что не можете доверять компилятору C и вынуждены писать не ассемблере, а большая часть софта для однокристалок сейчас благополучно пишутся именно на C и даже на C++ (если хорошо понимать, что можно, а чего не стоит делать). На ассемблер приходится спускаться довольно редко, только когда это действительно необходимо. Именно поэтому Ваши проблемы со сдвигом и подобными операциями, выполняющимися на микроконтроллере за 1 такт (для того же STM — это, в зависимости от тактовой частоты, порядка десятков наносекунд) большинству непонятны.


                            1. AssemblerKing Автор
                              30.05.2018 16:58

                              Я ничего не путаю) Для ПЛК тоже можно писать программы на Си-подобном языке, только код будет неоптимален и совсем не похож на код исходника. Скорость разработки программы зависит больше не от того, на чем написано, а от того, кто пишет. Когда сказал о скорости разработки, само собой. имел в виду скорость разработки решения в целом, от и до, а не только его программной части.
                              То, что «многим непонятно», уже отметил для себя.


                              1. Serge78rus
                                30.05.2018 17:46

                                Для ПЛК тоже можно писать программы на Си-подобном языке, только код будет неоптимален и совсем не похож на код исходника.
                                ключевое слово — на Си-подобном.
                                Скорость разработки программы зависит больше не от того, на чем написано, а от того, кто пишет.
                                Естественно, если человек не умеет программировать, то никакой язык высокого уровня ему не поможет. По поводу того, что уровень языка имеет мало значения — Ваш тезис опровергается всей историей развития программирования. Кстати, тот же C, ранее считавшийся языком высокого уровня, с некоторых пор перестал считаться таковым. Но в области микроконтроллеров для C пока нет полноценной замены (да, про Rust знаю, но он еще слишком молод для повсеместного использования).


                                1. AssemblerKing Автор
                                  30.05.2018 18:14

                                  Я согласен с тем, что писать на Си для МК намного удобнее, чем покомандно кодить. Сам во времена, когда ещё персоналки были только отечественными и строго под замком, писал длинный код в несколько кБ на листочке в клеточку, прокручивая работу программы в голове.
                                  Но вот в части разработки программ для ПЛК с начала 2000-х Си фактически укокошил профессию программиста систем автоматики и продолжает добивать средства разработки программ для ПЛК, из которых стараниями нерадивых и необразованных менеджеров, которые слыхом не слыхивали о сквозной программной совместимости. стали изымать прочие языки, входящие в стандарт IEC. Написанные прежде программы обновленными версиями софта больше не открываются, а годами наработанные библиотеки готовых элементов и процедур вмиг превратились в труху. Просмотр целиком скомпилированного программного кода, написанного на языках, отличных от Си, больше невозможен — только мелкими кусочками и т.д., и т.п.
                                  Когда в начале 2000-х с Запада надуло поветрием брать на работу Си-программистов взамен программистов ПЛК, а вместо самих ПЛК ставить ПК с силиконовым диском, вторая категория работников вынуждена была оставить свой привычный труд… Чуть позже у сишников, трудящихся в иных сферах, выросли зарплаты и новоявленные программисты систем автоматики хлынули туда, где больше платят, ПК с силиконовыми дисками, не прослужив и двух лет стали резко сыпаться, решили вернуться к старым, добрым ПЛК, а программистов-то, настоящих, знающих линейку с которой нужно работать, уже ищи свищи. Тогда и пришлось приделывать примочку к пакетам разработки в виде компилятора с языка Си. Теперь программистов на Си пруд-пруди, а заработки в профессии сильно упали.


                                  1. Serge78rus
                                    31.05.2018 10:18

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

                                    Теперь программистов на Си пруд-пруди
                                    Знание языка C для программиста сродни знанию латыни для врача. Но от этого не стоит считать любого программиста, знакомого с этим языком, а их действительно — большинство, именно C программистами, коих сейчас не так и много на фоне всех других языков.


                                    1. AssemblerKing Автор
                                      31.05.2018 10:56

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


                                      1. Serge78rus
                                        31.05.2018 12:06

                                        Как говорится: в каждой избушке — свой погремушки. Естественно, работая в разных конторах, у нас складывается разное видение истории смены тенденций.


                              1. picul
                                30.05.2018 19:58
                                +1

                                код будет неоптимален
                                Это говорит о том, что в 2018 году для обсуждаемой архитектуры еще нет нормального C-компилятора — еще одна причина оставить эту архитектуру в прошлом.
                                и совсем не похож на код исходника
                                Про это не очень понял. Он ведь и не должен быть похожим, то С, а то Ассемблер.
                                Скорость разработки программы зависит больше не от того, на чем написано, а от того, кто пишет.
                                Если Вы способны писать качественный код на Ассемблере с той же скоростью, что и на С, то мне остается только Вас поздравить. Но мне все же кажется, что большинство людей в мире не такие.


                                1. AssemblerKing Автор
                                  30.05.2018 20:06

                                  Тогда порадуйтесь, я способен;)


                                  1. picul
                                    30.05.2018 20:21

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


                                    1. AssemblerKing Автор
                                      30.05.2018 20:26

                                      Уверен, что да (только сперва надо Си выучить;). О каких архитектурах речь?


                                      1. picul
                                        31.05.2018 14:24

                                        Ну, например, x86-64, ARM. На них кодить на Асме нужно как минимум очень осторожно.


                          1. picul
                            30.05.2018 20:02
                            +1

                            гибкость в пользу ПЛК
                            Отсутствие операции побитового сдвига — это гибкость?


                            1. AssemblerKing Автор
                              30.05.2018 20:11

                              Я ж не об этом. Я о том, что новая задача может быть решена на том же железе.


                              1. picul
                                30.05.2018 20:22
                                +1

                                А еще ее наверняка на вакуумных лампах можно решить. Но от этого ведь совсем не легче?


                                1. AssemblerKing Автор
                                  30.05.2018 20:28

                                  Ох, не любите Вы ПЛК)))


                                  1. Serge78rus
                                    31.05.2018 10:04

                                    Можно я отвечу за picul? Я их ненавижу, так как по ряду сложившихся обстоятельств, уже имея солидный опыт разработки больших проектов на C++, а так же некоторый опыт эмбединга, вынужден был 4 года программировать ПЛК Сименс. Эти годы я считаю бессмысленно потерянными из своей жизни программиста.


                                    1. AssemblerKing Автор
                                      31.05.2018 10:46

                                      У меня их выпало нааамного больше. Причем, Сименс когда-то я тоже программировал. И был момент, когда в качестве программиста ПЛК Сименс все АСУТПшные компании города, делавшие проекты на их контроллерах, рвались «приобрести» меня за любые деньги, на любых условиях. Но я уже более полугода как перешел работать с ПЛК Митсу, которая профукала рынок.


                                      1. Serge78rus
                                        31.05.2018 12:21

                                        Я же и не пытаюсь сравнивать свой опыт в области ПЛК с Вашим. Я честно говорю, что весь этот период работы считаю бесполезно потерянным временем. Причина — именно то, чему Вы посвятили обе свои статьи: вместо решения действительно актуальных проблем находишься в перманентном состоянии войны с несовершенством системы разработки. Причем, для большинства АСУТПшников, не имеющих серьезного опыта работы с «нормальными» средствами разработки, это является нормой. Естественно, как только смог посчитать возможным прекратить эту деятельность, я ее прекратил и вернулся к более привычному для себя программированию «традиционными» средствами.


                    1. Alexeyslav
                      30.05.2018 14:18

                      Так в ПЛК по сути и стоит свой МК с микропрограммой. Но дело тут не в МК, а в том что для ПЛК все интерфейсы стандартизированы и имеют защиты от статики, перегрузки, гальваническую развязку и прочее. Как начнёшь аналогичное разрабатывать на основе МК с теми же характеристиками и требованиями, сразу же с первых шагов перебъёшь этот ценник. И во вторых насобираешь детских багов в процессе разработки прошивки, гуляя по граблям.


                      1. Serge78rus
                        30.05.2018 17:57
                        +1

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


                        1. AssemblerKing Автор
                          30.05.2018 18:24

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


                          1. picul
                            30.05.2018 19:59

                            Да, во всем виноват компилятор)


                            1. AssemblerKing Автор
                              30.05.2018 20:09

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


                              1. picul
                                30.05.2018 20:28
                                +1

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


                                1. AssemblerKing Автор
                                  30.05.2018 20:33
                                  +1

                                  Это не архитектура устарела, это программисты измельчали…


                                  1. picul
                                    31.05.2018 14:28

                                    Правда? Просто у нас на x86-64 баги компилятора встечаются ну очень редко.


                            1. Serge78rus
                              31.05.2018 09:56

                              Вы знаете, в области АСУТП в этой шутке есть приличная доля истины. Только, я бы заменил слово «компилятор» на более широкое понятие «средство разработки».


                              1. AssemblerKing Автор
                                31.05.2018 10:31

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


                                1. Serge78rus
                                  31.05.2018 12:28

                                  Я отвечал не Вам, а на комментарий

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


      1. yizraor
        30.05.2018 21:22

        Понял Вас.
        Прошу простить за скептическое отношение.
        Сам микроконтроллеры не программировал, и тонкостей не знал (про отсутствие сдвига на некотором железе), поэтому судил со своей x86-й колокольни :)


        1. AssemblerKing Автор
          30.05.2018 21:35

          )) Никаких проблем)