Страница 1 из 2

Прошу объяснить матекатику 1,8-1=0,800004

СообщениеДобавлено: 19.03.2015 03:07:04
Sharfik
Натыкался на проблему с округлением ранее, и все решалось добавлением "SetRoundMode(rmUP);", но тут.
Я не понимаю.
Алгоритм работает отдельно, если ему передать руками число и округлить, но если в составе прохода расчетов требуется округлить число, то вечно в окончании 0,00000000004.
Подумал что замудрил с типами, но тогда число сконвертированное в строку и обратно должно было исключить такую проблему, а тут стабильность.
Может кто то натыкался, а то особо нет времени ставить среду с fpc 3.1 и любоваться теми же ошибками.
Код: Выделить всё
// Входит значение 0,79545153321
function GetRoundValueTo2(X:Double):Double;
begin
   try
       Math.SetRoundMode(rmUP);
       if not((x>-1)and(x<1)) then
       begin
          x:=SimpleRoundTo(x,-2);
          result:=x;
       end
       else begin
          x:=x+1; //получаем 1,79545153321
          x:=SimpleRoundTo(x,-2); //получаем 1,8
          x:=x-1; //получаем 0,8000000004
          result:=x;
       end;
   except
       result:=x;
   end;
end;

Re: Прошу объяснить матекатику 1,8-1=0,800001

СообщениеДобавлено: 19.03.2015 09:24:55
zub
Это плавающая запятая. Почти никогда нет ровно какогото числа, есть близкое к нему и это всегда надо учитывать

Re: Прошу объяснить матекатику 1,8-1=0,800001

СообщениеДобавлено: 19.03.2015 10:12:56
Дож
У меня всё чётко:
Код: Выделить всё
[doj@larion ~/temp]$ cat round.pas
{$MODE OBJFPC}
uses
  Math;

var
  X: Double;

procedure DumpBitsInByte(Value: Byte);
var
  I: LongInt;
begin
  for I := 0 to 7 do begin
    if (Value and 128) > 0 then
      Write('1')
    else
      Write('0');
    Value := Value shl 1;
  end;
end;

procedure DumpBits(Value: PByte; Bytes: LongInt = 8);
begin
  Inc(Value, Bytes - 1);
  while Bytes > 0 do begin
    DumpBitsInByte(Value^);
    Dec(Value);
    Dec(Bytes);
  end;
end;

begin
  Writeln('SE..........F...................................................');
  X := 1;
  DumpBits(@X); Writeln(' = ', X:0:11);
  X := 1.8;
  DumpBits(@X); Writeln(' = ', X:0:11);
  X := X - 1;
  DumpBits(@X); Writeln(' = ', X:0:11);
  Writeln('SE..........F...................................................');
  X := 0.79545153321;
  DumpBits(@X); Writeln(' = ', X:0:11);
  X := X + 1;
  DumpBits(@X); Writeln(' = ', X:0:11);
  X := SimpleRoundTo(1.8, -2);
  DumpBits(@X); Writeln(' = ', X:0:11);
  X := X - 1;
  DumpBits(@X); Writeln(' = ', X:0:11);
end.
[doj@larion ~/temp]$ fpc round.pas && ./round
/usr/bin/ld: warning: link.res contains output sections; did you forget -T?
SE..........F...................................................
0011111111110000000000000000000000000000000000000000000000000000 = 1.00000000000
0011111111111100110011001100110011001100110011001100110011001101 = 1.80000000000
0011111111101001100110011001100110011001100110011001100110011010 = 0.80000000000
SE..........F...................................................
0011111111101001011101000101011011000110000101100001010010001011 = 0.79545153321
0011111111111100101110100010101101100011000010110000101001000110 = 1.79545153321
0011111111111100110011001100110011001100110011001100110011001101 = 1.80000000000
0011111111101001100110011001100110011001100110011001100110011010 = 0.80000000000


(И да, тут последовательность бит неправильная, но мне было лениво переделывать.)

