Совсем недавно, кстати, рассказывая про алгоритм Краута разложения квадратной матрицы в произведение двух треугольных, столкнулся с недостатком языка Паскаль. Схема алгоритма там такая:
procedure UnsymDet (...; var fail: Boolean);
...
begin
{ некоторые вычисления }
for k := 1 to n do
begin
{ вычисление ведущего элемента x столбца k }
if abs(x) < eps then
{ надо присвоить fail := true и выйти, но как??? }
end
end.

Read more... )
Как некоторые знают, Никлас Вирт придумал язык программирования Паскаль не для того, чтобы отвлекать российских школьников от говнокодинга на «самых лучших» языках C# и Python, а для обучения программированию как науке. Судя по тому коду, что я видел у наших программистов, кодить они научились, а программировать — нет.
 
И для того, чтобы сложный синтаксис и множество операторов не отвлекали учащихся от постижения предмета, Вирт намеренно минимизировал структуру языка — включил туда только совершенно необходимые конструкции. Так же поступил и с набором стандартных функций. Зато добавил возможность конструировать пользовательские типы данных.

Однако, некоторые недостатки всё же остались. Например, в операторах if и for, while вложенной конструкцией был только один оператор, из-за чего при необходимости написать несколько приходилось заключать их в операторные скобки begin...end. А цикл until изначально допускал любую последовательность вложенных операторов. Так же в языке отсутствовал оператор досрочного выхода из цикла или процедуры — выход допускался только по достижению конца тела процедуры. А ведь есть много случаев, когда где-то глубоко внутри вложенного цикла выясняется, что решение невозможно, и надо завершить процедуру с соответствующим результатом.

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

Эти и некоторые другие недостатки были исправлены в другом изобретении Вирта — языке Модула-2. Новый язык содержал средства не только для обучения, но и для промышленного программирования. Но в то время уже широко пошел язык C (а кто помнит A и B?), и Модула-2 конкуренцию не выдержала. А язык Паскаль прочно занял нишу учебного языка. К сожалению, многие преподаватели не понимают разницу между изучением программирования и изучением языка программирования, отчего без раздумий грузят шестиклассников инкапсуляцией, наследованием и полиморфизмом на С++, когда те еще не вкурили даже линейный поиск.
 
В России создан офисный пакет "Мой офис", призванный заменить как Microsoft Office, так и Libre/Open Office. А еще у него всякие сертификаты есть от органов, что он для этих органов совершенно безопасен.
 
Замечательно! Как бы посмотреть на него? А, впрочем, чего смотреть, своё же, родное. Сразу куплю! Но вот с покупкой не сложилось. На сайте создателей можно скачать сильно кастрированную демо-версию, так называемый "Мой офис Стандартный. Домашняя версия", состоящий только из редактора и электронной таблицы. А за полноценными идите, говорят, к партнерам-реселлерам. 
 
Патрнер Softline предлагает целю линейку лицензий по разным ценам, все на очень ограниченный период времени и только для государственных, коммерческих или академических учреждений. А если у меня некоммерческая негосударственная неакадемическая организация или я вообще частное лицо? Нельзя мне "Мой офис", недостоин? И вот еще что у них написано: "Поставка ключа в электронном виде на e-mail, указанный при оформлении заказа. Срок доставки: от 3 раб.дн." Т.е. им нужно три рабочих дня, чтобы отправить e-mail. Зато могут доставить не только по e-mail, но привести целый DVD-диск. Вот это я понимю - сервис!
 
Другой партнер - Softtex - ни цены, ни вариантов лицензий не называет, а предлагает написать менеджеру, чтобы он, менеджер, с вами связался и всё обсудил.
 
Партнер "Инфотех" на сайте сие изделие нарисовал, но как его получить - загадка. Выбрать ничего нельзя, можно только спросить автоответчик. 
 
