К вопросу о парадигмах
Я кажусь вам академиком с большим задом,
Один, мол, я жрец поэзий непролазных.
А мне в действительности
единственное надо —
Чтоб больше поэтов хороших и разных.
В. Маяковский
«Послание пролетарским поэтам»
В статье речь пойдет о сравнении двух языков программирования на примере одной и той же задачи.
Программисты и так прекрасно осознают, что одинаковая задача в разных системах программирования будет выглядеть по-разному и для очередного подтверждения этого никакой статьи и не требуется.
Но тут сыграли роль два обстоятельства:
-
Во-первых, последнее время часто сравниваются языки, которые не так уж и отличаются друг от друга. Т.е. парадигмы этих языков схожи. Интереснее и полезнее сравнить более «разные» языки.
-
Во-вторых, автору попались на глаза два фрагмента реальных программ, выполняющих одно и то же действие, но написанные людьми, живущими буквально в разных программных мирах. Жаль упускать такой случай и не провести сравнительный анализ, тем более что сама задача простая и понятная. Кроме того, получились очень естественные условия: программисты не знали друг о друге и не соревновались между собой. Каждый написал несколько строк самым простым для себя образом, не задумываясь, идеальное ли это решение, и приступил к программированию следующих фрагментов. Специально придуманные для сравнения тесты иногда выглядят надуманными как задачи, да и сил на их эффективное кодирование тратится очень много.
Все изложенное ниже, конечно, отражает личную точку зрения автора. Только для сокращения объема статьи я не вставляю в каждое предложение «с моей точки зрения» и «по-моему», хотя они там подразумеваются.
Итак, простая и реальная задача, на примере которой сравниваются две системы программирования - это расчет циклически избыточного кода с заданным полиномом. Для тех, кто не сталкивался с подобными вещами, поясню, что это контрольные данные, которые должны совпасть с заданными при проверке и подтвердить тем самым целостность исходных данных. На заре программирования использовалась просто сумма всех кодов (контрольная сумма), сейчас же это нечто вроде остатка от деления. Например, данные каждого сектора жесткого диска «защищены» циклически избыточным кодом. В отличие от контрольной суммы, при изменении даже одного бита в исходных данных циклически избыточный код меняется очень сильно, уменьшая вероятность случайного совпадения с правильным значением.
Реализация на языке C
// Заголовочный файл экспорта имен функций
#include "crc32.h"
// Полином для расчета контрольной суммы
#define CRC32_POLYNOM 0x04C11DB7
// Таблица для расчета контрольной суммы
unsigned long crc32tab[256];
// Процедура инициализации таблицы, используемой для расчета контрольной суммы
void
init_crc32_table()
{
int i, j;
unsigned long crc_accum;
for (i = 0; i < 256; i++) {
crc_accum = ((unsigned long)i << 24);
for (j = 0; j < 8; j++) {
if (crc_accum & 0x80000000)
crc_accum = (crc_accum << 1) ^ CRC32_POLYNOM;
else
crc_accum = (crc_accum << 1);
}
crc32tab[i] = crc_accum;
}
}
// Процедура расчета контрольной суммы массива байтов длиной len
// Проверка на валидность параметра len не проводится
unsigned long
calc_crc32(unsigned char *pdata, int len)
{
unsigned long crc32 = 0xFFFFFFFF;
unsigned char k;
while (len--) {
k = ((unsigned char)(crc32 >> 24) ^ *pdata++) & 0xFF;
crc32 = (crc32 << 8) ^ crc32tab[k];
}
return (crc32);
}
Фрагмент взят именно так, как он написан в программе. Полагаю, большинству читателей понятно, как именно происходит расчет в этих нескольких строках.
А вот ниже приведен фрагмент, который, вероятно понятен старшему поколению программистов, а молодые вообще не сталкивались с таким языком и многое здесь не понятно. Но далее как раз и пойдет обсуждение и сравнение.
Реализация на языке PL/1
//----------------- ПРОЦЕДУРА ПРОВЕРКИ CRC -----------------------
ПРОВЕРКА_CRC:PROC(P_ДАННЫЕ, ДЛИНА_ДАННЫХ) RETURNS(BIT);
DCL // CRC32 ВНЕШНЯЯ ПЕРЕМЕННАЯ BIT(32)
//---- ВХОДНЫЕ ДАННЫЕ - АДРЕС И ДЛИНА ----
P_ДАННЫЕ PTR,
ДАННЫЕ (1_000_000_000) BIT(8) BASED(P_ДАННЫЕ),
ДЛИНА_ДАННЫХ FIXED(31),
//---- ВСПОМОГАТЕЛЬНЫЕ ТАБЛИЦЫ И ПЕРЕМЕННЫЕ ----
CRC32TAB (0:255) BIT(32) STATIC,
CRC_ACCUM BIT(32),
LAST_CRC BIT(32),
//---- ИНДЕКСЫ КАК СЛОВА И КАК БАЙТЫ ----
(I,K) FIXED(31),
B_I BIT(8) DEF(I),
B_K BIT(8) DEF(K);
//---- ОДНОКРАТНОЕ ЗАПОЛНЕНИЕ ТАБЛИЦЫ РАСЧЕТА CRC ----
1 DO I=0 TO 255;
CRC_ACCUM=B_I||'000000'B4;
DO K=0 TO 7;
LAST_CRC=CRC_ACCUM;
CRC_ACCUM=SUBSTR(CRC_ACCUM,2);
IF (LAST_CRC & '80000000'B4) = '80000000'B4 THEN
CRC_ACCUM=BOOL(CRC_ACCUM,CRC32_POLYM,XOR);
END K;
CRC32TAB(I)=CRC_ACCUM;
END I;
//---- СОБСТВЕННО ПРОВЕРКА CRC БЕЗ УЧЕТА СИНХРОБАЙТ ----
CRC32='FFFFFFFF'B4;
DO I=1 TO ДЛИНА_ДАННЫХ;
B_K =BOOL(SUBSTR(CRC32,1, 8),ДАННЫЕ(I), XOR);
CRC32=BOOL(SUBSTR(CRC32,9,24),CRC32TAB(K),XOR);
END I;
//---- ВЫХОДИМ С РЕЗУЛЬТАТОМ СРАВНЕНИЯ И С CRC32 ----
RETURN(CRC32='00000000'B4);
END ПРОВЕРКА_CRC;
Еще раз подчеркну, что это реальные фрагменты двух больших программ, выполняющих одно и тоже действие.
Сначала рассмотрим отличия, не являющиеся предметом данной статьи и вызванные просто разными стилями и привычками. Автор на PL/1 вставляет больше комментариев и пустых строк, а также пишет все большими буквами по его утверждению «для сохранения зрения». Если посчитать программные строки без комментариев, окажется, что в обоих фрагментах их почти одинаковое число. Также не является отличием возвращаемый результат, автор на Си просто возвращает контрольный код, а автор на PL/1 возвращает и результат сравнения и сам код (как внешнюю переменную). Очевидно, это опять зависит от стиля, а не от языка. Это же относится и к оператору сравнения. Автор на PL/1 выписывает все условие полностью:
IF (LAST_CRC & '80000000'B4) = '80000000'B4 THEN
не ограничиваясь «результат равен/не равен нулю» и считает, что эта четкость условия способствует лучшему пониманию.
Бросается в глаза, что на языке Си описаны две процедуры: инициализация таблицы init_crc32_tabl и собственно расчет calc_crc32 ,
а на языке PL/1 только одна, ПРОВЕРКА_CRC , причем утверждается, что внутри таблица заполнится только раз, а перед циклом ее заполнения стоит странный символ «1».
Это отличие также оказывается «ненастоящим», поскольку речь идет о «самодельном» (не включенном в стандарт) расширении языка.
Если его добавить в язык Си, оно будет выглядеть как 1{...};
Это действие означает одноразовое выполнение составного оператора. Такой оператор обычно и используется как сложная инициализация.
В данной задаче его использование вполне естественно и вообще это довольно частый случай, позволяющий упростить интерфейс.
А поскольку в PL/1 и цикл и составной оператор выглядят как DO ... END; добавление единицы перед циклом превращает его в одноразовый.
Реализовано это следующим образом: «префикс» 1 заменяется транслятором на системный вызов, который при своем выполнении меняет команду вызова на команду обхода составного оператора.
Замечу, что это отличается, например, от вызова конструктора класса, который выполняется каждый раз при создании экземпляра.
Кому-то может показаться неэффективным выполнение команды перехода при каждом выполнении процедуры.
Однако, как говорят в Одессе, «я вас умоляю» — один безусловный переход не даст ощутимого снижения скорости работы.
Можно считать, что все предыдущее было введением, а вот сейчас и начинается обсуждение разных парадигм.
Первое и, пожалуй, главное отличие PL/1 и Си в данной задаче в том, что в PL/1 изначально предусмотрена работа с битовыми строками как с объектами встроенного типа «BIT», а в Си разрешены логические операции с числами. Парадигма PL/1: есть битовые строки – цепочки нулей и единиц, к которым применимы все действия булевой алгебры. Эти строки можно резать, склеивать, заменять/доставать в них фрагменты. В программе на PL/1 собственно это и делается, поскольку сама задача как раз и состоит в «перемешивании» цепочек нулей и единиц. Обратите внимание, что при такой парадигме не требуется и понятие «сдвиг». А ведь даже в стандарте Си сдвиг вправо не определен: может быть, а может и не быть расширение знаковым разрядом.
При работе со строками бит не указанные разряды не меняются и обнуление их не требуется, сравните два расчета индексов:
k = ((unsigned char)(crc32 >> 24) ^ *pdata++) & 0xFF;
B_K = BOOL(SUBSTR(CRC32,1,8),ДАННЫЕ(I),XOR);
В языке Си операция «исключающее ИЛИ» входит в состав набора операций, а в языке PL/1 – нет. Вместо этого имеется встроенная функция BOOL, реализующая все 16 булевых операций (большая часть из которых, правда, довольна бессмысленна). Удобнее все же было бы и ее добавить в PL/1 к уже имеющимся И-ИЛИ-НЕ, так как на практике она довольно часто нужна (как в данной задаче).
Второе различие связано с преобразованием типов. Парадигма PL/1: если одни и те же данные должны использоваться как объекты разного типа,
то можно описать эти данные (один и тот же участок памяти) как объекты разного типа. Это реализуется оператором описания DEFINED (или DEF как в фрагменте).
В данной задаче как раз тот самый случай: индекс таблицы становится частью полинома.
Никаких преобразований типов в PL/1-программе нет вообще, точнее они неявно (или наоборот, явно, это как считать) присутствуют в описании.
В программе на Си потребовалось указать приведение к типам unsigned char и unsigned long .
Вероятно, ни к каким дополнительным командам это и не приводит, однако в PL/1-программе преобразования гарантированно исключены самой парадигмой «наложения» объектов в памяти.
Вот, собственно, и все по части парадигм в этих маленьких примерах. Остальные отличия больше обусловлены привычными стилями и традициями, чем различиями языков.
Кому-то понятнее указывать индекс массива: ДАННЫЕ(I) , кому-то ставить два знака плюса перед указателем: *data++ .
В принципе, в программах это место можно было написать наоборот: на Си использовать индекс, а в PL/1 «наложить» число на указатель и прибавлять к нему единицу.
Ну, и что дает такое сравнение? Вроде получается «ничья»: для одинаковой задачи получились программы одинакового размера и, вероятно, примерно одинаковой эффективности при разных подходах.
Однако есть один штрих, посмотрите еще раз на основные операторы вычисления на Си и на PL/1:
k = ((unsigned char)(crc32 >> 24) ^ *pdata++) & 0xFF;
crc32 = (crc32 << 8) ^ crc32tab[k];
B_K =BOOL(SUBSTR(CRC32,1, 8),ДАННЫЕ(I), XOR);
CRC32=BOOL(SUBSTR(CRC32,9,24),CRC32TAB(K),XOR);
Во втором случае больше проглядывает одинаковость действий, замаскированная в первом случае использованием операции сдвига, хотя именно сдвиг по смыслу самой задачи и не требуется. Эта одинаковость является косвенным признаком, того, что данную задачу удается все-таки яснее выразить, используя парадигмы PL/1, а не Си. И если предложить человеку, незнакомому с обоими языками, разобраться в алгоритме вычисления циклически избыточного кода, то понятие битовых строк и их фрагментов могут оказаться понятнее.
Таким образом, вполне очевидный вывод, что желательно иметь больше парадигм «хороших и разных» находит еще одно подтверждение. Хотя рассмотренная задача довольно специфическая, она реально встретилась на практике. И наличие в языке таких объектов как битовые строки, а также возможности манипулировать с одним и тем же участком памяти как с объектами разного типа выглядит более естественным при реализации.
Автор: Д.Ю.Караваев. 15.06.2019
Опубликовано: 2023.06.06, последняя правка: 2023.06.06 23:16
Отзывы
✅ 2023/06/06 23:35, Автор сайта #0
Вообще-то на фоне «инопланетных» Пролога и Хаскелла, которые действительно из других парадигм, PL/1 и Си выглядят как братья :) Подумаешь, какие-то детали отличаются. Но в общем и целом они решают задачу одинаково: последовательно, императивно.✅ 2023/06/07 04:17, Вежливый Лис #1
Хаскелла Рефала
Пролог
https://ru.wikipedia.org/wiki/Oz_(%D1%8F%D0%B7%D1%8B%D0%BA_ %D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8 %D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F)
Включает в себя всё, как невсебя.✅ 2023/06/07 05:57, kt #2
PL/1 и Си выглядят как братья :) Подумаешь, какие-то детали отличаются. Братья, но не близнецы. Си часто называют ассемблером высокого уровня. Но при этом он не имеет битовых объектов, которые однозначно отображаются на команды всех универсальных процессоров. А PL/1 — имеет. И получается, что в примере для Си обязательно требуется преобразование типов, а для PL/1 никакого преобразования нет.Но в общем и целом они решают задачу одинаково: последовательно, императивно А Вы попробуйте решить приведенную задачу не последовательно ))✅ 2023/06/07 12:18, Автор сайта #3
в примере для Си обязательно требуется преобразование типов, а для PL/1 никакого преобразования нет. Это не является причиной относить языки к разным парадигмам. Это та же самая императивная парадигма, только вид сбоку.А Вы попробуйте решить приведенную задачу не последовательно Программирование без хранения состояний — тот ещё вынос мозга. Вот поэтому и говорят, что это как чинить движок автомобиля через выхлопную трубу. Вот примерчики на Си и Хаскелле, разница между ними впечатляет.✅ 2023/06/08 00:00, alextretyak #4
ktSUBSTR(CRC32,9,24) А как это работает? Почему это равнозначно сишной записи crc32 << 8?Си часто называют ассемблером высокого уровня. Но при этом он не имеет битовых объектов, которые однозначно отображаются на команды всех универсальных процессоров. А PL/1 — имеет. Очень интересно о каких командах процессора, которых нет в Си, идёт речь? Я могу вспомнить только циклические сдвиги (ROL/ROR), а также поиск наибольшего/наименьшего значащего бита (BSR/BSF). Но они доступны посредством интринсиков _rotl/_rotr, __builtin_clz/__builtin_ctz для GCC и _BitScanReverse/_BitScanForward для MSVC. Так что, пусть и с костылями, но возможность обратиться ко всем битовым операциям в Си присутствует.
И что значит «не имеет битовых объектов»? Процессоры тоже не имеют каких-то особых битовых объектов, а только регистры. Различной разрядности и типа (общего назначения, FPU, XMM, YMM, ZMM)]. В Си это присутствуют в полном объёме (хотя требуется включать дополнительные заголовочные файлы вроде <immintrin.h> и поддержка со стороны компилятора). Отсюда:PL/1. Давно придумано размещение переменных разных типов в одном месте памяти. Как раз для довольно частых случаев, когда в одном месте нужен, например, символ, а соседнем — он же, но уже как набор бит... Мы очень часто используем этот прием и не путаемся с набором операций. А можно привести эти случаи использования такого приёма? Если не все, то хотя бы наиболее часто встречающиеся? Просто «случаи, когда в одном месте нужен символ, а соседнем — он же, но уже как набор бит» в языке Си и его производных неактуальны, т.к. в Си итак символ трактуется как целое число, к которому применимы все возможные битовые операции (в отличие от PL/1, в котором приходится так "изворачиваться" только по причине того, что битовые операции применяются только к специальному типу данных).✅ 2023/06/08 19:51, kt #5
Автор сайта Надо бы все-таки упомянуть, что статья писалась в 2011 году. А то подумают, что мы до сих пор CRC считаем. А сейчас уже и в систему команд процессора расчет CRC вводят )) это не является причиной относить языки к разным парадигмам Понятно, что есть парадигмищи и парадигмочки. Парадигмищи, например, «все есть объекты» или «у программы нет сохраняемого состояния» предназначены, на мой взгляд, для того, чтобы не дать программисту нормально и продуктивно работать. Взамен они обещают погоню за миражом волшебных вещей, которые никто не видел, типа «математического доказательства правильности программ».
Главная беда парадигмищ — стремление только к одному пути, все другое не существует. Так, якобы, легче изучить. Я уже приводил пример с кирпичами. Из них можно построить что угодно. Но если вы получили заказ на пешеходный мостик через ущелье, то вместо 4 тросов и дощечек вы будете возводить что-то вроде римского виадука, поскольку использование не кирпичей даже не обсуждается. Так же глупо все пытаться делать «из тросов и дощечек». Но иначе постоянно звучит, например, в адрес PL/1, обвинения в эклектике. А парадигмищи приводят к постоянным вопросам на форумах: ребята, а как так наследовать этот класс, чтобы большая часть свойств не наследовалась — они мне не нужны? А вообще отказаться от ООП в данной задаче не пробовали? Нет, нельзя, парадигмища не разрешает.
А парадигмочки вроде и мелочь, конечно, но могут увести в сторону не хуже парадигмищ. Например, в исходном Си не было вложенных подпрограмм. Ну почему? Ведь в компиляторе это копейки! И вот 30 лет мы гребем в сторону лямбда-исчислений и прочей ерунды, нелепо выглядящей не в функциональных языках. Или вот, объекты в виде строк нулей и единиц. Казалось бы, мелочь добавить в язык такие объекты. Нет, мы будем применять побитовые операции к числам. Или гадать, что должно получиться от 2+’2’: 4 или ’22’ ? Из-за того, что кто-то очень умный решил сэкономить и сложение и склейку строк отобразить одним символом. А в PL/1 неоднозначности нет потому, что сложение чисел и склейка строк (пусть даже в виде строк-чисел) — это разные вещи.
Главная парадигма PL/1: «эклектичность — это благо, а не недостаток». Вот смешная аналогия. Уже давно Каспаров с треском проиграл шахматной программе. По-моему «Дип Фриц» называлась. И начал обвинять в жульничестве. Дескать, когда он начал выигрывать, там подключился человек, поскольку программа резко сменила стиль игры. А, на мой взгляд, это как раз доказательство именно программы. Нет у неё никакого стиля. Она выбирала наиболее подходящий к каждому ходу. И выиграла за счет эклектичности ))
У нас были программы, работающие на одних исключениях, как Эль-76 или сама Windows. А были только расчетные, текст от Фортрана не отличишь. И все на одном языке, где эклектика всегда только помогала.
alextretyak А как это работает? Если объект битовая строка, то подстрока — это часть нулей и единиц внутри этой строки, например:dcl b1 bit(32); dcl b2 bit(24); b2=substr(b1,9,23); транслируется в команды8B1DE8000000 mov ebx,B1 C1E308 shl ebx,8 81E300FFFFFF and ebx,-256 891DEC000000 mov B2,ebx Неявно используется сдвиг, поскольку нужно «задвинуть» младшую часть строки в подстроку. Но для программиста явное использование сдвига не требуется. В PL/1 не было вообще понятия «сдвиг», он заменяется выкусыванием подстрок.О каких командах процессора идет речь? Наверное, невнятно выразился. Речь не о собственно командах процессора, а все о тех же цепочках нулей и единиц (битовых строках) длиной от 1 до 64 бит. Все логические побитовые команды AND, OR, NOT, XOR работают именно с ними. В Си от бедности приходится использовать в программе тип «число», что часто приводит к недоразумениям. В PL/1 все четко делится: целые, плавающие и десятичные числа, символьные и битовые строки. Ну и указатели и похожие на них метки-переменные и косвенные вызовы подпрограмм. Операции над всеми этими типами не смешиваются и различаются отображением. Вообще x86 разрабатывалась в 1977 году и в разделе «поддержка языков высокого уровня», похоже, был указан только PL/1. Поэтому типы PL/1 легко отображать на команды x86. Например, для символьных строк используются «цепочечные» команды с длиной в регистре ecx/rcx. Если строка в PL/1 «пустая» — нулевая длина в регистре соответствует ожиданиям от работы этих команд. А в Си сначала ещё длину нужно определить, а потом только использовать эти команды. Используя PL/1, я легче понимаю работу команд x86. А самих команд процессора в PL/1, конечно, нет. Хотя вот, исключение отладки INT 3 мы прямо пишем в программе как UNSPEC(’CC’b4);А можно привести эти случаи использования Например, вся криптография, где исходными данными обычно является шифруемый текст. И символы часто надо использовать как строки бит меняя их командой XOR и сдвигами.
Или вот, в WinAPI строки замыкаются нулем, а длины в начале нет. А в PL/1-KT строки с байтом длины в начале и без нуля в конце. Такие строки обычно называют паскалевским стилем, что для меня звучит оскорблением. Так вот, в программах накладывается одна строка на другую.dcl s1 char(*) var; // строка переменной длины dcl s2 char(*) def(s1+1); // строка постоянной длины В программе мы используем s1 в понятиях PL/1, а при обращении к WinAPI сначала дописываем в конец нольs1||=’^@’; // это запись равна s1=s1||’^@’, // где «крышка» в текстовой константе гасит 3 старших разряда символа А затем обращаемся к WinAPISetConsoleTitleA(addr(s2)); // та же строка, но уже по правилам Си. В Си символ трактуется как целое число Так вот это и плохо. В битовой строке каждый бит не зависит друг от друга. Именно так их обрабатывает процессор. В числе же биты зависят друг от друга. В PL/1 мне не надо ничего «трактовать», а можно явно и однозначно написать, что я имею в виду: символ или 8 бит. С моей точки зрения, наоборот, в Си изворачиваются, считая тип объекта то так, то этак, но не указывая явно.✅ 2023/06/09 01:36, Автор сайта #6
Парадигмищи, например, «все есть объекты» или «у программы нет сохраняемого состояния» предназначены, на мой взгляд, для того, чтобы не дать программисту нормально и продуктивно работать. Взамен они обещают погоню за миражом волшебных вещей, которые никто не видел, типа «математического доказательства правильности программ». Тема функционального программирования очень дискуссионна. Эта парадигма порою злит и раздражает непонятностью, тем что лишает привычного фундамента. Я Вас понимаю, сам проходил через это. Но если себя успокоить, то можно попытаться найти плюсы. Они есть и они существенны. В чём-то переубеждать не буду. Но если есть время, то можно попытаться проникнуться этими идеями и Вы на многое посмотрите под другим углом.как так наследовать этот класс, чтобы большая часть свойств не наследовалась — они мне не нужны? Ага, и не только свойства, но и многие методы мне не нужны! А ведь объекты в ООП — это такой монолит, что оттуда ничего нельзя убрать. Но некоторым и этого мало. Слишком мал размер этих монолитов! «А давайте перейдём на модульное программирование! У нас минимальной единицей будет модуль, а не класс. Всё будет инкапсулировано, только одни интерфейсы будут смотреть наружу. Мы сделаем программирование эффективным!» (Показать пальцем на проводников такой линии?). И после этого размер монолита вырастает. А потом удивляемся, что простейший «Hello, world» на Qt занимает 3,5 Мб.Каспаров с треском проиграл шахматной программе. По-моему «Дип Фриц» называлась. Во-первых без треска, счёт был не разгромным. Громким был сам факт выигрыша машины, а не счёт матча. Во-вторых, он назывался «Дип Блю». Он работал на технике «голубого гиганта» — IBM, поэтому так и назывался. А «Дип Фриц» уже на персоналках работал. Говорю так уверенно, потому что в моей семье есть КМС по шахматам :)начал обвинять в жульничестве Он начал требовать, чтобы ему выдали записи хода вычислений/размышлений машины, то есть отладочную информацию. И не поверил, что запись не велась. Вот что значит далёкий от ИТ человек! Запись отладочной информации сильно замедляет вычисления, поэтому и отключили. Иначе со слабой производительностью машина не смогла бы победить.В Си от бедности приходится использовать в программе тип «число», что часто приводит к недоразумениям. Почему от бедности? Просто это было максимально близко к регистрам и ячейкам памяти машины, которые были бестиповыми. И первые версии Си тоже были такие, что тип был понятием почти что факультативным. Особых недоразумений не заметил. Нет, они были и есть, но в других областях, типа выход за пределы массива и т. п. А вот побитовые операции над числами как-то не мешали. Просто биты не могут храниться отдельно, они хранятся пакетами не менее 8 штук. А значит с ними можно работать как с числами.в Си изворачиваются, считая тип объекта то так, то этак, но не указывая явно. Совсем как в ассемблере :)✅ 2023/06/09 11:24, MihalNik #7
Программирование без хранения состояний — тот ещё вынос мозга. Это не вынос мозга, это обманчивая игра слов. Ничто же не мешает по исходникам с переменной А, или неизменными промежуточными её состояниями А1, А2, А3 скомпилировать одинаковые программы. Синтаксическое таскание груды данных от функции к функции не исключает компилятору возможности оптимизаций вместо полного копирования. Где-то есть место неявным промежуточным переменным с точки зрения языка и/или компилятора и/или конкретного вычислительного устройства, а где-то нет. Где-то нет возможности из-за сложности напрямую вмешаться в алгоритм оптимизиации на уровне языка при открытом исходном коде компилятора. Где-то легко сломать совместимость с устройствами и переносимость программы.Но в общем и целом они решают задачу одинаково: последовательно, императивно. Последовательно до некоторой точности — функциональной зависимости. Ничто не мешает в компиляторах переставлять и распараллеливать действия. Пусть, например,a = b + c; d = e + f; g = a * d; Возможно упорядоченность двух первых сложений относительно друг друга избыточна, однако в рамках линейной письменности она неустранима, даже если порядок будет a, d, b, c, e, f или a, d, b, e, c, f. А дальше в зависимости от контекста может оказаться избыточной переменная g и можно было бы написатьa *= d илиd *= a И т.д. На уровне железа так часто и происходит.А Вы попробуйте решить приведенную задачу не последовательно )) И вообще записать текстом не последовательно) Но нужно различать неотвратимую упорядоченность от полезно задействованной. Например, последовательность уникальных именований значений не так нагружена, как последовательность изменения переменной. В первой, можно разрешить:g = a * d; a = b + c: d = e + f; Синтаксически и вычислительно эффективные последовательности действий вообще могут сильно отличаться друг от друга. Например, если в ЯП есть операторы уточнения областей видимости, сокращающие код без явных промежуточных переменных.А ведь объекты в ООП — это такой монолит, что оттуда ничего нельзя убрать. Конечно же можно.Но некоторым и этого мало. Слишком мал размер этих монолитов! «А давайте перейдём на модульное программирование! У нас минимальной единицей будет модуль, а не класс. Всё будет инкапсулировано, только одни интерфейсы будут смотреть наружу. Мы сделаем программирование эффективным!» (Показать пальцем на проводников такой линии?). И после этого размер монолита вырастает. Или уменьшается. А2 не даст соврать. Ох уж эти заимствования, модульность и монолитность прямо противоположные понятия, если заглянуть в словарь.А потом удивляемся, что простейший «Hello, world» на Qt занимает 3,5 Мб. Это проблемы конкретных компиляторов в конкретных языках, а не ООП и модулей. Так-то кто угодно может заявить что вот у него в языке блекджек ООП и модули, но потом оказывается, что в таком "автомобиле" нужно педали крутить для работы "кондиционера")✅ 2023/06/10 00:00, alextretyak #8
dcl b2 bit(24); b2 = substr(b1,9,23); Тут у вас b2 имеет тип bit(24), а CRC32 имеет тип bit(32). Т.е. получается, в PL/1 происходит автоматическое заполнение битовой строки нулями справа? А почему не слева? Но если так, то для чего тогда писать CRC_ACCUM=B_I||'000000'B4 вместо просто CRC_ACCUM=B_I?and ebx,-256 А для чего нужна эта команда? (Ведь после сдвига влево на 8 бит в младших разрядах итак уже будут все нули, а -256 соответствует 0xFFFF'FF00.)✅ 2023/06/10 00:43, Автор сайта #9
Ничто же не мешает по исходникам с переменной А, или неизменными промежуточными её состояниями А1, А2, А3 скомпилировать одинаковые программы. В языке Clean можно даже делать деструктивные обновления переменной при обеспечении уникальности доступа к ней. Согласен, что со скалярными переменными можно использовать промежуточные состояния. Но как быть с коллекциями? Делать промежуточную копию, в которой что-то поменялось, но остальные элементы коллекции остались неизменными? Сравнительные тесты показывают, что при большом размере коллекции Haskell на порядки медленнее Си. И проблема пока что не решена.упорядоченность двух первых сложений относительно друг друга избыточна, однако в рамках линейной письменности она неустранима Согласен. Такова традиция, которой тысячи лет.И вообще записать текстом не последовательно Осваиваем древнемонгольское вертикальное письмо, может оно поможет писать многомерные тексты :) В ассемблере «Эльбруса» с его явным параллелизмом есть форма записи, подразумевающая параллельность. Хорошую тему Вы подсказали — придумать способ записи нелинейного письма. Хотя это может оказаться просто гимнастикой для мозгов. Была критика «Эльбруса», что его явный параллелизм проигрывает неявному x86-64 и ARM.
Но не в одном параллелизме смысл функционального программирования. Лидия Васильевна Городняя отстаивает тезис, что «правильность важнее эффективности», что с функциональным программированием правильность достигается быстрее. Рекомендовала «Научные основы доказательного программирования» Ершова, который был у неё руководителем дипломного проекта.А ведь объекты в ООП — это такой монолит, что оттуда ничего нельзя убрать. Конечно же можно. В C++ все объекты класса имеют константный размер, который узнаётся функцией времени компиляции sizeof. «Убрать» — это в том числе уменьшить размер. Если уменьшить размер невозможно, то как можно говорить об «убрать»?
Возможно, в других языках объекты класса подобны ассоциативному массиву, в котором доступ к свойству обеспечивается по ключу (допустим, по имени свойства). Тогда отсутствие какого-то свойства вполне достижимо. Но в этом случае получаем язык уровня PHP или Питона. Да, и с этим можно жить, но это не наши планы.✅ 2023/06/10 09:52, MihalNik #10
В C++ все объекты класса имеют константный размер, который узнаётся функцией времени компиляции sizeof. «Убрать» — это в том числе уменьшить размер. Если уменьшить размер невозможно, то как можно говорить об «убрать»? Если компилятор какого-то языка не может выкинуть неиспользуемые части объектов/модулей/функций — это проблема компилятора, а не парадигмы.Но как быть с коллекциями? Делать промежуточную копию, в которой что-то поменялось, но остальные элементы коллекции остались неизменными? Делать промежуточную копию, в которой что-то поменялось, но остальные элементы коллекции остались неизменными? И что это меняет? Почему Вы полагаете, что старые значения ещё понадобятся? Вот есть какой-нибудь алгоритм, в нем изменяется переменная или массив, если переписать его в функциональный стиль, то предыдущие значения также никогда использованы не будут, значит, можно спокойно перезаписывать поверх. Если же нужно, то будут отдельные перменные и в программах и физически в памяти, уже независимо от стиля записи. Промежуточные копии — это вопрос конкретного алгоритма и компилятора, а не парадигмы.Но не в одном параллелизме смысл функционального программирования. Лидия Васильевна Городняя отстаивает тезис, что «правильность важнее эффективности», что с функциональным программированием правильность достигается быстрее. Да, правильность в целом важнее, но быстрота её достижения и принципиальность чаще всего некритична. Люди как-то живут с долей брака на производствах и сами постоянно совершают ошибки.✅ 2023/06/11 12:41, kt #11
Тут у вас b2 имеет тип bit(24), а CRC32 имеет тип bit(32). Т.е. получается, в PL/1 происходит автоматическое заполнение битовой строки нулями справа? А почему не слева? Потому, что нумерация бит в PL/1 слева. Поэтому, увы, логическая переменная «истина» это 80, а не 01 (( Но если так, то для чего тогда писать CRC_ACCUM=B_I||'000000'B4 вместо просто CRC_ACCUM=B_I? Потому, что человек не знал-не знал, да и забыл. Справедивости ради, операторы типа: b1=b2||'00000000'b; компилятор переводит в:8B1DEC000000 mov ebx,B2 891DE8000000 mov B1,ebx А для чего нужна эта команда? Это отрыжка от генерации выражения общего вида. Ведь можно написать, что-нибудь такое:substr(b2,3,20)=substr(b1,9,23); И оно превращается в ужас:substr(b2,3,20)=substr(b1,9,23); 8B1DE8000000 mov ebx,B1 C1E308 shl ebx,8 81E300FEFFFF and ebx,-512 8BD3 mov edx,ebx 8B2DEC000000 mov ebp,B2 BB00F0FFFF mov ebx,-4096 B102 mov cl,2 D3EB shr ebx,cl D3EA shr edx,cl 23D3 and edx,ebx F7D3 not ebx 23EB and ebp,ebx 0BEA or ebp,edx 892DEC000000 mov B2,ebp Не забудьте, что компилятор PL/1-KT это очень маленькая программа с ограниченными возможностями оптимизации. Файл PLINK64.EXE занимает 1.9 Мбайт. Вроде бы и немало. Однако кроме компилятора там есть редактор связей, редактор библиотеки, системная библиотека, интерактивный отладчик и т.п. Даже заголовочные файлы есть. Собственно компилятор занимает сейчас 328677 байт.✅ 2023/06/12 00:00, alextretyak #12
логическая переменная «истина» это 80, а не 01 Хм, а я думал, что логическая истина в PL/1 записывается как '1'b. Ведь так же можно писать, верно? '1'b и '0'b в значении истина и ложь используются в этом примере: https://rosettacode.org/wiki/100_doors#PL/I Для чего тогда нужно 80?substr(b2,3,20)=substr(b1,9,23); А как можно битовой строке длиной в 20 бит присвоить строку длиной 23 бита? Возьмутся первые 20 бит или при этом произойдёт расширение/удлинение b2?Во втором случае больше проглядывает одинаковость действий, замаскированная в первом случае использованием операции сдвига, хотя именно сдвиг по смыслу самой задачи и не требуется. Эта одинаковость является косвенным признаком, того, что данную задачу удается все-таки яснее выразить, используя парадигмы PL/1, а не Си. ...понятие битовых строк и их фрагментов могут оказаться понятнее. Соглашусь с таким утверждением. Поэтому решил добавить в 11l метод bits() для целочисленных типов, который принимает аргумент-диапазон, которые [диапазоны] в 11l бывают на любой вкус:var i = 1234'ABCD // шестнадцатеричное число (0x1234ABCD в Си) print(hex(i.bits(24..))) // выведет биты, начиная с 24-го (выведет 12), // биты нумеруются справа [начиная с 0] print(hex(i.bits(4..11))) // выведет биты с 4-го по 11-й включительно // (выведет BC) print(hex(i.bits(4.<12))) // выведет биты с 4-го до 12-го не включая последний // (вывод такой же: BC) print(hex(i.bits(4.+8))) // выведет 8 бит начиная с 4-го (вывод такой же) В связи с этим, если у уважаемого kt найдутся ещё какие-то стоящие "фишки"/полезные возможности PL/1, о которых мало кто знает и/или которых нет в C/C++/Rust/Python, буду рад о них услышать, дабы утащить их в свой язык. :)(:✅ 2023/06/12 09:29, kt #13
Хм, а я думал, что логическая истина в PL/1 записывается как '1'b. Разумеется. Но для логической переменной bit(1) компилятор выделяет байт, а значащий разряд — первый слева. Поэтому 80, а не 00. Иногда это приводит к лишним сдвигам в коде, особенно после сравнения и использования команд x86 типа SETxxВозьмутся первые 20 бит Да, по правилам языка. Уже давно была написано сводка всяких мелких исправлений в PL/1. Вы её не видели?✅ 2023/06/12 23:14, Автор сайта #14
MihalNik. Если компилятор какого-то языка не может выкинуть неиспользуемые части объектов/модулей/функций — это проблема компилятора, а не парадигмы. Компиляторы C++ от Microsoft, Intel, GCC не могут выкинуть неиспользуемые поля из объекта класса. Но не потому, что компиляторы плохие. А потому что язык такой. В нём объект класса имеет постоянный размер. И точка. А то мы придём к выводу, что «медицина у нас плохая, а вот врачи хорошие».Да, правильность в целом важнее, но быстрота её достижения и принципиальность чаще всего некритична. Люди как-то живут с долей брака на производствах и сами постоянно совершают ошибки. Да, людей не переделать, но технику-то можно. Меня бесит некачественное ПО. И мне кажется, это что бесит всех. Но нет, оказывается не всех. Казалось бы, «отремонтируй» программу, и она в этом месте больше никогда не сломается — в отличие от материального мира. Ан нет, не ремонтируют. К примеру, никогда не сталкивался с тем, что зависает или глючит Notepad или MSPaint. А вот MS SQL — запросто. Типа надёжность «не критична». А пипл хавает, законы у нас разрешают «as is». alextretyak если у уважаемого kt найдутся ещё какие-то стоящие "фишки"/полезные возможности PL/1, о которых мало кто знает и/или которых нет в C/C++/Rust/Python, буду рад о них услышать, дабы утащить их в свой язык. Хотя спрашивали не меня, попробую назвать то, на чём остановилось внимание.- Типы в инженерных задачах. Но я не созрел, чтобы это как-то применить у себя.
- Почему в PL/1 не зарезервированы ключевые слова — польза в том, что при развитии языка новые ключевые слова не вступают в противоречия с идентификаторами в старых программах. Это накладывает некоторые ограничения на язык, и мне пришлось выбирать — то ли это, то ли что-то другое. Язык — это всегда компромиссы. У Вас же нет цикла с проверкой условия внизу из-за питоновского синтаксиса?
- Насчёт «утащить»: битность чисел почти как в PL/1.
Первые два пункта уникальны, насколько мне известно.✅ 2023/06/13 09:24, MihalNik #15
Компиляторы C++ от Microsoft, Intel, GCC не могут выкинуть неиспользуемые поля из объекта класса. Но не потому, что компиляторы плохие. А потому что язык такой. В нём объект класса имеет постоянный размер. И точка. И что? Если в структуре не используется часть полей или часть функций, то размер используемых частей также останется постоянным.Да, людей не переделать, но технику-то можно. Меня бесит некачественное ПО. И мне кажется, это что бесит всех. Но нет, оказывается не всех. Думается, всех. Но почти всегда от использования чрезмерно бесящего ПО можно отказаться. Даже работу поменять. И также с техникой — почти всегда можно отказаться/заменить или сузить/изменить способ применения, продать/обменять, а вот переделать — очень редко. Потому что сложность и несовместимость. Поэтому же ПО постоянно делают с нуля.К примеру, никогда не сталкивался с тем, что зависает или глючит Notepad или MSPaint. А я сталкивался, что это меняет?А пипл хавает, законы у нас разрешают «as is» "As is" разрешают не законы, а потребители сами себе. В одежде, еде, общении, транспорте, ЖКХ... Это не какое-то особое свойство ПО, а повседневная реальность. По-русски называется "и так сойдет".✅ 2023/06/13 17:35, Клихальт #16
Компиляторы C++ от Microsoft, Intel, GCC не могут выкинуть неиспользуемые поля из объекта класса. Но не потому, что компиляторы плохие. А потому что язык такой. В нём объект класса имеет постоянный размер. И точка. А то мы придём к выводу, что «медицина у нас плохая, а вот врачи хорошие». Очень интересно, а почему тогда, насколько я помню, почившие в бозе компиляторы TopSpeed это могли делать?✅ 2023/06/13 19:38, kt #17
"фишки"/полезные возможности PL/1, о которых мало кто знает и/или которых нет в C/C++/Rust/Python Попалась тут мне статья Грайдена нашего Хоара, одного из разработчиков Раста: "Rust моей мечты — несостоявшийся язык" (https://habr.com/ru/articles/741124/), где он плачется, что теперь многие вещи сделал бы не так. Поскольку в теме Раста я нахожусь между категориями "без понятия" и "без малейшего понятия", я ничего не понял. Сплошные "сепульки в сепулярии". Комментаторы, правда ругают перевод и терминологию. Но один абзац я понял. Он хотел бы ввести точные числа с десятичной дробной частью как встроенные в язык тип.Отсутствие десятичных чисел с плавающей точкой. Это уже мелочь, но, в принципе, любому языку приходится проделать долгий путь к пониманию специфики финансовой математики. Рано или поздно в этот язык добавляется десятичный тип. Я бы хотел, чтобы в Rust это было сделано заранее, но эту возможность вечно откладывали, полагаясь на библиотеки. Несколько таких библиотек есть, но я предпочёл бы, чтобы такой тип был встроенным (чтобы можно было пользоваться литералами, обеспечить интероперабельность и так далее). 60 лет назад это было очевидно, что, например, двоично-десятичное представление — отличная база для финансовых расчетов и она должна быть встроена в язык. В 1978 году в x86 её сделали с аппаратной поддержкой. Но подражать PL/1 в Си не захотели (ага, "Если тебе нужен PL/1, ты знаешь, где его взять").
Интересно, что одна из моих старых заметок на эту тему (https://habr.com/ru/articles/535404/) набирает 20-30 новых просмотров в неделю, заметно больше остальных старых заметок.✅ 2023/06/15 00:00, alextretyak #18
Уже давно была написано сводка всяких мелких исправлений в PL/1. Вы её не видели? Возможно видел, но внимательно не читал. Сейчас прочитал более вдумчиво, но, увы, не нашёл ничего соответствующего моему запросу.Типы в инженерных задачах. Но я не созрел, чтобы это как-то применить у себя. Я тоже. Хотя вот в C++ 14 появились единицы измерения для интервалов времени, чтобы можно было писать 3h + 20min + 45s , но тут я решил пойти по пути Python и в 11l запись получается длиннее: TimeDelta(hours' 3, minutes' 20, seconds' 45) , но зачастую последняя запись более наглядна: TimeDelta(hours' h) хоть и длиннее, но выглядит лучше, чем h * 1h .Почему в PL/1 не зарезервированы ключевые слова — польза в том, что при развитии языка новые ключевые слова не вступают в противоречия с идентификаторами в старых программах. В современных языках программирования эта проблема решается контекстными ключевыми словами (смотри документацию C# или soft keywords в Котлине). В том же Python новые ключевые слова match и case являются soft keywords.
Впрочем, незарезервированные ключевые слова могут быть полезны для программ, которые являются переводом с других языков программирования. В программах на других языках могут встречаться идентификаторы, являющиеся ключевыми словами в целевом языке. Если бы я знал о такой особенности PL/1 до начала реализации своего языка, то вполне вероятно, что я бы подумал о том, как сделать многие ключевые слова контекстными, то есть незарезервированными. Но на данном этапе проводить рефакторинг синтаксического анализатора как-то неохота.Насчёт «утащить»: битность чисел почти как в PL/1. Да, я думал на тему того, чтобы разрешить типы вроде Int24 или даже Int23, но пока не могу определиться с реализацией. А вообще, подход Си мне нравится больше, чем подход PL/1: битовые поля, хотя и довольно неуклюжи, но хорошо ложатся на реальную аппаратуру и понятно как реализуются.✅ 2023/06/15 23:25, Автор сайта #19
MihalNik: "As is" разрешают не законы, а потребители сами себе. В какой-то мере Вы правы. Сперва потребители, они же избиратели, формируют законодательную власть, а потом эта власть не может распространить свои законы (которые уже работают для других товаров) на ПО. Разработчики перекладывают свои расходы на потребителей. Бизнес-потребители вынуждены содержать армию сисадминов, чтобы не встало производство, чтобы не задохнулись бизнес-процессы. Разработчики могли бы понести дополнительные расходы на доведение ума на ПО. Но оно им надо? Пипл и так хавает.
Клихальт: Компиляторы C++ не могут выкинуть что-то ненужное из классов (да и других структур данных) по той причине, что они не могут отследить все обращения к объектам программы и на основании этого сделать выводы об их использовании или неиспользовании. Просто в языке много неявных и «грязных» способов обратиться к чужой памяти. Эти способы вполне законны с точки зрения стандарта языка. Например, законны отрицательные индексы массивов. Законны указатели с произвольным адресом:char* x = (char*) 256; int* y = (int*) random(); Это роднит Си с ассемблером (кстати, кто-то слышал об оптимизирующих компиляторах ассемблерного текста?), считается что программист знает, что делает, а компилятор получает указание «делай то, что тебе говорят».
Как можно надеяться на правильность оптимизации, что какой-то член класса можно удалить, если объекты этого класса используются в другом модуле, который связан с этим модулем линковщиком? Ведь при линковке утеряна масса информации.
Или возьмём массив объектов. Сколько будет выделено памяти под него? Количество элементов умножить на sizeof(object). А можно ли меньше, если какие-то объекты — элементы массива — используются не на 100%, а допустим на 50? А потом передадим адрес массива в какую-то функцию? А потом как обратимся к N-му элементу массива. Как компилятор вычислит адрес элемента? Наверное, как адрес начала + N * sizeof(object)?
Нет, это реально, безошибочная оптимизация будет слишком сложна для компилятора.kt: двоично-десятичное представление — отличная база для финансовых расчетов и она должна быть встроена в язык. Неоднократно видел, как на кассе было посчитано, что надо заплатить N рублей и после этого делается платёж на эту сумму. Но потом выдаётся, что оплачена не вся покупка, надо доплатить ещё 0,00 руб. После платежа на 0,00 руб. чек закрывается. Это 5 копеек в дискуссию с MihalNik о качестве ПО. «Как хороши, как свежи были розги» для таких разработчиков, но времена не благоволят розгам. Наверно, наказание деньгами сделает ПО лучше.
В принципе, числам всё равно, как они будут посчитаны. Лишь бы правильно. Двоично-десятичное представление помогает избежать ошибок, но не всех. Можно и другими способами добиться правильных расчётов, надо лишь только иметь к этому желание. Но его зачастую нет: эти 0,00 руб. наблюдаю несколько лет.
alextretyak: Мягкие (контекстные) ключевые слова — хорошая идея. Но отслеживание контекста конечно усложняет анализ. Но раз на этот идут, то игра стоит свеч.подход Си мне нравится больше, чем подход PL/1 Создатели Си имели возможность подсмотреть, как сделано в PL/1, а вот обратное неверно. «На ашипках учацца». За 60 лет с момента создания PL/1 число программистов увеличилось на несколько порядков. И совокупный опыт тоже.✅ 2023/06/16 18:55, kt #20
За 60 лет с момента создания PL/1 число программистов увеличилось на несколько порядков. И совокупный опыт тоже. В случае финансовых расчетов, похоже, наоборот, опыт утерян. И PL/1 ни причём, ведь все это перенесено из Кобола. Программы на котором работают до сих пор, замечу. А создатели современных языков, например, C#, лепят свое. Я ещё по комментариям в RSDN осознал, что большинство вообще не в теме: "будем хранить все в копейках и — ура — задача решена!". Ага, и "землекопа полтора, отдыхать теперь пора".
Ладно ещё платеж на 00 руб. У меня на картинке в статье про числа лимит от щедрого МТС — 1 копейка (на самом деле нет лимита, это округление). Получается прямо нарушение закона сохранения: что-то из ничего. А ведь в таких случаях начинают проценты или пени капать. И уже ничего смешного ((✅ 2023/06/22 17:38, Бурановский дедушка #21
Двоично-десятичная арифметика тоже не даёт гарантий от ошибок округления. Разница между двоичными дробями и десятичными в том, что у первых делитель — сплошные двойки, а у вторых — двойки и пятёрки. Чем больше разных множителей в делителе (2, 3, 5, 7, 11, 13 и т.д.), тем реже ошибки округления.это перенесено из Кобола Была история, что какой-то программист в банке (наверняка на Коболе!) при начислении процентов на вклады «брал без спросу» остатки от деления (доли цента) и записывал их на свой счёт. Заработал немало, но погорел на неуплате налогов.✅ 2023/06/22 21:56, Вежливый Лис #22
Про ограбление банка на округлениях было в какой-то книге автора Harry Harrison из цикла The Stainless Steel Rat — https://ru.wikipedia.org/wiki/ %D0%A1%D1%82%D0%B0%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F _%D0%9A%D1%80%D1%8B%D1%81%D0%B0 я уж не буду перечитывать и искать, какой год 1981 или 1957.✅ 2023/06/23 18:40, kt #23
Двоично-десятичная арифметика тоже не даёт гарантий от ошибок округления. Никто не дает, но дело в том, что в финансовых расчетах важны проценты, сложные проценты и т.д. Именно поэтому так важны десятичные дроби и их поддержка.Была история Про ограбление банка на округлениях Да всё это сказки, легенды, тосты… Вот была история — в переключателе пропустили один «break» и из-за этого взломали банковскую систему. Была такая история, точно. Только в немецком криминальном сериале. Одна история на самом деле была. Программист «одноруких бандитов» в Лас-Вегасе сделал так, что если ставишь 16 (по-моему) раз подряд на одно и тоже — получаешь куш. Но кончилось дело плохо, самоубийством.✅ 2023/06/24 11:35, Бурановский дедушка #24
Да всё это сказки, легенды, тосты… Это была реальная история, которая, вероятно, и подарила сюжет писателям и режиссёрам. Это был первый (или один из первых) случай криминального использования вычислительной техники. Ещё до Кевина Митника. Ссылок у меня нет. Но нередко этот рассказ сопровождают словами «а вот у нас» и приводят в пример программиста АвтоВАЗа, который запрограммировал останов конвейера на время своего отпуска.в финансовых расчетах важны проценты, сложные проценты и т.д. Именно поэтому так важны десятичные дроби и их поддержка. Простой пример. У человека оклад 100 000 рублей, но в этом месяце он отработал не 168 часов, а 167. Тогда ему должно быть начислено 99404,7619047619 рубля. А после вычета подоходного (умножить на 0,87) на руки он должен получить 86482,1428571429. рубля. Простой пример того, как десятичные дроби накапливают ошибки вычислений. Один из способов их избежать — это иметь отдельные числитель и знаменатель (например, m/n), а операции с ними проводить по правилам арифметики:m/n * x/y = m*x / n*y m/n + x/y = (m*y + x*n) / n*y а перевод к привычной десятичной дроби проводить в последний момент, когда надо выдать «итого».✅ 2023/06/27 00:00, alextretyak #25
Один из способов их избежать — это иметь отдельные числитель и знаменатель (например, m/n), а операции с ними проводить по правилам арифметики Способ хороший, вот только например при начислении процентов на остаток на счёте числитель и знаменатель будут всё время расти до бесконечности, и число, хранимое в такой форме, будет требовать всё большего и большего объёма памяти. Вот простая программка на Python, иллюстрирующая эту проблему:import fractions x = 1000 for i in range(20): print(x) x *= (1 + fractions.Fraction(1, 100)) # x *= (1 + 1/100) Если процент на остаток начисляется один раз в год, то это, конечно, не настолько критично. Но что если потребуется начислять процент каждый день?
И я не вижу, чем двоично-десятичное представление принципиально лучше обычного float двойной точности [т.е. double в терминах Си] для хранения денег и проведения финансовых расчётов. А чтобы не выводить числа вроде 1.68999999999999E-001, достаточно просто округлять выводимое число до 6 знаков после точки/запятой (используя round(x, 6) в Python, sprintf(s, "%.6f", x) в Си или аналоги).✅ 2023/06/27 05:59, kt #26
Все дело в волшебных пузырьках округлениях. Если они пойдут "в одну сторону", то может появиться копейка из нуля, как на фото баланса МТС. В двоично-десятичном представлении нет заранее определенной длины результата. В отличии от float, здесь вы как бы считаете "столбиком на бумажке", и длина ответа зависит от операндов. Это было придумано в Коболе и последующие 60 лет финансисты не жалуются. Добавить свой отзыв
Написать автору можно на электронную почту mail(аt)compiler.su
|