Форум » Clipper » Что бы это значило и как с этим бороться? » Ответить

Что бы это значило и как с этим бороться?

sergey5703: // Программа LOCAL n10 := 10, n3 := 3, n10div3, i n10div3 := (n10 / n3) FOR i := 1 TO n3 n10 := n10 - n10div3 NEXT ? n10 IF n10 == 0 ? "ноль" ELSE ? "не ноль" ENDIF ? // Результат 0.00 не ноль

Ответов - 29, стр: 1 2 All

Andrey: sergey5703 пишет: IF n10 == 0 Попробуй IF n10 == 0.0

sergey5703: Andrey пишет: Попробуй IF n10 == 0.0 Попробовал - результат не изменился!

Pasha: Это результат использования арифметики с числами double Ничего с этим не поделаешь, точность представления double ограничена. Такой же результат даст программка на С: HB_FUNC( N10_3 ) { hb_retl((10.0-10.0/3.0-10.0/3.0-10.0/3.0) == 0.0); } Вызов N10_3 даст .F. так работает FPU А лечение традиционное: вместо сравнения с нулем написать функцию: Function Equ(n1, n2) Return Abs(n1-n2) < 0.0001 точность сравнения подобрать самому по вкусу


sergey5703: Вы абсолютно правы - все дело в арифметике с плавающей точкой. Вы уж извините старого чудака - решил задать "вопрос на засыпку", хотя ответ на него я лично узнал после нескольких лет программирования на Клиппере, когда программировал задачу по распределению чего-то там на три декады месяца. В этой задаче печатался отчет и я чтобы не засорять отчет нулями вставил проверку на ноль и тогда печатал пробелы. Каково же было мое удивление когда отчет все равно печатался с нулями и что самое смешное и в отладчике тоже значение переменной - ноль, а в программе ветвление идет на "не равно нулю". Хотя должен все таки похвалить писишную арифметику и особенно арифметику с плавающей точкой в Клиппере - я знавал дипломированных программистов для которых было откровением узнать, что арифметика с плавающей точкой НЕ ТОЧНА "по определению". А решение этой проблемы простое - как мычание, мы в нашей организации после делений или умножений (с сложением и вычитанием все нормально) просто писали: n10 := VAL(STR(n10)) и ВСЕ, и больше ничего не надо - теперь НОЛЬ!

Pasha: :)

petr707: Можете попробовать для "простого решения VAL(str()) " 378.70*0.75-284.03 ?

sergey5703: // Программа LOCAL n10 := (378.70 * 0.75) - 284.03 ? n10 IF n10 == 0 ? "ноль" ELSE ? "не ноль" ENDIF ? // Результат -0.0050 не ноль // Вывод - все нормально, никакого "решения" - ни "простого" ни "сложного" не требуется, потому что выводится не ноль и условие проверки выдают "не ноль". Вы вероятно "не просекли" "фишку" - вопрос был в том, что выводился ноль, а для сравнения был не ноль.

sergey5703: // Вычисление эпсилон LOCAL n10 := 10, nEpsilon := 1, nOldEpsilon nEpsilon := (nEpsilon / n10) nOldEpsilon := nEpsilon DO WHILE (nEpsilon == VAL(STR(nEpsilon))) nOldEpsilon := nEpsilon nEpsilon := (nEpsilon / n10) ENDDO ? "Epsilon = " + ALLTRIM(STR(nOldEpsilon)) ? // Результат Epsilon = 0.01 Получается что 0.001 - выводится как 0.00, а -0.005 как -0.0050? Загадка, однако ...

sergey5703: // Вычисление эпсилон LOCAL n10 := 10, nEpsilon := -5, nOldEpsilon nEpsilon := (nEpsilon / n10) nOldEpsilon := nEpsilon DO WHILE (nEpsilon == VAL(STR(nEpsilon))) nOldEpsilon := nEpsilon nEpsilon := (nEpsilon / n10) ENDDO ? "Epsilon = " + ALLTRIM(STR(nOldEpsilon)) ? // Результат Epsilon = -0.05 Получается - никакой загадки, просто в Вашей формуле отсутствует ДЕЛЕНИЕ - в этом вся "фишка" ... Как там у А.С.Пушкина - "О сколько нам открытий чудных готовит просвещенья дух ..."

sergey5703: // Программа 1 LOCAL n10 := 10, nm5 := -5 nm5 := (nm5 / n10) nm5 := (nm5 / n10) nm5 := (nm5 / n10) ? nm5 IF nm5 == 0.0 ? "ноль" ELSE ? "не ноль" ENDIF ? // Результат -0.01 не ноль // Программа 2 LOCAL n10 := 10, nm5 := -4 nm5 := (nm5 / n10) nm5 := (nm5 / n10) nm5 := (nm5 / n10) ? nm5 IF nm5 == 0.0 ? "ноль" ELSE ? "не ноль" ENDIF ? // Результат 0.00 не ноль

