Форум » 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

suv2: Sergy пишет: 2) никогда не использовать усечение усечение, а именно, округление промежуточных результатов - обязательная вещь привожу пример суммируются N числовых полей с 2мя знаками после запятой на каждом шаге необходимо округлять результат до 2го знака иначе накопится ошибка в сумме

suv2: Sergy пишет: 4) выводить результаты усечения в документах в полях "сумма", "итого" и тп - только после того, как с макс. точностью просчитан столбец или строка. учитывая вышесказанное, п.4 неверен

suv2: Sergy пишет: 3) всегда использовать максимально возможную точность в таблицах, 3 минимум, а лучше 4-5 знаков после запятой (для руб/коп) а почему не 10? ))) я могу за тебя ответить. потому если не применяются правильные алгоритмы, то как недостаточная точность, так и избыточная точность приводит тем или иным проблемам. в общем, этот пункт тоже неверен. Копейки прекрасно умещаются в 2 знака, если речь идет о стоимости. И ДОЛЖНЫ умещаться в 2 знака, ибо таковы реалии бухучета. Вряд ли тебя поймут в налоговой, если ты с жаром будешь доказывать, что продал 1шт изделия за 3рубля 33.33333333333333 копейки, потому что ты их покупал 3 штуки за 10 рублей. В данном случае 1е изделие продается за 3.33, второе за 3.34, третье - за 3.33 имеем 3 шт за 10.00 продаем 1шт за RoundTo(10/3,2) == (3.33) остается 2шт и стоят они 10-3.33==6.67 второе изделие при продаже будет стоить RoundTo(6.67/2,2) == 3.335==3.34 (при округлении 5 к четному, равно как и при округлении 5 к большему) и останется одно изделие со стоимостью 3.33, которое прекрасно продастся и останется ровно НОЛЬ на суммовом остатке. А избыточная точность может например привести к тому, что на остатке будет числится 0.000001 изделия (одна миллионная штуки вместо нуля) со стоимостью -0.000000001 руб (минус одна миллиардная рубля) и средней стоимостью МИНУС тысяча рублей за штуку, что приведет к хрен знает к чему, чаще всего к коллапсу системы. Надо просто применять правильные алгоритмы.

les: suv2 пишет: имеем 3 шт за 10.00 обычно в накладной есть колонки "цена", "количество", "сумма". как тут получить 3 шт за 10.00 suv2 пишет: Надо просто применять правильные алгоритмы. согласен! Вот в Excell 2003 есть такая фишка: Сервис->Параметры->Параметры книги->Точность как на экране. Бухам нравится

suv2: les пишет: обычно в накладной есть колонки "цена", "количество", "сумма". как тут получить 3 шт за 10.00 я в курсе, и цифры там двузначные причем. и в чем вопрос?

les: suv2 пишет: и в чем вопрос? какая цена 1 штуки?

suv2: Допустим, речь о закупочной цене. Тебе их продавали как комплект, а ты его разукомплектовал и продаешь поштучо. Допустим, в Уставе прописан учет по средней. Цена одной штуки 3.333333333 (в таком виде закупочная цена будет хранится в таблице), но это вещь вспомогательная, потому что приоритетом является стоимость всех штук, она точнее по определению. В момент продажи лучше закупочная стоимость одной штуки вычисляется точно, округляется до 2х знаков и в таком виде попадает в расходный документ. На складе остается суммовой остаток 10руб за минуом округленного ухода - 6.67, средняя стоимость 1ш будет 3.335 Если речь о продажной цене, то неточностей обычно нет, потому что наценка идет от одной штуки с 2мя точно прописанными знаками. Тем не менее и там бывают случаи, когда из-за применение скидок, стоимость 1 штуки не укладывается точно в 2 знака. Ну и что, она округляется, приоритетом все равно является ИТОГО, а не ЦЕНА

AndreyZh: Добрый день! В последний год часто начались встречаться глюки с "математикой", что заставило переделать множество старых механизмов работы с числами: 1. x:=0; (х==0) даёт .f. Помогает. Empty(x) 2. много лет корректно работал механизм разделения количества на упаковки и штуки. Упаковки u := Int(количество/фасовка); Штуки = количество - (u*фасовка)... Народ стал активно использовать дробные фасовки и стало получаться, например: Фасовка = 5.4 кг. - количество 21.6 разбивает на 3 упаковки + 5.4 штуки, но - количество 27 разбивает на 5 упаковок + 0 штук. Пришлось сменить алгоритм на (помогло) Упаковки Int((количество + 0.00000001)/фасовка); Штуки = Round(количество - (u*фасовка),6) // у меня точность количеств 6 знаков 3. Новая "засада" по вопросу цены/суммы отгрузки и соответственно точности: цена 235.99 * количество 1.072 равно сумма 252.98128 при этом разные функции по разному могут округлять данное значение... Пока ограничился "предупреждением для user"..

suv2: AndreyZh пишет: 1. x:=0; (х==0) даёт .f. x == 0 даёт TRUE. что у тебя за странный клиппер? AndreyZh пишет: Упаковки u := Int(количество/фасовка); Это совершенно неверная формула. Int возвращает то, что получилось при ОТБРАСЫВАНИИ дробной части числа!!!!!! Если у тебя в результате операции деления должно получится целое число 4, но в из-за математических неточностей получилось 3.99999999999999999, то результат Int будет ТРИ, а не 4. Правильная формула для упаковок u:=round(количество/фасовка,0) Ну и кроме того, клиппер (паскаль, ассемблер, процессор) абсолютно точно делит 21.6 на 5.4 и дает РОВНО 4, без всяких там арифметических ошибок, абсолютно целое число (дамп: 00 00 00 00 00 00 10 40), так что наблюдаемые тобой факты опять не подтверждаются. Даже по потенциально неверной формуле Упаковки u := Int(количество/фасовка) мы получим 4 упаковки и ноль штук, а не 3 упаковки и 5.4 штук AndreyZh пишет: 252.98128 при этом разные функции по разному могут округлять данное значение господи, да о чем ты? что тут можно по-разному округлить?



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