:: Visual Foxpro, Foxpro for DOS
Почему округляет?
Baga
Автор

Сообщений: 540
Откуда: г. Махачкала
Дата регистрации: 03.06.2006
STORE 819338974300.0439 TO nLow
cLow = ALLTRIM(STR(nLow*10000,16,0))
clear
?cLow
Почему округляется 0439 до 0440?
cLow = ALLTRIM(STR(MTON(nLow*10000),16,0))
Тоже округляет.
Как обойти?


------------------
Багавудин Мирзаев
Ratings: 0 negative/0 positive
Re: Почему округляет?
andrewk

Сообщений: 174
Откуда: Красноярск
Дата регистрации: 15.05.2005
Потому что Фокс работает с 15-ю значащими цифрами. Насколько помню, это связано с внутренним представлением чисел с плавающей точкой. Не Фоксовым, а принципиально компьютерным. Для большей детализации нужно подключать цифровой сопроцессор, а это другая песня. Всё это говорю по памяти многолетней давности, возможно, что-то не очень так.
В твоём примере, если не считать десятичную точку, то 15 цифр будет "819338974300.043". Но после "3" идёт "9", поэтому округляет вверх. Как обойти не разбирался, поскольку не было нужно.
Ratings: 0 negative/0 positive
Re: Почему округляет?
Crispy

Сообщений: 18571
Дата регистрации: 16.05.2005
Baga
Как обойти?

А для каких целей нужно? Для каждой ситуации можно придумать какой-то свой способ, наиболее подходящий.


------------------
В действительности все иначе, чем на самом деле.
                                      (Антуан де Сент-Экзюпери)
Ratings: 0 negative/0 positive
Re: Почему округляет?
Baga
Автор

Сообщений: 540
Откуда: г. Махачкала
Дата регистрации: 03.06.2006
Спасибо, что напомнили.
Я, конечно, для своей цели вышел из положения.
STORE 819338974300.0439 TO nLow
nLow=ALLTRIM(STR(nLow,20,4))
nLow=CHRTRAN(nLow,".","")
nLow =SUBSTR(nLow,1,16)


------------------
Багавудин Мирзаев
Ratings: 0 negative/0 positive
Re: Почему округляет?
Igor Korolyov

Сообщений: 34580
Дата регистрации: 28.05.2002
Baga
Почему округляется 0439 до 0440?
Потому что таков предел точности для вещественных чисел в фоксе (да и в большинстве других систем тоже).
msdn.microsoft.com
Цитата:
Numeric precision when storing floating-point numbers in Numeric fields is limited to approximately 15 digits in Visual FoxPro. Therefore, precision more than 15 digits might be lost when converting from decimal to binary numbers, storing numbers with infinitely repeating decimal values in binary, performing multiple repeated operations, and storing numeric values in Character fields and in memory variables in binary format.
This limitation is based on the way Pentium-based processors calculate and store floating-point numbers and follows the Institute of Electrical and Electronics Engineers (IEEE) floating-point specification for manipulating floating-point numbers in binary format. This standard makes it possible for floating-point numbers to be stored in reasonable amount of space and for performing calculations more quickly.
Baga
Как обойти?
Использовать другой тип данных - например Currency позволяет хранить 19 десятичных знаков (кроме "самых больших" 19-значных - в хелпе чётко прописан диапазон значений для этого типа) без потери точности (но имеет строго фиксированную позицию десятичной точки).
Иначе - искать библиотеки работы с числами произвольной точности.


------------------
WBR, Igor
Ratings: 0 negative/0 positive
Re: Почему округляет?
Igor Korolyov

Сообщений: 34580
Дата регистрации: 28.05.2002
Канешна, именно так
STORE 819338974300.0434 TO nLow
nLow=ALLTRIM(STR(nLow,20,4))
nLow=CHRTRAN(nLow,".","")
nLow =SUBSTR(nLow,1,16)
? nLow
Давай, ищи следующий "выход".


------------------
WBR, Igor
Ratings: 0 negative/0 positive
Re: Почему округляет?
vic7tar

Сообщений: 48
Дата регистрации: 27.02.2017
Igor Korolyov
Канешна, именно так
STORE 819338974300.0434 TO nLow
nLow=ALLTRIM(STR(nLow,20,4))
nLow=CHRTRAN(nLow,".","")
nLow =SUBSTR(nLow,1,16)
? nLow
Давай, ищи следующий "выход".
И что этот код означает?
У меня выдал 8193389743000435.
Ratings: 0 negative/0 positive
Re: Почему округляет?
Igor Korolyov