ООО "МастерСофт-ИТ" так же предлагает связаться с менеджером чтобы узнать цену.
 
Похоже, компания-разработчик совсем не заинтересована в продажах своего продукта. Меня терзают смутные сомнения, что они и так хорошо наварятся на принудительных поставках в госконторы по конским ценам.



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

Теперь напоролся на глюк в коде JPL, с помощью которого читаются файлы эфемерид серии DE (DE200, DE405 и т.п.). Вот кусок их кода (подпрограмма READHD в файле testeph1.f):

read(NRFILE,REC=1)TTL,(NAMS(K),K=1,OLDMAX),SS,NCON,AU,EMRAT,
& ((IPT(I,J),I=1,3),J=1,12),NUMDE,(IPT(I,13),I=1,3),
& (NAMS(J),J=K,NCON),
& (IPT(I,14),I=1,3),
& (IPT(I,15),I=1,3)
Тут из первой (заголовочной) записи читаются сначала имена констант
(NAMS(K),K=1,OLDMAX)
числом OLDMAX (= 400), затем разные другие величины, потом остаток имен констант
(NAMS(J),J=K,NCON)
Так получилось, потому что в старых теориях констант было мало, вот и зарезервировали в файле для них 400 мест. За ними в файле следовали прочие параметры. Когда число констант увеличилось сверх лимита (в DE430, например, их 572), решили формат файла не менять, а константы дописать в конец.

Когда я компилировал это код транслятором gfortran из семейства GCC без оптимизации, все работало как нужно. Но стоило включить оптимизацию O2, как получалась полная фигня: порядок чтения нарушался, элементы массива IPT получали неправильные значения.

Засада была вот в этом элементе списка чтения:
(NAMS(J),J=K,NCON)
Переменная K использовалась как параметр цикла в одном из предыдущих элементов этого списка. Поэтому нельзя ожидать, что по окончании цикла она будет иметь определенное значение. Без оптимизации она действительно сохраняла последнее присвоенное значение. Но оптимизирующий компилятор построил код иначе, что и привело к проблемам. Понятно даже, что именно сделал компилятор: он вычислил начальные и конечные значения параметров всех внутренних циклов до начала выполнения оператора READ, когда переменная K еще не имена конкретного значения. 

Ежу понятно, что программисту надо бы читать описание языка, прежде чем генерить год. Однако в индустрии победил подход Дональда Кнута: "мне лень про это писать, спроси у кого-нибудь, кто знает" (книга Кнута The Joy of TEX изобилует подобными советами). А сейчас еще любят учиться по роликам на Ютубе.

Короче говоря, перепишите это оператор так:
        read(NRFILE,REC=1)TTL,(NAMS(K),K=1,OLDMAX),SS,NCON,AU,EMRAT,
& ((IPT(I,J),I=1,3),J=1,12),NUMDE,(IPT(I,13),I=1,3),
& (NAMS(J),J=OLDMAX+1,NCON),
& (IPT(I,14),I=1,3),
& (IPT(I,15),I=1,3)
и будет вам счастье.

Есть у меня ноутбук. На ноубуке маленький загрузочный раздел и один большой для всего остального. В большом стоит Gentoo Linux на файловой системе reiser4. Но в мире дохрена программ, которые едут только под окошкам. Поэтому я держал VirtualBox, а на нем Windows 7. И вот внезапно узнал, что iTunes на виртуальной машине никак не может взаимодействовать по USB-кабелю со всякими яблочными девайсами. И решил, что надо поставить операционки не друг в друга, а параллельно. Сказано - сделано! Процесс смены конфигурации прошел так:

