GNU BC: Как полезен “модуль” (%) с масштабом, отличным от 0?

Вопрос или проблема

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

При использовании bc оператор % утверждается, что вычисляет “остаток”, и да, он работает для целых чисел и когда масштаб равен нулю:

$ bc <<<' scale=0; 27 % 7 '
6

Но он не дает “целый остаток”, если масштаб не ноль:

$ bc <<<' scale=10; 27 % 7 '
.0000000003

Почему (или как) это определение оператора % модуль полезно?

Оператор % явно определен в руководстве bc как [a] :

# Определение внутреннего оператора %:
define internalmod(n,d,s) { auto r,oldscale;
                           oldscale=scale; r=n/d;
                           s=max(s+scale(d),scale(n)); 
                           scale=s; r = n-(r)*d;
                           scale=oldscale; return(r) }

Предполагая, что max было определено как:

define max(x,y){ if(x>y){return(x)};return(y) }

Как это длинное определение полезно?

  1. Целый остаток

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

    Если числа целые, и масштаб установлен на 0, это функция целого остатка.

    $ bc <<<'n=17; d=3; scale=0;a=internalmod(n,d,scale);b=n%d;print a," ",b,"\n"'
    2 2
    $ bc <<<'n=17; d=6; scale=0;a=internalmod(n,d,scale);b=n%d;print a," ",b,"\n"'
    5 5
    

    Это не то же самое, что и математическая функция мод. Я решу это ниже.

  2. Дробный остаток
    Если число n является более длинным десятичным числом, и мы изменим масштаб, мы получим:

    $ bc <<<'n=17.123456789;d=1; scale=0 ;a=internalmod(n,d,scale);b=n%d;
                      print a," ",b,"\n"'
    .123456789 .123456789
    
    $ bc <<<'n=17.123456789;d=1; scale=3 ;a=internalmod(n,d,scale);b=n%d;
                      print a," ",b,"\n"'
    .000456789 .000456789
    

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

    $ bc <<<'n=17.123456789;d=1; scale=7 ;a=internalmod(n,d,scale);b=n%d;
                      print a," ",b,"\n"'
    .000000089 .000000089
    

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

    Теперь это: остаток после значения масштаба.

  3. Изменение масштаба

    Изменение масштаба требуется, потому что число d (делитель) может иметь больше десятичных знаков, чем n. В этом случае нужно больше десятичных знаков, чтобы получить более точный результат от деления:

    $ bc <<<'n=17.123456789; d=1.00000000001; scale=0;
             a=internalmod(n,d,scale);   b=n%d;
             print a," ",scale(a)," -- ", b," ",scale(b),"\n"'
    .12345678883 11 -- .12345678883 11
    

    И если изменить масштаб:

    $ bc <<<'n=17.123456789; d=1.00000000001; scale=5;
             a=internalmod(n,d,scale);    b=n%d;
             print a," ",scale(a)," -- ", b," ",scale(b),"\n"'
    .0000067888287655 16 -- .0000067888287655 16
    

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

    Я предположу, что по сравнению между internalmod и оператором % оба были доказаны как эквивалентные.

  4. Недоразумение

    Будьте осторожны, потому что игра с значением d может стать запутанной:

    $ bc <<<'n=17.123456789; d=10; scale=3; a=n%d;
                      print a," ",scale(a),"\n"'
    .003456789 9
    

    И:

    $ bc <<<'n=17.123456789; d=1000; scale=3; a=n%d;
                      print a," ",scale(a),"\n"'
    .123456789 9
    

    Это: значение d (выше 1) изменит эффект установленного значения масштаба.

    Вероятно, для значений d, отличающихся от 1, вам следует использовать scale=0 (если вы действительно знаете, что делаете).

  5. Математический мод

    Поскольку мы так глубоко погружаемся в функции мод, нам, вероятно, следует уточнить реальный эффект % в bc. Оператор % в bc использует “усеченное деление”. То есть оно округляется к 0. Это важно для отрицательных значений как n, так и/или d:

    $ bc <<<'scale=0; n=13; d=7; n%d; '
    6
    
    $ bc <<<'scale=0; n=13; d=-7; n%d; '
    6
    

    Знак остатка следует за знаком делимого.

    $ bc <<<'scale=0; n=-13; d=7; n%d; '
    -6
    
    $ bc <<<'scale=0; n=-13; d=-7; n%d; '
    -6
    

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

    Чтобы получить эту (целочисленную) функцию мод, используйте:

    # Модуль с всегда положительным остатком (евклидово деление).
    define modeuclid(x,div)  {  if(div!=int(div)){
                                "ошибка: делитель должен быть целым ";return(0)};
                                return(x - div*int(x/div))  }
    

    И (тогда) это будет работать:

    $ bc <<<"n=7.123456789; d=5; modeuclid(34.123456789,7)"
    6.123456789
    

