Pull to refresh

Comments 41

Ну в Java, например, для решения этой проблемы есть BigDecimal. А вообще, проблема настолько общеизвестна и известно как решаема, что не думаю, что задержки с переходом с COBOL'а связаны именно с этим.

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

Интересно, как бы это выглядело бы в Lisp, в котором поддержка рациональных дробей есть «из коробки»?

В своё время, когда я впервые столкнулся с Lisp-ом (а именно с диалектом Scheme, но и в Comon Lisp и в Clojure, всё вроде так же), меня очень впечатлило что работа с дробями там совершенно прозрачная.

можно писать что-то типа:
(+ 1/2 1/4 1/4) и получить 1
или
(+ 1 1/5 1/10) и получить 13/10
ну и т.п.

Да и в Питоне можно (https://docs.python.org/3/library/fractions.html).
Что будет — понятно. Будет "правильно" сходиться к 5, становясь всё медленнее с каждой итерацией по мере роста числителя и знаменателя.
На самом деле, 100 для той дроби является также стационарной точкой, так что ответ не настолько неверен, как пытаются представить.


Как выглядит в Julia с рациональной арифметикой:


Заголовок спойлера
muller(y, z) = 108 - (815 - 1500 / z) / y

function muller_seq(n)
    x = [4, 17//4]
    for i = 3:n
        push!(x, muller(x[end], x[end-1]))
    end
    x
end

let ms = muller_seq(25)
    for i in 1:25
        println("$i $(ms[i])")
    end
end

1 4//1
2 17//4
3 76//17
4 353//76
5 1684//353
6 8177//1684
7 40156//8177
8 198593//40156
9 986404//198593
10 4912337//986404
11 24502636//4912337
12 122336033//24502636
13 611148724//122336033
14 3054149297//611148724
15 15265963516//3054149297
16 76315468673//15265963516
17 381534296644//76315468673
18 1907542343057//381534296644
19 9537324294796//1907542343057
20 47685459212513//9537324294796
21 238423809278164//47685459212513
22 1192108586037617//238423809278164
23 5960511549128476//1192108586037617
24 29802463602463553//5960511549128476
25 149012035582781284//29802463602463553
На самом деле это метод решения уравнений Мюллера. И находятся корни следующего уравнения:
x3 — 108x2 + 815x — 1500 = 0
У которого их три. Да, «правильный»: 5, другой как раз 100 и ещё один: 3. То есть, оба варианта вычислений дают правильное решение, если, конечно, задача состоит именно в нахождении корней. Кстати, в этом примере вариант с фиксированной точкой тоже сходится к 100 после 30 итераций. Всё зависит от числа знаков после запятой, то есть точности. Только вариант с fractions всегда будет давать 5. Так как метод Мюллера известен своими проблемами с уравнениями порядка три и выше, лучше на практике использовать более стабильные методы.
(defun rec (y z)                                                                   
  (- 108 (/ (- 815 (/ 1500 z)) y)))                                                     
                                                                                   
(defun floatpt (N)                                                                 
  (let ((x (list 4 4.25)))                                                         
    (loop :for i :from 2 :to (+ N 1) :do                                           
         (setf x (append x (list (rec (nth (- i 1) x)                              
                                      (nth (- i 2) x))))))                         
    x))                                                                            
                                                                                   
(defun fixedpt (N)                                                                 
  (let ((x (list 4 17/4)))                                                         
    (loop :for i :from 2 :to (+ N 1) :do                                           
         (setf x (append x (list (rec (nth (- i 1) x)                              
                                      (nth (- i 2) x))))))                         
    x))                                                                            
                                                                                   
                                                                                   
(let* ((max 25)                                                                    
       (flt (floatpt max))                                                         
       (fxd (fixedpt max)))                                                        
  (format t "~%")                                                                  
  (loop :for i :from 0 :to max :do                                                 
       (format t "~%~2,'0d | ~30,20f  | ~30,20f"                                   
               i (nth i flt) (float (nth i fxd)))))  

=>
00 | 4.00000000000000000000 | 4.00000000000000000000
01 | 4.25000000000000000000 | 4.25000000000000000000
02 | 4.47058870000000000000 | 4.47058800000000000000
03 | 4.64474500000000000000 | 4.64473700000000000000
04 | 4.77070600000000000000 | 4.77053830000000000000
05 | 4.85921500000000000000 | 4.85570050000000000000
06 | 4.98312400000000000000 | 4.91084770000000000000
07 | 6.39543150000000000000 | 4.94553760000000000000
08 | 27.63263000000000000000 | 4.96696300000000000000
09 | 86.99376000000000000000 | 4.98004600000000000000
10 | 99.25551000000000000000 | 4.98797940000000000000
11 | 99.96258500000000000000 | 4.99277000000000000000
12 | 99.99813000000000000000 | 4.99565600000000000000
13 | 99.99991000000000000000 | 4.99739100000000000000
14 | 100.00000000000000000000 | 4.99843400000000000000
15 | 100.00000000000000000000 | 4.99906000000000000000
16 | 100.00000000000000000000 | 4.99943600000000000000
17 | 100.00000000000000000000 | 4.99966140000000000000
18 | 100.00000000000000000000 | 4.99979700000000000000
19 | 100.00000000000000000000 | 4.99987800000000000000
20 | 100.00000000000000000000 | 4.99992700000000000000
21 | 100.00000000000000000000 | 4.99995600000000000000
22 | 100.00000000000000000000 | 4.99997400000000000000
23 | 100.00000000000000000000 | 4.99998430000000000000
24 | 100.00000000000000000000 | 4.99999050000000000000
25 | 100.00000000000000000000 | 4.99999430000000000000

А разве функция fixedpt не должна называться по-другому?

Возможно, но я стремился быть ближе к Python-версии.

Как показывает результат — CommonLisp не уступает COBOL

Если вы стремились быть ближе к Python-версии — то и использовать надо было фиксированную точку вместо рациональных чисел...

У вас не fixed-point, а рациональная аримфметика тут.
И nconc прям просится, а лучше push и nreverse.

Я в прошлом веке, чтобы уйти за 32 битные числа переписал на символьное хранение значений переменных и вместо ограничения в 2^32 расширил возможности вычисления до 10^256

Это называется "длинная арифметика". Кстати, вы зря взяли основание 10, основание 109 работает куда быстрее.

Я просто от встроенных переменных перешел на хранения цифр числа как символов, а в символьную переменную входило 256 символов.

Наверное, вы имеете в виду строковую переменную, в символьную только одна цифра влезает. Да, так можно делать, но лучше всё-таки использовать массивы и основание 109. Так можно в той же самой памяти хранить числа до 109*64 = 10576, притом это будет работать намного быстрее.

Судя по упоминанию "в символьную переменную входило 256 символов", речь идёт о Паскале.


Так вот, вместо string надо использовать array [0..63] of longint, а дальше то же самое. Только надо не забывать приводить разряды к int64 перед умножением, чтобы не было переполнения.


Если используется версия языка без int64 — надо использовать array [0..127] of integer и основание 10000.

Так я не понял, они (эти самые некие программисты) что, пытались считать финансы, используя вычисления с плавающей точкой? Ну, тогда они некомпетентны как программисты, но разве это причина не переходить на Java?

Ну, есть и пользовательский софт с такой проблемой.


Я пользуюсь одной программой учёта финансов. Платной.
В ней есть функция расчета стоимости по цене и количеству. Вводишь, скажем, объем и цену за литр топлива — и оно показывает рубли и копейки.
И как-то раз возникло у меня расхождение на копейку.
Сверил банковскую выписку и расходы — совпадает. А копейки нет.


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


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


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

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

Знаю банк, в котором есть сервисы, написанные с 80-ых еще на Коболе и выполняют все поставленные перед ними задачи. Есть сервисы на голой энтерпрайзной Джаве ранних версий, написанные лет 10-15 назад, которые, как ни странно тоже выполняют все задачи, и обладают просто феноменальным запасом по прочности. И есть часть сервисов, написанных на последних версиях Джавы и Котлина, со всеми Спринг Бутами, Хибирнейтами, современными фреймворками для фронтенда и обернутые в докер. Эти сервисы призванны обслуживать сиюминутные потребности бизнеса и спроектированные кое-как. Угадайте, где быстрее всего тухнет код? Так что не в языке дело, совсем не в языке.
UFO just landed and posted this here
Всё развивается, и рано или поздно приходится дописывать новые фишки. Системы на коболе имеют типичные проблемы легаси кода. Через какое-то время разработчики уходят, и уже никто точно не знает, как это всё работает. Новых разработчиков найти сложно. Переписать всё на современные технологии получается вдвойне сложней.

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

Вопрос со стороны, в чем проблема выучить COBOL и разобраться в применявшихся ранее практиках программирования?
UFO just landed and posted this here
Рассмотрим COBOL как часть ТЗ, почему бы нее потратить время на его изучение (а он вряд ли сильно отличается от того-же Паскаля в принципиальном плане) за счет работодателя? К тому же COBOL все-таки не аналог одежде из звериной шкуры — автор текста, по видимому, клонит к тому что принципиальным плюсом COBOL были вещественные типы с фиксированной точкой, которых нет ни в замшелом, но по прежнему эффективном Фортране (как с точки зрения написания кода для частых, но важных задач, так и с точки зрения производительности), ни в С, но которые могут быть достаточно эффективны с точки зрения производительности, а главное, незаменимы (по сути) в финансовых расчетах.
UFO just landed and posted this here
UFO just landed and posted this here
COBOL — древний язык, значит на нём кодили опытные программисты изрядного возраста, а раньше в программисты шли подготовленные кадры с математическим образованием, и таких было большинство.
Сейчас все эти новые языки программирования и фреймворки на них призваны упрощать процесс написания кода, и теперь кодить могут все кому не лень, и получаем, что большинство программистов обладает низкой квалификацией

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

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


А в банковском и государственном секторе все воют от того, что последний программист на коболе в округе умер в возрасте 93-х лет.


Неужели нельзя сделать хорошо отлаженный транспайлер для кобола (язык то не развивается, статичный) — а уж он хоть из Lua пускай транспилирует.

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

Это вообще первый самый очевидный вопрос в данном случае, но не всё так просто в крупных конторах :)


В начале нулевых 2 (ДВА) года вели переговоры с крупным территориальным подразделение МВД о таком методе перехода на что то более современное чем их глюкавое ПО под DOS…
Это не закончилось вообще ни чем — ни кто не взял на себя ответственность.


Год назад я писал ПО для анализа статистических данных и формирования отчетности из СУБД написанной на FoxPro и используемой в колониях ФСИН…
Когда то давно кто то весьма грамотно спроектировал структуру БД, но потом коллектив сменился и всё было обильно унавожено костылями типа хранения текстовой конкатенации ИНДЕКСОВ статей УК, по которым был осужден зэк, в поле основной таблицы. И это только один пример.
Понятно, что всё глючило и тормозило, но разговор сейчас не об этом. Я сразу предложил переписать всё, реализовав хотя бы просто на Access. Там довольно всё очевидно и я озвучивал сроки в месяц на написание и месяц на отладку в полубоевом режиме.
Всё всех устраивало, но вопрос так же точно подвис в воздухе и ни чем не закончился.


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

Неужели нельзя сделать хорошо отлаженный транспайлер для кобола (язык то не развивается, статичный) — а уж он хоть из Lua пускай транспилирует.

А с упомянутой в статье математикой как быть?
Поменять всю математику на вызовы специальной библиотеки?
Так какому число должно сходиться это соотношение, к 5 или к 100?
x^3+108*x^2-815*x+1500=(x-3)*(x-5)*(x-100), так что к обоим сходится.

При строго указанных начальных значениях — к 5. Но при любой погрешности в них — к 100.

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

Насколько я помню, там устойчивая точка только 100.

Ну тогда очень неудачная рекурсия, в обоих точках ( 3 и 100) производная функции больше нуля и должны себя вести одинаково.

А при чём тут вообще производная многочлена?


Если представить xi как отношение yi+1/yi, то быстро окажется что yi = a 3i + b 5i + c 100i, где коэффициенты a, b и c зависят от начальных условий. А отношение соседних членов этой последовательности в общем случае (т.е. при ненулевом c) сходится к 100.

Интересно, а как на Javascript можно решить задачку ?


const floatpt = n => {
  const x = [4, 4.25];
  for (let i = 2; i < n; ++i) {
    x.push(rec(x[i - 1], x[i - 2]));
  }
  return x;
};

const N = 20;
const flt = floatpt(N);

flt.forEach((el, index) => console.log(index, el));
Sign up to leave a comment.