1) виндовым мастером переноса сохранил свой профиль в Windows 7 на флешку;
2) сделал резервную копию всего винта (500GB) на USB-Drive (CloneZilla);
3) загрузился с SystemRescueCd и скопировал весь корневой раздел reiser4-диска на другой USB-Drive (cp -a);
4) с помощью GParted уменьшил гентушный раздел и создал на освободившемся месте новый;
5) нарезал на уменьшенном разделе снова reiser4;
6) вернул (cp -a) на этот раздел сохраненные в п. 3 данные;
7) выкачал на другом компьютере помощью мелкомягкой утилиты дистрибутив Windows 7 и записал его на флешку;
8) загрузился с этой флешки на ноутбуке и поставил Windows в новый раздел;
9) вновь загрузился с SystemRescueCd, смонтировал reiser4-раздел, сделал туда chroot, поставил (emerge) дополнительный модуль к Grub2 для обнаружения сторонних операционок на диске;
10) установил grub в загрузочный раздел;
11) загрузился в Windows и путем правки реестра рассказал ей о том, что часы в компьютере идут по UTC;
12) мастером переноса восстановил в новой Windows свой профиль со старой;
13) заново установил в Windows нужные мне программы.

Все!

Если у вас, как и у меня, ноутбук с двойной загрузкой: линух для работы и винда, чтобы кино на iPad переписать, то, вероятно, до сих пор системные часы шли по местному времени, что создавало некоторое неудобство при переезде в другой часовой пояс. Иметь часы UTC намного удобнее, но винда, как мне казалось, такое не понимает. Оказывается, понимает. Если в реестре в разделе
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\TimeZoneInformation 
добавить ключ RealTimeIsUniversal типа DWORD и установить его в 1, то Windows будет знать, что часы идут по гринвичскому времени.


Александр Семенович Кронрод был математиком. А еще он был программистом. В начале 60-х годов он написан книжку "Беседы о программировании". Она так не понравилась другим математикам, что они запретили ее издание.

Книга была издана только в 2001 году.

Мне она тоже не понравилась.

В XXI веке человеку трудно понять, что такое "трехадресная машина", у которой есть "4096 ячеек памяти". К счастью, история вычислительной техники интересовала меня давно, и такие названия, как М-2, Наири, М-20 и Минск-32 мне прекрасно известны, равно как и архитектура и система команд этих машин. Поэтому я понял, о чем пишет Александр Семенович Кронрод.

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

С того момента, как неленивые инженеры это сделали, все программы Александра Семеновича Кронрода перестали быть интересны кому-либо, кроме самого Александра Семеновича. Потому что они стали непереносимыми. Машина, для которой они написаны, существует в единственном экземпляре, в то время как машин с системой команд типа М-20 - более тысячи. Представьте, что неленивые инженеры, вдохновленные кронродами, переделали на свой лад все эти машины. Теперь любую задачу нужно решать не один раз, а тысячу раз. "Э" - эффективность.

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

Я только что закончил ковырять очень эффективную программу для ЕС ЭВМ. В той программе очень много данных заведено операторами DATA. Так вот, для большей эффективности все целые и вещественные числа были вбиты в виде шестнадцатеричных констант. Ну, транслятору же проще транслировать именно шестнадцатеричные константы, правда?!

Как вы догадываетесь, в моем распоряжении нет ЕС ЭВМ. Есть писюк с процессором AMD. В отличие от EС ЭВМ у этого процессора архитектура little-endian: байты в многобайтных числах идут в другом порядке. Запиши авторы той программы целые числа обычным образом (десятичные), забота о размещении их в памяти легла бы на плечи компилятора. Страшно неэффективно! Поэтому выворачивать шестнадцатеричные константы пришлось мне.

В процессоре ЕС ЭВМ вещественные числа представляются не так, как в процессоре AMD. Запиши авторы той программы вещественные числа обычным образом, забота о преобразовании их в машинный формат легла бы на плечи компилятора. Но как пострадала бы эффективность! Поэтому конвертировать форматы пришлось мне, а это несколько сложнее, чем просто переставить байты.

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

