Проблемы с FloatToStr()

Вопросы программирования и использования среды Lazarus.

Модератор: Модераторы

Сообщение tria » 23.08.2006 15:39:42

В общем, решил проблему "в лоб". Написал функцию, которая анализирует получившуюся строку на предмет наличия предпоследних 4 девяток или нулей:
Код: Выделить всё
function TriaFloatToStr(f:extended):string;
var s:string;
    i,j,n,LenS:integer;
    ch:char;
begin
  s:=FloatToStr(f);
  Result:=s;
  LenS:=Length(s);
  //Если длина числа меньше максимального числа знаков - значит нет ошибки округления
  If LenS<18 Then Exit;
  n:=pos(DecimalSeparator,s);
  If LenS-n>8 Then begin
    ch:=s[LenS-1];
    //Найдем первый знак, отличный от предпоследнего
    For i:=LenS-1 downto 1 do begin
      If (s[i]<>ch)and(i<>n) Then begin
        //Если девяток или нулей меньше 4, то считаем, что округлять не надо
        If Lens-i<5 Then Exit;

        If ch='9'Then begin
          Case s[i] of
          '0':s[i]:='1';
          '1':s[i]:='2';
          '2':s[i]:='3';
          '3':s[i]:='4';
          '4':s[i]:='5';
          '5':s[i]:='6';
          '6':s[i]:='7';
          '7':s[i]:='8';
          '8':s[i]:='9';
          end;
          If i>n Then begin//До десятичных - обрежем по текущую позицию
            SetLength(s,i);
          end
          Else begin//Иначе - обрежем по целые и до точки заполним нулями
            SetLength(s,n-1);
            For j:=i+1 to n-1 do s[j]:='0';
          end;
        end
        else begin//Обрежем по позицию/по целые
          If i>n Then SetLength(s,i)
          Else SetLength(s,n-1);
        end;
        Result:=s;
        Exit;
      end;
    end;
  end;
end;

По первым тестам - пока работает...
Да, функция будет работать только для вещественных double.
tria
постоялец
 
Сообщения: 401
Зарегистрирован: 03.04.2006 11:24:10

Re: Проблемы с FloatToStr()

Сообщение debi12345 » 23.08.2006 20:37:33

tria писал(а):В Делфи:
FloatToStr(-58.399999999999999)="-58.4"
В Лазаре:
FloatToStr(-58.399999999999999)="-58.399999999999999"
Собственно вопрос в следующем.
Ка сделать, чтобы в Лазаре работало так как в Делфи?

Старый FPC-баг ( или фича ? ), связанный с промежуточным расширением числа в extended-тип. Это тип намного длиннее "float", поэтому ни о каком округлении не может быть и речи ( скажите спасибо, что еще девяток не добавляет - мог бы, да разрядов не хватает ).
Зато есть другой побочный, обратный, эффект - генерация длинной последовательности из некоторых чисел вроде "99.1".
AFAIK, единственная возможность побороть этим траблы - наша любимая "Format". Никак не въеду, чем она Вам в данном случае не угодила.
Или вышеописанным способом.
Аватара пользователя
debi12345
долгожитель
 
Сообщения: 5759
Зарегистрирован: 10.05.2006 23:41:15
Откуда: Ташкент (Узбекистан)

Сообщение STAKANOV » 24.08.2006 14:54:41

tria писал(а):>STAKANOV
Сори, чего-то протормозил проверить. Код:
FloatToStr(double(-58.399999999999999))
приводит к тому же результату


Попробуй на всех типах: Real, Single , Double , Extended, Comp , Currency.

Суть идеи основана на моем предположении. что результат работы функции зависит от типа ее аргумента. А вот тип аргумента fpc и delphi возможно определяют по разному.

Прим.: а вообще за борландом водятся грешки с округлением и наооборт. :wink:
Аватара пользователя
STAKANOV
энтузиаст
 
Сообщения: 1069
Зарегистрирован: 14.05.2006 21:26:24
Откуда: Зеленоград

Сообщение Mirage » 24.08.2006 15:35:56

Странно, у меня
FloatToStr(-58.399999999999999)="-58.399999999999999"
Delphi 7
Что я делаю не так? Если вместо числа подставить переменную типа Single с таким значением, то будет тоже самое.
А вот так все нормально:
Result := FloatToStrF(Value, ffGeneral, 7, 0);
Может и в FPC прокатит?
В документации на FloatToStrF так и сказано что надо именно FloatToStrF использовать для получения минимальной длины строки.
Mirage
энтузиаст
 
Сообщения: 881
Зарегистрирован: 06.05.2005 20:29:07
Откуда: Russia

Сообщение tria » 26.08.2006 19:08:24