Re: Прошу объяснить матекатику 1,8-1=0,800001

СообщениеДобавлено: 19.03.2015 11:51:49
wavebvg
Потому что запятая "поплыла". Чтобы такого не было нужно добавить и оперировать текущей точностью после запятой (количество значящих цифр не поменялось же, значит после запятой появилось какое-то новое значение 0-5). Это дорого (с точки зрения машинного времени), но Вы можете реализовать это самостоятельно и радоваться точности.

Re: Прошу объяснить матекатику 1,8-1=0,800001

СообщениеДобавлено: 19.03.2015 12:08:01
zub
Дож
попробуйте так, всё станет ясно
Код: Выделить всё
begin
  X := SimpleRoundTo(0.79545153321, -2);
  DumpBits(@X); Writeln(' = ', X);
  X := X - 1;
  DumpBits(@X); Writeln(' = ', X);
end.

выводит
0011111111101001100110011001100110011001100110011001100110011010 = 8.0000000000000004E-001
1011111111001001100110011001100110011001100110011001100110011000 = -1.9999999999999996E-001


Writeln(X:0:11) - тоже округляет при надобности, поэтому в вашем примере "проблему" не видно.
Либо 0.8 не имеет точного представления в двойной точности - по факту 8.0000000000000004E-001, либо SimpleRoundTo посчитала что точности 0.0000000000000004 вполне достаточно. Разницы нет - всеравно все операции над числами с плавающей запятой нужно производить с учетом точности (так называемый эпсилон)

Re: Прошу объяснить матекатику 1,8-1=0,800001

СообщениеДобавлено: 19.03.2015 12:54:28
Sharfik
zub писал(а):Это плавающая запятая. Почти никогда нет ровно какого то числа, есть близкое к нему и это всегда надо учитывать

Это полный бред. Есть число, заданной размерности, ему присваивают 0, а потом увеличивают на 1. Оно не может, не в какой логике быть после 1,000001. Учитывать тут не плавающую запятую надо, а идиотизм компилятора, который выше 1 считает нормально, а в диапазоне близко нуля глючит. Это хорошо видно, если словить такую ошибку. А если он такой точный, то при значении 0,800000004-0,000000004=0,8. А не игнорирование операции вообще.
Я понимаю в Java не сравнения строк, адреса сравниваются оператором, а строки только через функцию. А в математике, это уже перебор.
$#%#$% такой проект прибили :(

Re: Прошу объяснить матекатику 1,8-1=0,800001

СообщениеДобавлено: 19.03.2015 12:59:26
zub
>>Это полный бред
не бред. 0+1 конечно сработает точно, но есть мноооого других чисел которые в десятичной системе выглядят коротко и красиво, а в двоичной не имеют точного представляния. Просто смириться и учитывать - ничего в этом страшного нет.

>>Есть число, заданной размерности, ему присваивают 0, а потом увеличивают на 1. Оно не может, не в какой логике быть после 1,000001.
непонял к чему это, но если так происходит, то это глюк не компилятора, а программы))

Re: Прошу объяснить матекатику 1,8-1=0,800001

СообщениеДобавлено: 19.03.2015 13:16:01
Sharfik
zub писал(а):>>Это полный бред
не бред. 0+1 конечно сработает точно, но есть мноооого других чисел которые в десятичной системе выглядят коротко и красиво, а в двоичной не имеют точного представляния. Просто смириться и учитывать - ничего в этом страшного нет.

>>Есть число, заданной размерности, ему присваивают 0, а потом увеличивают на 1. Оно не может, не в какой логике быть после 1,000001.
непонял к чему это, но если так происходит, то это глюк не компилятора, а программы))