Почему же имеющиеся алгоритмя врут в таких случаях? Исследование показывает, что главная причина связана с понятиями целочисленного частного и остатка. Дональд Кнут в своем фундаментальном труде "Искусство программирования" определял целочисленное частное чисел x и y как наибольшее целое, не превосходящее x/y. Иными словами, частное чисел 10 и 3 ожидаемо равно 3, но вот для чисел -10 и 3 результат должен быть -4, а вовсе не -3, потому что -4 - как раз наибольшее целое, не превосходящее -10/3=-3.333... Сообразно с этим, остаток определяется как неотрицательное число r, такое, что x=q·y+r, где q - определенное выше целочисленное частное.

Проблема в том, что популярные языки программирования этим определениям не следуют. Как правило, при вычислении частного дробная часть просто отбрасывается, и получается -10÷3=-3. Остаток тоже сплошь и рядом получается отрицательным. Из известных мне языков только Модула-2 реализует эти операции в том же смысле, как их понимает Кнут. Но стоит только корректно выразить эти операции, как отыскание календарных формул, годных для любых дат, становится простым.

Ниже приводится алгоритм вычисления юлианской даты по календарной и обратно, пригодный для любых дат. Формулы и их вывод можно найти здесь. Алгоритм написан на ныне мертвом, но очень популярном в прошлом языке Алгол-60. Для любителей живых даю две реализации: на языках Паскаль (ISO 7185) и Фортран. Для языка Паскаль нужно обратить внимание, что стандарт ISO 7185 определяет операцию mod точно в смысле Кнута, чего не скажешь про операцию div. Если ваш компилятор не следует стандарту и реализует mod иначе - выкручивайтесь самостоятельно. Любители всяких Сей и Яв, тоже пишите сами!

Вывод для тестового примера такой:
 2415020.000  1899 12 31   12  0  0.000000
 2451545.000  2000  1  1   12  0  0.000000
 -104998.829 -5000  7 12   16  6 32.870000
 3547465.171  5000  7 12   16  6 32.869994
Алгоритм )

Многие пишут программы и упускают из виду некоторые тонкости используемого языка программирования. А потом долго ломают голову, не понимая, что не работает. (Впрочем, есть люди, которые что-то пишут, вообще не используя голову, но такие именуются не программистами, а говнокодерами). Вот пример. Допустим, вы пишете на языке Паскаль такой код:

while (a[i]>0) and (x mod a[i] <> 4) do
    { что-то делать}

Вы наивно думаете, что условие в заголовке цикла будет вычисляться по краткой схеме, т.е., если a[i]=0, то, очевидно, условие ложно, и вторая часть (x mod a[i] <> 4) вычисляться не будет. Ваша уверенность основана на том, что ваш любимый компилятор именно так и делает. Но ВДРУГ выясняется, что, будучи откомпилированной в другой среде, программа подрывается как раз при вычислении второй части (я однажды встретился с таким явлением сам). Почему?

Потому что надо читать стандарт языка, а не только инструкцию к данному конкретному компилятору. Скажем, Модифицированное сообщение о популярном в прошлом языке Алгол-60 недвусмысленно требует, чтобы в любом выражении вычислялись все его составляющие, поэтому приведенный выше трюк не должен работать ни в каком случае. Стандарт языка Паскаль ISO 7185 более лоялен к разработчикам компиляторов, но совершенно нелоялен к их пользователям, т.е. к прикладным программистам. Этот документ оставляет порядок вычисления выражений целиком на усмотрение реализаторов. И если в одном случае фрагмент будет работать, а в другом - нет, то программа оказывает непереносимой. Я полагаю, что это недостаток стандарта. Но изменить его я не в силах, как не в силах заставить разработчиков компиляторов следовать какому-то одному пути. Но я в силах изменить свою программу и сделать её, хотя и ценой потери красоты и эффективности, независимой от деталей реализации.

В приведенном примере, вроде бы, напрашивается очевидное решение