[a]

expr % expr
Результат выражения – это “остаток”, и он вычисляется следующим образом. Чтобы вычислить a%b, сначала вычисляется a/b до десятичных знаков масштаба. Этот результат используется для вычисления a-(a/b)*b до масштаба максимум scale+scale(b) и scale(a).
Если масштаб установлен в ноль и оба выражения являются целыми, это выражение является функцией целого остатка.


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

$ alias bc="bc -l "$HOME/.func.bc""

И создайте файл с именем $HOME/.func.bc, который содержит (по крайней мере):

# Внутреннее определение оператора %:
define internalmod(n,d,s) { auto r,oldscale;
                           oldscale=scale; r=n/d;
                           s=max(s+scale(d),scale(n)); 
                           scale=s; r = n-(r)*d;
                            scale=oldscale; return(r) } 
# Функция max
define max(x,y){ if(x>y){return(x)};return(y) }

# Целая часть числа по направлению к 0:  -1.99 -> -1, 0.99 -> 0
define int(x)        {  auto os;os=scale;scale=0;
                        x=sgn(x)*abs(x)/1;scale=os;return(x)  }

define sgn (x)       {  if (x<0){x=-1};if(x>0){x=1};return(x) };
define abs (x)       {  if (x<0) x=-x; return x }; 

# Модуль с всегда положительным остатком (евклидово деление).
define modeuclid(x,div)  {  if(div!=int(div)){
                           "ошибка: делитель должен быть целым ";return(0)};
                           return(x - div*int(x/div))  }

Функцию мод для любого числа (целого или нет) можно определить как:

# Модуль с всегда положительным остатком (евклидово деление).
define modeuclid(x,div)  {  div=abs(div);return(x - div*floor(x/div))  }

# Округление вниз до целого ниже x (в сторону -бесконечности).
define floor (x)         {  auto os,y;os=scale;scale=0;
            y=x/1;if(y>x){y-=1};scale=os;return(y) };

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

Описание BC и его операторов является частью стандарта POSIX. В настоящее время оно доступно здесь:

https://pubs.opengroup.org/onlinepubs/9699919799/utilities/bc.html

a % b определяется как a – (a / b) * b и вычисляется до масштаба max(scale + scale(b), scale(a)).

Это имеет место в GNU BC в текущей версии 1.07.1. Для BSD BC является лишь оболочкой для DC, и DC использует “bn” для “%”.

В C-BC это также правда. C-BC, исходный код которого архивирован здесь:

https://github.com/RockBrentwood/CBC

является большим расширением BC, гораздо ближе к C и (в основном) является надмножеством GNU BC и BSD BC.

Ответ или решение

Как оператор “модуль” (%) в GNU BC полезен при ненулевом масштабе?

GNU BC (базовый калькулятор) представляет собой инструмент для выполнения математических расчетов с высокой точностью, поддерживающий операции с числами с плавающей запятой. Одной из интересных особенностей в GNU BC является оператор % (модуль), который позволяет находить остаток от деления. Однако его поведение может показаться неинтуитивным, особенно при ненулевом масштабе. В этой статье мы раскроем, как и почему операторы и функциональность модуля полезны, даже когда масштаб превышает ноль.

1. Оператор % и его предназначение

Оператор % в GNU BC вычисляет остаток от деления с использованием следующей формулы:
[
a \% b = a – (a / b) \times b
]
При этом результат округляется до максимальной точности, заданной как max(scale + scale(b), scale(a)). Это значит, что в случае работы с вещественными числами и ненулевым масштабом, результаты остаются точными на заданное количество знаков после запятой.

2. Полезность остатка при ненулевом масштабе

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

$ bc <<< 'scale=10; 27.4321 % 7.3'
0.2321

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

3. Расширяемый масштаб

Польза от работы с масштабом не ограничивается только остатком возможностей. Если d (делитель) имеет больше десятичных знаков, чем n, это требует изменения масштаба для достижения более точного результата:

$ bc <<< 'n=17.123456789; d=1.0001; scale=5; a=n%d; print a'
0.000002789 

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

4. Работа с отрицательными числами

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

$ bc <<< 'scale=0; n=-13; d=7; n%d'
-6

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

5. Применение в сложных расчётах

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

Заключение

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

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

Оцените материал
Добавить комментарий

Капча загружается...