Ну из моего примера посмотри комменты. Округление вне диапазона -1...1 происходит нормально, а округление внутри него выдает "паразитное" значение. Прототипы моих расчетов в Delphi все считали нормально. Если такая погрешность существует, это не задача для высокого уровня(lazarus), это должно было обработаться на низком уровне, где компилятор рулит, иначе какой толк от fpc и lazarus, если все равно самому считать байты надо.
Если условность в точности есть, то она должна быть везде, а не здесь хочу, а здесь не хочу. Следовательно с твоих же слов, и арифметика должна была компилятором обработаться с учетом погрешности. Операторы +-/*= это не мы решаем как они работают, а в компиляторе описана их функция. Не нормально, если половина расчета выполняется что "1*0,8=0,8", другая "1*0,8=0,800004" (к примеру). С трудом представляю как вывести на чертеж/в форму/еще куда то коэффициент расчета "0,8", если его даже функция округления превращает переменную с ним в число с 10 знаками после запятой, вместо заданных ей 2х.

wavebvg писал(а): Это дорого (с точки зрения машинного времени), но Вы можете реализовать это самостоятельно и радоваться точности.

С точки зрения машинного времени дорого изначально правильно высчитать число, а потом оперировать десятком таких в более медленных функциях корректировки числа не дорого?)))

Re: Прошу объяснить матекатику 1,8-1=0,800001

СообщениеДобавлено: 19.03.2015 14:01:47
Sergei I. Gorelkin
Если что, преобразование чисел с плавающей запятой в строку и обратно в fpc <= 2.6.4 вносит очень большие погрешности.
В 3.0 эта часть полностью переписана и дает результаты, сравнимые с другими библиотеками.

Re: Прошу объяснить матекатику 1,8-1=0,800001

СообщениеДобавлено: 19.03.2015 14:19:03
zub
>>но есть мноооого других чисел которые в десятичной системе выглядят коротко и красиво, а в двоичной не имеют точного представляния
Это оспаривается? если оспаривается то дальше рассуждать бесполезно.
Если нет, то продолжим. Комп работает с двоичными числами, то что ты видишь их десятичные представления - например "0.88" вовсе не значит что там где ты смотришь лежит именно 0.88, там по факту лежит 0.88+-eps. Ну нету у десятичного 0.88 точного представления в двоичной системе и никакой SimpleRoundTo не заставит его появиться. (естественно 0.88 взял от балды, как оно выглядит в двойной точности не проверял)
Задача компилятора выполнять требуемые действия и он с тей неплохо справляется, как интерпретировать результаты - задача програмиста. Например если ты сравниваешь 2 числа, будь добр использовать чтонить типа
Код: Выделить всё
if abs(x1-x2)<eps then ...
//или если хочется быть повыше возни с цифрами
if MyImplDoubleIsEqual(x1,x2) theh


>>Округление вне диапазона -1...1 происходит нормально
Чем больше число, тем меньше точность double. Округление в этом диапозоне происходит абсолютно также как и за ним, просто вероятность получить "непредвденный" результат чуток поменьше))

>>С трудом представляю как вывести на чертеж/в форму/еще куда то коэффициент расчета "0,8", если его даже функция округления превращает переменную с ним в число с 10 знаками после запятой, вместо заданных ей 2х.
Ничего хитрого, другие справляются))

Re: Прошу объяснить матекатику 1,8-1=0,800001

СообщениеДобавлено: 19.03.2015 14:58:16
Sharfik
Sergei I. Gorelkin, спасибо. Посмотрю.

zub, я прекрасно понимаю, что другие справляются. И сам представляю как можно обойти некоторые приколы, но это время на переработку, учет функций и иногда функции могут получится весьма веселыми. Хотя, можно было просто на самом низком уровне сделать чтобы при присвоении переменной Extended значения Double не оговоренная точность не заполнялась от балды, как это делает fpc 2.x. (на 3.х не успел проверить все это).
Это видно, если поиграться с SetPrecisionMode(); Если делать расчет в переменных Extended, а сравнивать значения в переменных размерностью Double, то все работать начинает как хочется(надеюсь что везде). Но если делать все в Extended, то не о какой точности и речи нет. Хотя исходя из логики вещей, и того что Lazarus+FPC все же IDE высокого уровня, если оговаривается точность Extended, то она должна быть, а ее нет. То что невозможно точно в битовом виде что то указать, это не относится к этому уровню, это просто должно быть учтено в работе операторов, чего нет для максимально доступной нам точности Extended. (Это меня и задевает)Получается что делать расчеты точнее Double не представляется возможным, потому что Extended используется как тип для фильтра "паразитных" значений.

Короче итог один, сравнения производить либо функциями либо в функциях где есть сравнения простые не использовать extended, и сделить за этим везде. :(

Re: Прошу объяснить матекатику 1,8-1=0,800001

СообщениеДобавлено: 19.03.2015 15:32:35
Дож
zub писал(а):Дож
попробуйте так, всё станет ясно


А, ну тогда сложных примеров мудрить не нужно, дело не в округлении, достаточно просто распечатать число 0.8:
Код: Выделить всё
[doj@larion ~/temp]$ cat round.pas
{$MODE OBJFPC}
uses
  Math;

var
  X: Double;

procedure DumpBitsInByte(Value: Byte);
var
  I: LongInt;
begin
  for I := 0 to 7 do begin
    if (Value and 128) > 0 then
      Write('1')
    else
      Write('0');
    Value := Value shl 1;
  end;
end;

procedure DumpBits(Value: PByte; Bytes: LongInt = 8);
begin
  Inc(Value, Bytes - 1);
  while Bytes > 0 do begin
    DumpBitsInByte(Value^);
    Dec(Value);
    Dec(Bytes);
  end;
end;

begin
  Writeln('SE..........F...................................................');
  X := 0.8;
  DumpBits(@X); Writeln(' = ', X);
end.
[doj@larion ~/temp]$ fpc round.pas && ./round
/usr/bin/ld: warning: link.res contains output sections; did you forget -T?
SE..........F...................................................
0011111111101001100110011001100110011001100110011001100110011010 =  8.0000000000000004E-001


Берём формулу для декодирования дабла:
Изображение

Самое интересное тут — мантисса. В F сразу видна периодичность битов.
Изображение

С учётом порядка, если это число поделить на 2, то утверждается, что мы должны получить 0.8=8/10. Давайте посчитаем разницу:
Изображение

Перевод обыкновенной дроби в десятичную читателю предлагается провести самостоятельно. Также предлагаю подумать над другими способами закодировать 0.8 в формате double.

С трудом представляю как вывести на чертеж/в форму/еще куда то коэффициент расчета "0,8", если его даже функция округления превращает переменную с ним в число с 10 знаками после запятой, вместо заданных ей 2х.

Как вариант: использовать свой тип для работы с десятичными нецелыми числами.

Re: Прошу объяснить матекатику 1,8-1=0,800001

СообщениеДобавлено: 19.03.2015 15:53:17
zub
>>Перевод обыкновенной дроби в десятичную читателю предлагается провести самостоятельно.
>>Также предлагаю подумать над другими способами закодировать 0.8 в формате double.
Честно даже не хочется вникать что тут доказывается то что есть 0.8 в виде флоата, или то что нет. настолко страшно выглядит))

Предлагаю сделать проще - ввести епс для вычислений, требемое колво знаков после запятой для вывода результатов и забить на все тонкости реализаций в виде преобразований extended<->double. Деревья сразу станенут выше, солнышко ярче и девченки красивее))

Re: Прошу объяснить матекатику 1,8-1=0,800001

СообщениеДобавлено: 19.03.2015 16:17:18
Sharfik
zub писал(а):Деревья сразу станенут выше, солнышко ярче и девченки красивее))

Деревья, солнышко, девченки... слышал про такие :roll:

Re: Прошу объяснить матекатику 1,8-1=0,800001

СообщениеДобавлено: 19.03.2015 16:57:06
zub
Ладно переборем страх))
Изображение=4,4408920985006261616945266723633e-17
вполне согласуется с 8.0000000000000004E-001

У Sharfik все конечно драматичней, в названии 0,800001, по тексту 0,00000000004 - больше похоже на single или на результат расчета в конце которого извлекался корень