while a[i] > 0 do
    if x mod a[i] <> 4 then
        { что-то делать}

но оно неправильное, поскольку нарушение второго условия должно закончить цикл, а в таком варианте не закончит. Надо извращаться так:

cont := true;
while (a[i] > 0) and cont do
    if x mod a[i] <> 4 then
        { что-то делать}
    else
        cont := false;

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

Вообще, стандартный Паскаль содержит много неудачных решений. Видел это и его создатель Никлаус Вирт. В стандарте Модулы-2 - наследницы языка Паскаль - четко сказано, что не нужно вычислять второй операнд, если значение выражения понятно из первого. Именно, там написано:

If the left operand to a logical conjunction operator has the value false , the value of the Boolean infix operation shall be the value false , and the right operand shall not be evaluated. If the left operand to a logical conjunction operator has the value true , the value of the Boolean infix operation shall be the value of the right operand.

If the left operand to a logical disjunction operator has the value true , the value of the Boolean infix operation shall be the value true , and the right operand shall not be evaluated. If the left operand to a logical disjunction operator has the value false , the value of the Boolean infix operation shall be the value of the right operand.

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

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

Поклонники Фортрана тоже не должны обольщаться. В стандарте Фортрана-2008, например, сказано (эта фраза без изменений перешла еще из Фортрана-95):

It is not necessary for a processor to evaluate all of the operands of an expression, or to evaluate entirely each operand, if the value of the expression can be determined otherwise.

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

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

Так что, предохраняйтесь, и будет вам программистское счастье!

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

Допустим, есть оператор

for p:= 3 to r do 
    if n mod p = 0 then
        <здесь нужно объявить, что число не простое и завершить цикл>;

И вот засада: оператора типа exit или return в Паскале нет, а goto использовать запрещают религиозные убеждения. Можно было бы плюнуть, поскольку в любом промышленном паскалеподобном языке exit есть, но мне хотелось, чтобы дети включили мозг и нашли таки очевидное с моей точки зрения решение. А они не смогли. :(

И еще одна беда. Паскалевский цикл for работает только с приращениями 1 или -1, в нем нет никакого step. Что за психологическая проблема мешает использовать в таком случае while, я не знаю.



Правильный ответ на загадку о нудном проверяющем таков.

1. В условии сказано, что количество чисел не превышает 200. Поэтому нужно создать массив на 200 чисел и сначала ввести их, и только потом анализировать.

2. В условии сказано, что введенные числа по модулю не превышают 10000. Поэтому нужно использовать тип не Integer, а, если программа на Паскале, тип-диапазон -10000..10000. Не знаю, что скажет проверяющий, когда узнает, что в большинстве языков такого типа нет. Даже Вирт, создавая Oberon, наследник Модулы-2, наследницы Паскаля, от него отказался.

3. В условии сказано, что суммировать нужно трехзначные числа, начинающиеся на 4. Поэтому нужно сначала выяснить, является ли очередное число трехзначным, потом найти его первую цифру и сравнить с 4. Сам проверяющий полагал, что для этого нужно перевести число в строку, посчитать количество символов, потом взять первый символ и сравнить с символом "4".

Вот так сейчас учат программировать.
Решил на нетбуке собрать LibreOffice из исходников. 27 часов! Но собрался без ошибок и работает.
Установка Gentoo Linux на маломощный ноутбук занимает несколько дней, потому что все собирается из исходных текстов. Но я не боюсь трудностей!
Чтобы не быть голословным, приведу скриншот своего экрана. ОС OpenSuSE 13.2.

Альбом: ЖЖ

Попытки восстановить работу Службы обновления Windows по советам с сайтов answers.microsoft.com и technet.microsoft.com привели к полностью неработоспособной системе. Пришлось ставить её заново. Но беда в том, что дистрибутива Windows 8.1 у меня никогда не было. Я купил компьютер 7 лет назад с предустановленной Vista. Потом апгрейдился до Windows 8, потом до 8.1. Так что, восстанавливаться пришлось с Vista. А производитель компьютера дистрибутива тоже не оставил, вместо него дал образ винчестера на DVD. При восстановлении с него убивается весь диск нафиг и разворачивается предпродажная конфигурация. Т.е., последовательность моих действий оказалась такой:

1) сохранить все свои данные (почти 800 Гб);
2) равернуть исходный образ Vindows Vista;
3) выполнить upgrade до Windows 8;
4) выполнить upgrade до Windows 8.1;
5) установить заново все приложения;
6) вернуть данные, сохраненные в п. 1.