Sergy: Аффтар, вместо того, чтобы выносить мозг себе и людям, замени строчку ? nm5 на ? STR(nm5,20,10) и все встанет на свои места.

TimTim: Присоединюсь к Sergy. Хочу только немного уточнить. Приведенный выше алгоритм вычисления epsilon несколько озадачил. Вообще говоря, в серьезных математических вычислениях рекомендуют вычислять так называемый epsilon по следующему алгоритму [pre2]nEpsilon := 1.0 DO WHILE ( 1.0 + nEpsilon > 1.0 ) nOldEpsilon := nEpsilon nEpsilon := nEpsilon / 2 ? nI, ",", nEpsilon ENDDO[/pre2] nOldEpsilon как раз то, что нам нужно. И несмотря на то, что оператор [pre2]? nEpsilon[/pre2] начиная с 31 итерации будет выдавать 0.000000000 цикл продолжится и все-таки закончится. Причем итераций будет проделано 53. Поскольку деление было на 2, то это означает, что на мантиссу числа выделено примерно 7 байтов, что в свою очередь соответствует 16 значащим разрядам после запятой для чисел из интервала [-1,1]. Таким образом, во всех приведенных выше якобы курьёзах заменить в операторе "? <переменная>" на "? STR( <переменная>, 19, 16 )" и все встанет на свои места. sergey5703 пишет: (с сложением и вычитанием все нормально) И здесь немного неточно. Как раз таки с вычитанием не все нормально. Именно при вычитаниях может возникнуть значительная потеря точности. Ваша первая программа тому подтверждение. Если правильно поставить печать то, n10 будет равно -0.0000000000000009. Я не поленился и провёл аналогичные расчеты на С (компилятор Borland C 5.5) и на Pascal (компилятор Borland Pascal 7.0). Вот что получилось для первого примера с вычитаниями. На Pascal описал все переменные как double и тогда оператор writeln( n10 ) выдал -8.8817841900125E-0016. Как нетрудно видеть соответствует полученному в Clipper. А вот, на С с так же описанными переменными оператор printf( "%f", n10 ) выдает 0.000000, но printf( "19.16%f", n10 ) выдает результат точно такой же как в Clipper программе. Теперь, мне кажется, стало более понятно откуда у оператора "?" уши растут. Кстати, в [x]Harbour расчеты такие же как в Clipper-е. Итак, это означает, что в Clipper/[x]Harbour все числовые переменные представляют собой числа типа Double соответствующего С-компилятора и бороться с этим не имеет смысла. Для правильности расчетов надо это учитывать.

Петр: TimTim пишет: Таким образом, во всех приведенных выше якобы курьёзах заменить в операторе "? <переменная>" на "? STR( <переменная>, 19, 16 )" и все встанет на свои места. Я думаю, тут все в пылу дискусии забыли о кoманде SET DECIMALS TO nDecimal и ее влиянии на вывод на экран. Позволю себе напомнить, что по умолчанию nDecimal = 2 и все встанет на свои места

Andrey: Петр пишет: о кoманде SET DECIMALS TO nDecimal Точно, Петр ! Я сам хотел об этом написать ...

TimTim: Безусловно, Петр, для выдачи на экран можно и так. Но параметр nDecimal равный именно 16 ставит все на свои места.

sergey5703: Спасибо большое господа, что высказали свое мнение. Все абсолютно правы, вот только в РЕАЛЬНОЙ программистской работе не всегда есть время задуматься над тем, какие числа в программе получатся, тем более при подсчете рублей.копеек. Так что в нашей организации не я один вставлял в "подозрительных" местах программы конструкцию: var := VAL(STR(var)) - лучше заранее "соломки" подстелить!

Sergy: sergey5703 пишет: только в РЕАЛЬНОЙ программистской работе не всегда есть время задуматься над тем, какие числа в программе получатся, тем более при подсчете рублей.копеек. Так что в нашей организации не я один вставлял в "подозрительных" местах программы конструкцию: var := VAL(STR(var)) - лучше заранее "соломки" подстелить! Не рекомендую категорически. Иначе при элементарном подсчете суммы накладной, если используется НДС, курс валют с точностью более одного знака после запятой, либо дробные части количества (кг, тонн и тп) - сумма не сойдется НИКОГДА.

les: Sergy пишет: Не рекомендую категорически. это понятно при таком ответе должна быть рекомендуемая стратегия

Sergy: les пишет: при таком ответе должна быть рекомендуемая стратегия 1) задуматься над тем, как, когда и какие числа в программе получаются 2) никогда не использовать усечение, тем более такое, как x:=VAL(STR(x)) 3) всегда использовать максимально возможную точность в таблицах, 3 минимум, а лучше 4-5 знаков после запятой (для руб/коп) 4) выводить результаты усечения в документах в полях "сумма", "итого" и тп - только после того, как с макс. точностью просчитан столбец или строка. вроде как прописные истины...

suv2: сто раз обсуждали)) ?0.3-(0.1+0.2) -0.000000000000000055511151231258



полная версия страницы