Сообщений: 34580
Дата регистрации: 28.05.2002
То, что автор темы занимается ерундой, вместо того чтобы прочесть и понять/осознать документацию.


------------------
WBR, Igor
Ratings: 0 negative/0 positive
Re: Почему округляет?
vic7tar

Сообщений: 48
Дата регистрации: 27.02.2017
Месяц назад попадалось,что у всех версий, кроме 6, существует баг с этими округлениями.
Кто поправит?
Ratings: 0 negative/0 positive
Re: Почему округляет?
Baga
Автор

Сообщений: 540
Откуда: г. Махачкала
Дата регистрации: 03.06.2006
И все-таки. Что здесь происходит?
SET DECIMALS TO 4
STORE $819338974300.0434 TO nLow
nLow=VAL(ALLTRIM(STR(nLow,20,4)))
nLow= ALLTRIM(STR(nLow*10000,16,0))
clear
? nLow
Ну использовал я Currency. Но стоит мне преобразовать его в число или строку, так сразу 0434 становится 0435.
Пробовал и другие значения, некоторые меняются, а некоторые нет.
Разобраться в какой последовательности это происходит тоже не представляется возможным.
Igor Korolyov
Иначе - искать библиотеки работы с числами произвольной точности.
Это с чем едят?


------------------
Багавудин Мирзаев
Ratings: 0 negative/0 positive
Re: Почему округляет?
Igor Korolyov

Сообщений: 34580
Дата регистрации: 28.05.2002
Baga
И все-таки. Что здесь происходит?
То что и написано выше - потеря точности для вещественных чисел. Не может в стандартном double формате хранится без потерь 16- и более -значные десятичные числа. Под мантиссу (собственно "цифры") формат отводит 52 бита, плюс один неявный, итого 53 бита - что даёт место для хранения ~15.95 десятичных знаков. На практике это значит что некоторые десятичные числа из 16-ти цифр будут в этом формате терять точность в младшем знаке - что и видно из всех приводимых примеров. При этом имеют значение именно сами знаки, а вовсе не порядок числа. Т.е. с равной частотой будет "дёргаться" как 4-я цифра после запятой в числе из 12 знаков в целой части (и соответственно 4-х в дробной), так и просто младшая цифра целого числа из 16-ти цифр.
И это НЕ "округление".
Baga
Ну использовал я Currency. Но стоит мне преобразовать его в число или строку, так сразу 0434 становится 0435.
Естественно. Поэтому всё просто - НЕ преобразуй его в "просто число". Ни явно через MTON() ни неявно любым другим способом. В строку его тоже нужно преобразовывать крайне аккуратно. В частности функция STR работает с numeric (он же double) а не с currency - потому и проявляется ровно та же самая проблема.
Baga
Разобраться в какой последовательности это происходит тоже не представляется возможным.
Это на самом деле очень просто - достаточно посмотреть на бинарное представление числа (точнее "соседних" чисел). Только практического смысла в этом нет никакого. Ну и что с того что ты знаешь какие именно числа так себя ведут? Чем это тебе поможет?
Baga
Igor Korolyov
Иначе - искать библиотеки работы с числами произвольной точности.
Это с чем едят?
ru.wikipedia.org

vic7tar
Месяц назад попадалось,что у всех версий, кроме 6, существует баг с этими округлениями.
Глупости.
Во-первых это не баг, а ШТАТНОЕ поведение. Оно присутствует на всех системах использующих IEEE-ный формат представления вещественных чисел (тот же excel) - ссылка в фоксовом хелпе как раз указывает на MSKB статью описывающую сие поведение для эксела.
Во-вторых это поведение невозможно изменить. Просто разные системы чуть по разному "визуализируют" бинарную форму вещественного числа, и поэтому вполне может быть так что в одной системе (скажем excel) "неправильно" отображается число 819338974300.0434, а в другой (например VFP6) неправильно будет отображаться какое-нить 819338974300.0432
Вот и вся разница. Ни одна система оперирующая double форматом не в состоянии корректно хранить и отображать ВСЕ возможные вещественные числа из 16-ти цифр. Уж не говоря о 17-й и последующих, которые вообще ВСЕГДА теряются.


------------------
WBR, Igor
Ratings: 0 negative/0 positive
Re: Почему округляет?
vic7tar