Сейчас я закончил п. 3, что и дало мне возможность сюда написать.




Всякая программа содержит ошибки. Их время от времени исправляют. Бывает, что ошибка не в программе, а в данных. Тогда исправить нужно данные. И программа заработает. Но есть одна великая, даже величайшая, программа в мире, которая если вдруг не работает, то не заработает уже никогда. Это Windows Update.

О том, что в ней что-то сломалось, вы узнаете из лаконичного сообщения при перезагрузке: "Нам не удалось установить обновления". Вы НИКОГДА не узнаете, почему не удалось. Вы можете ходить на форумы, писать в  Tech. Support, отправлять любые логи и дампы, следовать множеству советов о том, что делать. НИ ОДИН из этих советов НЕ ПОМОЖЕТ. Разные утилиты от Microsoft часами будут пилить диск на вашем компьютере, а потом скажут, что все в порядке. Но ничего не изменится. НИКТО не скажет, что у вас сломалось и что нужно исправить. Windows Update НИКОГДА не заработает. Она так сделана. Это чудовищный монстр, живущий в недрах горы. Он глубоко. Он недоступен. Он не виден. Но если в тысяче километров от этой горы малелькая птичка колибри сядет не на тот цветок, монстр умрет. И никогда не воскреснет. Единственная возможность - распылить всю планету на атомы и собрать заново. А потом молиться, чтобы птичка, о которой вы даже не знаете и не должны знать, не перепутала свой цветок.

Вообще-то, я против сметрной казни. Но если создатетей этой чудо-программы будут медленно варить в масле на Красной площади, я приду, чтобы насладиться их криками!
В текстах программ из JPL для доступа к эфемеридам планет обнаружил такую строчку:

      INTEGER  RPT(3)    ! Pointer to number of coefficients for lunar euler angel rates

В комментариях буквально: "указатель на количество коэффициентов для производных эйлеровых ангелов". Понятно, что имели в виду не angel а angles, но все равно прикольно.
А есть ли в языке C стандартная библиотека функций для работы с вещественными числами на битовом уровне? Нужны вещи типа: узнать порядок (двоичный) числа, изменить этот порядок на заданную величину, выделить мантиссу и т.п. Я, конечно, могу сделать все это с помощью масок и сдвигов, но тогда программа станет непереносимой. Не хочу привязываться к конкретному представлению.

Любопытно, что в ISO-стандарте языка Modula-2 такие модули есть: LowReal и LowLong.

UPD. Собственно, мне нужно найти мантиссу разности двух чисел a и b, когда порядки их приведены к наибольшему из порядков чисел a,b и с. Задачу решает следующая универсальная, но неэффективная процедура comp:

int expon(double x)
{
    if (x==0.0)
        return -999;
    else
        return floor(log10(fabs(x))+1);
}

