LINUX.ORG.RU

[Octave] объясните поведение функции

 


0

0

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

A - данная большая длина, в которую «укладываем» неточную меньшую, которая задается диапазоном В допустимых значений: например, от 1 до 2.7 с шагом 0.01.

function equalize(A,B)
  for i = B
    quo = A / i;
    if quo - fix(quo) == 0
      disp([num2str(i), " поместится в А ", num2str(quo)," раз"]);
    endif
  endfor
endfunction

Вызываем:

> equalize(9,[2.7:-0.01:1])
2.25 поместится в А 4 раз
>
И всё! Очевидно, что неправильно, ведь на единицу все числа делятся без остатка, а единица не вывелась.

Попробуем по-другому:

> equalize(9,[3:-0.01:1])
3 поместится в А 3 раз
2.25 поместится в А 4 раз
1.8 поместится в А 5 раз
1.5 поместится в А 6 раз
1 поместится в А 9 раз
>
Удивительно, но правильно. Еще пример:
> equalize(9,[1:0.01:2.7])
1 поместится в А 9 раз
1.5 поместится в А 6 раз
1.8 поместится в А 5 раз
2.25 поместится в А 4 раз
>
Опять правильно.

В чем дело? Почему спотыкается на некоторых диапазонах? [2.8:-0.01:1] выдает 2.25 и 1, но пропускает 1.8 и 1.5

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



Последнее исправление: diode_on (всего исправлений: 1)

А вы посмотрите, что за единица и в том, и в другом случае.

octave:1> a=[2.7:-0.01:1];
octave:2> printf("%.20f\n", a(length(a)));
1.00000000000000022204
octave:3> a=[3:-0.01:1];
octave:4> printf("%.20f\n", a(length(a)));
1.00000000000000000000

Видим, что в первом случае единица - это совсем не единица. За деталями - гугли «What every computer scientist should know about floating point arithmetic»

dmitry_vk ★★★
()
Ответ на: комментарий от dmitry_vk

Ух ты ёлки! Спасибо! Думал, сойду с ума!

Я же сталкиваюсь с подобной вещью в maxima - меня там постоянно раздражает, что, например, 8.7 выводится как 8.699999999999999 и портит всю красивую формулу! Я даже гуглил по этому поводу и выяснил, что этому причина какие-то аппаратные нюансы представления чисел в процессоре, и так происходит во всем ПО, просто, например, табличные процессоры как-то обрабатывают такие ситуации, а другие программы - оставляют все по-честному. Но я не программист, так что подобные вещи для меня туманны.

гугли «What every computer scientist should know about floating point arithmetic»

Нарыл красивый серьезный док на 74 страницы. Спасибо за наводку, наверное, там написано все, что нужно по этой теме.

Можете ли сказать, есть ли какие готовые рецепты для таких случаев в контексте scilab, octave? На изучение дока сейчас нет времени, но, чувствую, придется разобраться с этим, так как про карманный калькулятор и одноразовый счет в тетрадке использую все реже.

diode_on
() автор топика
Ответ на: комментарий от diode_on

для конкретно этого случая нашел простой выход: считать в сантиметрах, а не метрах, т.е. просто умножить все на 100, превратив тем самым в целое.

diode_on
() автор топика
Ответ на: комментарий от diode_on

Введи погрешность epsilon = 0.00001, и вместо сравнения вещественных чисел на равенство, проверяй модуль разности с epsilon. Если модуль больше - числа разные, иначе одинаковые. Например, так if (abs(x - y) < epsilon) // x и y равны ... else // если не равны ...

P.S.: отучайся сравнивать вещественные числа на равенство напрямую, плохая привычка.

P.S.S.: epsilon - так называемая погрешность вычисления, при операциях с вещественными числами всегда надо вводить...

anonymous
()
Ответ на: комментарий от anonymous

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

quo = 9 / 1.00000000000000022204 = 8.9999999999999822364;

следовательно fix(quo) = 8 и с девяткой его сравнивать теперь бесполезно - никакой разумный epsilon не поможет.

замена сравнения разности целого и целой части на сравнение остатка от деления с epsilon тоже, теперь уже естесственно, ничего не дает: остаток от деления девятки на такую неправильную единицу - тот же 0.9999999999999822364

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

diode_on
() автор топика
Ответ на: комментарий от diode_on

Вместо fix стоит использовать round.

А IEEE754 применяется практически всюду, т.к. вещественная арифметика практически везде реализована по этому стандарту.

В «What every ...» как раз подробно описаны нюансы и подводные камни. Объем текста большой не потому что он так написан, а потому что нюансов много.

для конкретно этого случая нашел простой выход: считать в сантиметрах, а не метрах, т.е. просто умножить все на 100, превратив тем самым в целое.

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

Самые простые советы работы с вещественной арифметикой:

0) Если можно избежать арифметики с плавающей запятой, то следует воспользоваться этой возможностью.

1) Не использовать сравнение на равенство, а заменять его сравнением на близость: if abs(x-y) < eps

2) (как расширение 1) Не использовать вычисления, которые существенно зависят от мелких погрешностей; в частности, сравнение на равенство, округление вверх и вниз (но округление до ближайшего должно быть безопасным)

3) Не складывать/вычитать/умножать числа с существенно разными порядками размера и точности - в этом случае не хватит битов для хранения всего числа, и точность будет потеряна. Если надо сложить много разных чисел, то следует складывать уже отсортированные числа.

4) Не делать «длинных» вычислений, у которых постоянно увеличивается погрешность (например, постоянное прибавление 0.1 к числу вместо умножения 0.1 на индекс).

dmitry_vk ★★★
()
Ответ на: комментарий от dmitry_vk

Спасибо откликнувшимся, придется разбираться, что там происходит внутри на самом деле - боязно как-нибудь сесть в лужу в более серьезном случае.

diode_on
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.