Сообщений: 48
Дата регистрации: 27.02.2017
А это тоже штатное поведение?
?ROUND(512.9250000000,2) && =512.92
?ROUND(512.925000000,2) && =512.93
?ROUND(412.9250000000,2) && =412.93
?ROUND(412.925000000,2) && =412.93
Ratings: 0 negative/0 positive
Re: Почему округляет?
Baga
Автор

Сообщений: 540
Откуда: г. Махачкала
Дата регистрации: 03.06.2006
Igor Korolyov
В строку его тоже нужно преобразовывать крайне аккуратно.

Попробовал так
SET DECIMALS TO 4
STORE $819338974300.0434 TO nLow
nLow=CAST(nLow as varchar(17))
nLow=CHRTRAN(nLow,".","")
nLow =SUBSTR(nLow,1,16)
? nLow

Проверил все возможные значения после запятой от 0000 до 9999
Вроде все правильно, если проверяю правильно.


------------------
Багавудин Мирзаев
Ratings: 0 negative/0 positive
Re: Почему округляет?
Igor Korolyov

Сообщений: 34580
Дата регистрации: 28.05.2002
vic7tar
А это тоже штатное поведение?
?ROUND(512.9250000000,2) && =512.92
?ROUND(512.925000000,2) && =512.93
?ROUND(412.9250000000,2) && =412.93
?ROUND(412.925000000,2) && =412.93
Да, вполне, хотя и выглядит странно.
В бинарном виде в формате double НЕ БЫВАЕТ числа "точно 512.925"
? 0h+BINTOC(512.925,"BR")
? 0h+BINTOC(512.9249999999999,"BR")
Имеем типичную бесконечную/периодическую двоичную дробь. Как видишь, РАЗНЫЕ десятичные числа в памяти представлены совершенно идентично.
В фоксе есть дополнительная "нечеловеческая" логика, служащая для получения "красивых" результатов из вот таких "некрасивых" чисел. Внутренне фокс помимо самого числа хранит и его "визуальные атрибуты" - сколько десятичных знаков всего, сколько после запятой - и использует эти атрибуты при некоторых видах вычислений (поэтому, в частности ? 5*10 и ? 5.0*10.0000 выглядят на экране совсем уж по разному, хотя данные числа и представлены в памяти одинаково и "совершенно точно", без погрешностей и каких либо "округлений"). К сожалению, помимо полезной работы она же и приводит к подобным "глюкам" с тем же ROUND().


------------------
WBR, Igor
Ratings: 0 negative/0 positive
Re: Почему округляет?
Igor Korolyov

Сообщений: 34580
Дата регистрации: 28.05.2002
2 Baga
? TRANSFORM(nLow, "9999999999999.9999")

Хотя я не очень понимаю для чего нужно переводить это число в строку, да ещё и десятичную точку потом выбрасывать. Есть такое подозрение что это число, вовсе не число (а какой-то шифр/код), и потому его банально НЕ НУЖНО нигде и никогда переводить из строкового вида ни в number ни в currency...


------------------
WBR, Igor
Ratings: 0 negative/0 positive
Re: Почему округляет?
Baga
Автор

Сообщений: 540
Откуда: г. Махачкала
Дата регистрации: 03.06.2006
Igor Korolyov
Хотя я не очень понимаю для чего нужно переводить это число в строку, да ещё и десятичную точку потом выбрасывать. Есть такое подозрение что это число, вовсе не число (а какой-то шифр/код)...
Вы абсолютно правы. Это из штрих-кода получаю данные в currency, чтобы не потерять точность, так как заранее известно, что результатом должно быть символьное выражение, состоящее из 15 или 16-ти числовых знаков. Не преобразуя результат в символьную строку, невозможно его использовать, так как в штрих-коде может быть зашифровано символьное выражение в 15 заков и тогда его нужно дополнить ведущим нулем. Если бы результат не принимал бы значение в 16 символов, то и незачем было бы строить эту чехарду. Но результат может принимать 16-значное символьное выражение и без ведущего нуля, как мы рассматривали выше, и в 15 знаков, как, например, в примере ниже:
STORE 0h0200021DE06F250C5A TO binStr
cBin = Substr(binStr, 2, 8)
nLow =CTOBIN(cBin,"YS")
nLow=TRANSFORM(nLow, "9999999999999.9999")
nLow=ALLTRIM(CHRTRAN(nLow,".",""))
IF LEN(nLow)=15
nLow=PADL(nLow,16,'0')
ENDIF
clear
?nLow && 0595799728000090
Да! И вопрос: TRANSFORM всегда даст правильный результат? Насколько это надежно?