Mirage писал(а):Странно, у меня
FloatToStr(-58.399999999999999)="-58.399999999999999"
Delphi 7
Что я делаю не так? Если вместо числа подставить переменную типа Single с таким значением, то будет тоже самое.

Не знаю. Вы что-то не так делаете, или может результат зависит от каких-то опций.
С Single результат таким не может быть в принципе, так как Single не имеет столько значащих цифр. И если проверите (что я и сделал), то результат будет другой - -58,4000015258789.

Mirage писал(а):А вот так все нормально:
Result := FloatToStrF(Value, ffGeneral, 7, 0);
Может и в FPC прокатит?
В документации на FloatToStrF так и сказано что надо именно FloatToStrF использовать для получения минимальной длины строки.

Повторюсь где-то в 3 или 4 раз.
Я НЕ ЗНАЮ, СКОЛЬКО ЗНАКОВ ПОСЛЕ ЗАПЯТОЙ ДОЛЖНО БЫТЬ У ЧИСЛА.
tria
постоялец
 
Сообщения: 401
Зарегистрирован: 03.04.2006 11:24:10

Сообщение debi12345 » 26.08.2006 19:50:08

Повторюсь где-то в 3 или 4 раз.
Я НЕ ЗНАЮ, СКОЛЬКО ЗНАКОВ ПОСЛЕ ЗАПЯТОЙ ДОЛЖНО БЫТЬ У ЧИСЛА.

Тем не менее Вы согласны ( и хотите ? ) на округление, то есть сознательно отказываетесь от абсолютной точности - вплоть до 1-го знака после запятой.
Совсем запутали, чесслово. Уж определитесь.
Аватара пользователя
debi12345
долгожитель
 
Сообщения: 5759
Зарегистрирован: 10.05.2006 23:41:15
Откуда: Ташкент (Узбекистан)

Сообщение tria » 26.08.2006 20:03:20

STAKANOV писал(а):
Попробуй на всех типах: Real, Single , Double , Extended, Comp , Currency.

Суть идеи основана на моем предположении. что результат работы функции зависит от типа ее аргумента. А вот тип аргумента fpc и delphi возможно определяют по разному.

Прим.: а вообще за борландом водятся грешки с округлением и наооборт. :wink:


Суть проблемы в системе представления чисел.
Код:
var s:string;
f:single;
begin
f:=-58.4;
s:=FloatToStr(f);
end;
Код: Выделить всё
Дает результат -58,4000015258789.
С double и extended - -58,4.
При этом код:
var s:string;
    f:single;
    d:double;
begin
  f:=-58.4;
  d:=f;
  s:=FloatToStr(f);

Тоже даст результат -58,4000015258789.

Т.е. если ошибка уже произошла, то простым присваиванием она не будет исправлена.
Тут нужна какя-то функция преобразования из double вextended, которая бы делала не побайтное присвоение, а определяла, что число в следствии нехватки значащих цифр представлено неточно и необходимо его "подправить", чего, собственно, в Делфи похоже сделано (в FloatToStr).
tria
постоялец
 
Сообщения: 401
Зарегистрирован: 03.04.2006 11:24:10

Сообщение tria » 26.08.2006 20:17:14

debi12345 писал(а):Тем не менее Вы согласны ( и хотите ? ) на округление, то есть сознательно отказываетесь от абсолютной точности - вплоть до 1-го знака после запятой.
Совсем запутали, чесслово. Уж определитесь.


Попробую разъяснить еще раз.
Не все вещественные числа могут быть точно представлены с помощью типа single или double. Простой пример:
Код: Выделить всё
var s:string;
f:single;
begin
f:=-58.4;
s:=FloatToStr(f);

Дает результат -58,4000015258789.
Похожие примеры можно привести и для Double,и для Extended.

А теперь представьте. Пользователь вводи формулу, нажимает
"вычислить" и вместо ожидаемых -58,4 получает -58,4000015258789.
После этого он берет калькулятор, пересчитывает, получает -58,4 и посылает мою программу куду подальше.
Какое пользователю дело до проблем представления вещественных чисел?
tria
постоялец
 
Сообщения: 401
Зарегистрирован: 03.04.2006 11:24:10

Сообщение STAKANOV » 26.08.2006 23:47:14

tria писал(а):После этого он берет калькулятор, пересчитывает, получает -58,4 и посылает мою программу куду подальше.
Какое пользователю дело до проблем представления вещественных чисел?

это не решаемая проблема :cry: тонкости двоичной арифметики :cry: 0.05 в десятичной - это 0.000011(0011) в двоичной, т.е. периодическая дробь, об этом рекомендую рассказать заказчику с калькулятором (калькулятор работает не в двоичной системе), весь мир с этим смирился и он пусть тоже :wink:

в дельфи видно все таки что-то сдели специально (и наверняка это вылезет боком в другом месте), чтоб улучшить ситуацию, поэтому можно смело слать баг-репорт команде fpc :?