double comp(double a, double b, double c)
{
    int ae,be,ce;
    ae = expon(a); be = expon(b); ce = expon(c);
    if (ae    if (ae    return fabs(a-b)/pow(10.0,ae);
}


UPD2. Решил задачу функциями ldexp и frexp из <math.h> (спасибо [livejournal.com profile] dims12). Совершенно точно переносимо и скорее всего эффективнее, чем приведено выше.
Люди моего поколения еще помнят, что компьютеры появились не сразу в виде смартфонов и планшетов. Когда-то они были очень большие и занимали целый зал. Несколько сотен операций в секунду считалось высокой скоростью. И языки программирования - это не только Си и Visual Basic. Люди еще постарше (я - исключение) помнят такой язык Алгол-60. Число 60 в его названии - год официального опубликования описания этого языка. Причем, синтаксис его был описан с помощью формального языка БНФ - Нормальной Формы Бэкуса. В 60-е годы для этого языка создали массу трансляторов для самых разных машин. В СССР трансляторы с Алгола ТА-1М и ТА-2М работали на ЭВМ с системой команд М-20: М-220, М-222, БЭСМ-4. На советской транзисторной супер-ЭВМ было несколько трансляторов этого языка: ГДР-Алгол, БЭСМ-Алгол, система "Альфа" и др. В книгах и журналах публиковалась масса алгоритмов для решения самых разных (но большей частью вычислительных) задач на языке Алгол. Я уже упоминал книгу Уилкинсона и Райнша "Справочник алгоритмов на языке АЛГОЛ. Линейная алгебра", который содержит программы непревзойденного качества.

Алгол-60 имеет, среди прочих, особенность, которая не встречается в современных языках. Это передача параметра в процедуру по наименованию. Нынешние программисты знают два способа: по значению (единственный способ в Си) и по ссылке. Передача по имени отличается и от того, и от другого, потому что в процедуру передается не значение переменной и не указатель на место в оперативной памяти. Передача выполняется так, как будто фактический параметр подставляется в процедуру вместо формального. С непривычки трудно уловить разницу. Но вот вам пример использования такой передачи под названием "трюк Йенсена":

begin
  
  procedure p(a, b);
    integer a,b;
  begin
    for a:=1 step 1 until 10 do
      b := 0
  end p;

  integer i; integer array s[1:10];
  p(i,s[i])

end

Процедура p имеет два параметра, которые передаются по имени (в противном случае они были бы специфицированы как "value a,b;"). Первый параметр используется в качестве переменной цикла, а второму в этом цикле присваивается нулевое значение. При передача по ссылке (не говоря уже о передаче по значению) такой цикл лишен смысла. Зачем, в самом деле, 10 раз записывать в одно и то же место в памяти нуль? Но в том и фокус, что место это не одно и то же! При вызове процедуры фактическими параметрами являются i и s[i]. Способ передачи по наименованию предполагает подстановку параметров в тело процедуры. И получается, что выполняется следующий цикл:

for i:=1 step 1 until 10 do
      s[i] := 0

Согласитесь, это совсем другое дело. Кстати, хорошая задача для студентов-программистов: как можно реализовать такую передачу в трансляторе?

Но, казалось бы, время этого языка прошло. Ориентированный исключительно на вычисления, он непригоден для решения всех прочих задач. В частности, язык не содержит никаких средств для работы с символами, нет в нем и средств для создания интерфейса с пользователем. А откуда они могли взяться, если в 60-е гг. интерфейсом были устройство ввод перфокарт и АЦПУ (так тогда называли принтеры)? В то же время масса записанных на нем алгоритмов по-прежнему востребована, только перед употреблением эти алгоритмы переписываются на другие, современные языки: на тот же Си, например, или Фортран (в его нынешнем, не историческом виде).

И вот недавно я узнаю, что пациент еще не умер! Частью проекта GNU является конвертер marst с Алгола-60 на Си. Очень компактная программа прекрасно работает у меня в операционных системах на базе Linux, FreeBSD и даже под Windows в окружении CygWin. Конвертер прошел замечательный тест на зрелось - Man or Boy. А вот самый известный рабочий транслятор прошлого ТА-1М такой тест не пройдет, поскольку вообще не допускает рекурсию. С помощью marst я успешно транслировал оригинальный интегратор Булирша и Штёра и проверил некоторые недавние работы, касающиеся выявления устойчивых орбит в системе α Centauri. Если справлюсь с ленью, напишу об этом позже. :)
Вот кусок из библиотеки программ NOVAS, используемой в USNO для вычисления высокоточных эфемерид небесных тел.

*     PERFORM LAGRANGIAN INTERPOLATION FOR POSITION AND VELOCITY AT
*     EPOCH TJD
 60 TI = TJD - XJD(LMIDDL) + LMIDDL
    DO 63 J = 1, 3
        POS(J) = 0.D0
        VEL(J) = 0.D0
        DO 62 L = 1, INTPTS
            AK = ASTART + DFLOAT(L)
            P = 1.D0
            DO 61 I = 1, INTPTS
                IF ( I .EQ. L ) GO TO 61
                AI = ASTART + DFLOAT(I)
                P = P * (TI-AI) / (AK-AI)
 61         CONTINUE
        POS(J) = POS(J) + P * BPOS(LSTART+L,J)
        VEL(J) = VEL(J) + P * BVEL(LSTART+L,J)
 62     CONTINUE
 63 CONTINUE
 GO TO 99

Кусок интерполирует барицентрические координаты и скорости планеты, записанные в файле. Обратите внимание: интерполяция выполняется по формуле Лагранжа. Не только заголовок, но и сам текст об этом говорит.

Программу писал студент 1-го курса? Или сотрудники USNO в принципе не слышали про численные метода, поэтому и взяли первый, на который наткнулись в справочнике?

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

Что описывают учебники? Правильно, метод Гаусса. Некоторые еще рассказывают про схему Жордана1. Иван Петрович Мысовских в лекциях, конспект которых у меня сохранился, добавил сюда метод ортогонализации строк. Но дело даже не в том, что описывают мало методов. Дело в том, как и для чего их описывают. Вот, допустим, есть три метода. Чем один лучше другого? Молчок! Если один явно лучший, зачем тратить время на другие? Загадка! Максимум, посчитают количество операций для реализации метода. Все!

Но, дорогие мои, при вычислении на компьютере не менее важным, чем количество операций, вопросом является погрешность. Мало найти остаточный член, например, квадратурной формулы. Нужно уметь оценить реальную погрешность реального вычислительного процесса, который выполняется (неожиданно, да?) с конечным числом разрядов. Решать методом Гаусса систему 10000x10000 может быть и не так долго на современных ЭВМ, но совершенно бесполезно из-за нарастающей погрешности округления. Кто и в каком учебнике показал это? Кто предложил лучший метод?

На самом деле, эти вопросы исследованы и решены. И замечательная книга Дж. Уилкинсона "Алгебраическая проблема собственных значения" даже переведена на русский язык. Я считаю эту книгу эталоном того, как следует писать книги по численным методам. Все погрешности выявлены, проанализированы, и даны советы по их минимизации для разных типов машинных арифметик. Если вы хотите знать, как решить систему уравнений или найти собственные значения матрицы - вы узнаете это их книги Уилкинсона. Более того, автор не ограничился математическим анализом задачи. Совместно с Райншем он издал сборник конретных программ для ЭВМ, тоже переведенный, хотя и безобразно, на русский язык. Программы из этого сборника широко разошлись по миру и включены во многие известные пакеты прикладных программ, например SSP, IMSL, NAG.

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



1 Схему эту придумал Вильгельм Йордан, немецкое написание фамилии которого - Jordan - неправильно транслитерировано как Жордан. Некоторые, впрочем, думают, что он итальянец, и пишут: "Метод Жордано".

Профиль

waspagv: (Default)
DCS Foyle

March 2025

M T W T F S S
     12
3456789
10111213141516
17181920212223
242526272829 30
31      

Syndicate

RSS Atom

Style Credit

Expand Cut Tags

No cut tags
Page generated 02/07/2025 21:22
Powered by Dreamwidth Studios