------------------
Багавудин Мирзаев




Исправлено 2 раз(а). Последнее : Baga, 11.07.17 17:11
Ratings: 0 negative/0 positive
Re: Почему округляет?
Igor Korolyov

Сообщений: 34580
Дата регистрации: 28.05.2002
Начнём с того, что 8 байт могут трактоваться как знаковое (signed) так и беззнаковое (unsigned) "очень длинное целое" (long long). При этом и записываться в памяти они могут как в прямом (big-endian), так и в обратном порядке (little-endian). Оба эти вопроса (в каком виде принято соответствующее "число") следует выяснить ДО того как что либо с ним делать.
Да, фоксовый тип currency внутренне хранится как целое 8-байтное число со знаком (т.е. как раз long long), что и позволяет использовать его для целей "визуализации" такого рода чисел - конечно, с некоторыми неудобствами (фиксированная точка после 4-х младших десятичных разрядов из их числа).

Твой код:
- Предполагает big-endian порядок записи байт (т.е. строка 0h0000000000000001 это именно 1, а не 72057594037927936 - т.е. 1*16^14). Впрочем, этот нюанс урегулируется флажком "R" при конвертации "строки" в currency.

- Предполагает что число записано как ЗНАКОВОЕ (т.е. строка 0hFFFFFFFFFFFFFFFF это -1 а не 18446744073709551615). К сожалению этот нюанс изменить НЕЛЬЗЯ. Флаг "S" просто отключает ненужное "переворачивание" старшего бита во входном массиве перед его "конвертацией" в currency. Так чтобы строка 0h0000000000000001 не превратилась в строку 0h8000000000000001 которую фокс будет считать отрицательным числом, а конкретно -9223372036854775807. Заставить фокс трактовать эти байты как unsigned long long нельзя.

- Не встретится строка 0h8000000000000000 которая формально является числом -9223372036854775808 но, к сожалению, не обрабатывается фоксом.

Плюс к тому, максимальная длина строкового представления для значений типа currency составляет 21 символ, а не 18 как у тебя в шаблоне. Формат должен быть "9999999999999999.9999" - это 19 цифр, десятичная точка и место под минус.
Извращаться с ALLTRIM и PADL совершенно не обязательно - в форматную строку можно прописать модификатор заполняющий ведущие позиции нулями "@L 9999999999999.9999". Только учти, что для положительных чисел ведущий ноль ВСЕГДА будет выводится - даже для "максимального" числа 922337203685477.5807, тогда как для отрицательных на его месте будет знак минус.

Имеет ли TRANSFORM() с указанной маской какие-либо проблемы при конвертации currency в строки я не в курсе. Я не работал с этим типом, и тем более не пытался через него получать "десятичное" представление long long чисел

P.S. Если гарантировано то что "результатом должно быть символьное выражение, состоящее из 15 или 16-ти числовых знаков", то в общем и целом код пойдёт - т.к. это заметно меньше диапазона даже "знакового" long long, т.е. на отрицательные числа мы никогда не будем попадать. Ну и размер маски вполне можно уменьшить (16 девяток и одна точка) - чтобы после удаления точки получать гарантировано 16 десятичных цифр (с ведущими нулями если включать @L, естественно). Если "вдруг" попадётся последовательность байт представляющая собой 17 или более значное long long, или 16-значное отрицательное число, то TRANSFORM выдаст кучу звёздочек, так что эту "ошибку в данных" легко будет обнаружить (для других отрицательных чисел "переполнения" не будет, и их можно просто проверять по первому символу полученной строки - там будет "-").


------------------
WBR, Igor




Исправлено 1 раз(а). Последнее : Igor Korolyov, 11.07.17 18:42
Ratings: 0 negative/1 positive
Re: Почему округляет?
Baga
Автор

Сообщений: 540
Откуда: г. Махачкала
Дата регистрации: 03.06.2006
Огромное спасибо за столь поучительный пост. Надеюсь он будет полезен не только мне.


------------------
Багавудин Мирзаев
Ratings: 0 negative/0 positive


Извините, только зарегистрированные пользователи могут оставлять сообщения в этом форуме.

On-line: 25 PaulWist vech  (Гостей: 23)

© 2000-2024 Fox Club 
Яндекс.Метрика