хотя может это тот типичный для компьютеров случай когда от перестановки мест слагаемых сумма меняется :? но это опять же к fpc-team - они обещали совместимость с дельфи :wink:
Последний раз редактировалось STAKANOV 26.08.2006 23:53:45, всего редактировалось 1 раз.
Аватара пользователя
STAKANOV
энтузиаст
 
Сообщения: 1069
Зарегистрирован: 14.05.2006 21:26:24
Откуда: Зеленоград

Сообщение debi12345 » 26.08.2006 23:53:28

Почему "extended" не протестировали ( как Вам уже советовали ), прежде чем продолжать дискуссию ?

Только что проверено для fpc-2.0.4-rc3 :

---------------
var s:string;
f:extended;

begin
f:=-58.4;
s:=FloatToStr(f);
------------
печатает именно "-58.4"

Думаю, для "extended" не выполняется промежуточной конверсии ( в опять же "extended" ), искажающей результат.

extended:=-58.3(9) требует как минимум 15 цифр "9" после "3" для выполнения округления в "-58.4", и это 100% правильно.
Аватара пользователя
debi12345
долгожитель
 
Сообщения: 5759
Зарегистрирован: 10.05.2006 23:41:15
Откуда: Ташкент (Узбекистан)

Сообщение Mirage » 27.08.2006 12:05:51

debi12345:
Я НЕ ЗНАЮ, СКОЛЬКО ЗНАКОВ ПОСЛЕ ЗАПЯТОЙ ДОЛЖНО БЫТЬ У ЧИСЛА.


Почитайте описание FloatToStrF. Там не надо знать кол-во знаков. Оно определяется само. Надо лишь знать тип аргумента. Последний параметр должен быть 7/15/18 соот-но для single, double и extended, а кол-во знаков после запятой в строке определится само. Правда это в Дельфи. Незнаю как оно работает в FPC.
Mirage
энтузиаст
 
Сообщения: 881
Зарегистрирован: 06.05.2005 20:29:07
Откуда: Russia

Сообщение tria » 28.08.2006 11:32:29

debi12345 писал(а):Почему "extended" не протестировали ( как Вам уже советовали ), прежде чем продолжать дискуссию ?

Я проверял. Да, с -58.4 extended не дает ошибки.
И Double не дает. Но в моем случае это не помогает.
Мне надо решить задачу в принципе, а не для конкретного случая.
В моем конкретном случае один из аргументов выражения "приходит" из SQL-запроса к FIrebird и уже содержит ошибку округления (столбец в Group by).
Дальнейшее присвоение значения результата переменной типа extended эту ошибку не исправляет.
tria
постоялец
 
Сообщения: 401
Зарегистрирован: 03.04.2006 11:24:10

Сообщение tria » 28.08.2006 11:38:53

Mirage писал(а):debi12345:

Почитайте описание FloatToStrF. Там не надо знать кол-во знаков. Оно определяется само. Надо лишь знать тип аргумента. Последний параметр должен быть 7/15/18 соот-но для single, double и extended, а кол-во знаков после запятой в строке определится само. Правда это в Дельфи. Незнаю как оно работает в FPC.


Не знал, спасибо.
Проверил на Лазарусе:
Код: Выделить всё
var  f:single;
     s:string;
begin
  f:=-58.4;
  s:=FloatToStrF(f,FFfixed,7,7);   

Результат: -58.4000015
В Делфи: -58.4
А жаль...
tria
постоялец
 
Сообщения: 401
Зарегистрирован: 03.04.2006 11:24:10

Сообщение alexs » 28.08.2006 15:36:38

В моем конкретном случае один из аргументов выражения "приходит" из SQL-запроса к FIrebird и уже содержит ошибку округления (столбец в Group by).

А что за запрос ты используеш? какой тип данных
надо лечить причину - а не бороться со следствиями
увеличь точность в базе данных.
Аватара пользователя
alexs
долгожитель
 
Сообщения: 4060
Зарегистрирован: 15.05.2005 23:17:07
Откуда: г.Ставрополь

Сообщение tria » 28.08.2006 15:45:44

alexs писал(а):В моем конкретном случае один из аргументов выражения "приходит" из SQL-запроса к FIrebird и уже содержит ошибку округления (столбец в Group by).

А что за запрос ты используеш? какой тип данных
надо лечить причину - а не бороться со следствиями
увеличь точность в базе данных.

GROUP BY по столбцу Decimal(12,2).
Получаю данные как q.fields[x].AsVariant;
Насколько я понял, FIBL в этом случае берет данные как AsFloat.
tria
постоялец
 
Сообщения: 401
Зарегистрирован: 03.04.2006 11:24:10

Пред.След.

Вернуться в Lazarus

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 20

Рейтинг@Mail.ru