Pull to refresh

Целочисленная арифметика. Делим с округлением результата. Часть 1

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

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

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

Реализуя вычисления с дробями, этот момент частенько упускают из вида, а, упустив, получают потери в точности вычислений. Причем точность вычислений падает с ростом величины делителя. К примеру, что 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.
Продолжение следует...
Tags:
Hubs:
Total votes 17: ↑9 and ↓8+1
Comments139

Articles