Lazarus

Programming => General => Topic started by: Salazar on April 20, 2021, 09:25:58 pm

Title: Odd way of calculating the Logarithm
Post by: Salazar on April 20, 2021, 09:25:58 pm
Hi all,

recently I discovered that Lazarus/FreePascal calculates
Code: Pascal  [Select][+][-]
  1. Trunc(Log2(8))
as 2. Whereas it should be 3, because
Code: Pascal  [Select][+][-]
  1. Log2(8) = 3
and Trunc(3) is 3. I suppose that internally it is calculated as some floatingpointnumber 2.99999... and then truncated to 2.

I use this calculation to determin the position (level) of a number in a binary tree for visualizing it. And this calculation worked correctly earlier. Then - maybe because of a new Lazarusversion - the calculation went wrong.

Does anyone know how this could happen and whether it is a bug in Lazarus/FPC, that should be corrected?

Sorry for the bad formatting, but if I don't quote the text as code, silly smileys are put in  :(.

By the way: I use Lazarus-Version 2.0.12 and FPC-Version 3.2.0.

Thank You for any answer!

Title: Re: Odd way of calculating the Logarithm
Post by: howardpc on April 20, 2021, 09:30:46 pm
Trunc does exactly that: it truncates any fractional part of a number, no matter how small (.000000000000001) or large (.99999999999999999).

You are probably looking for the Round() function which returns the nearest integral value to a floating point number (and I believe does "banker's rounding" for values of x.5).
Title: Re: Odd way of calculating the Logarithm
Post by: Salazar on April 20, 2021, 09:38:00 pm
The Problem is, that "Round" calculates the levels wrong in principal. So what I would need is a trunc-function that works in the way that I see as mathematically correct.
Title: Re: Odd way of calculating the Logarithm
Post by: winni on April 20, 2021, 09:48:46 pm
Hi!

Write your own round:

Code: Pascal  [Select][+][-]
  1. function RoundCorrect (s: single) : Integer;
  2. var minus : Boolean;
  3. begin
  4. minus := s < 0;
  5. result := trunc (abs(s) +0.5);
  6. if minus then result := -result;
  7. end;

Winni
Title: Re: Odd way of calculating the Logarithm
Post by: Salazar on April 20, 2021, 10:05:11 pm
Thank you Winni for the answer, but no "version" of rounding will do the job. The 8th number has to go to the 3rd level as all numbers up to 15. The 16th number up to the 31. goes to level 4. This could (and should) be calculated by the trunc-function (trunc(log2(number)) respectively). My question is, why it doesn't. Is this a bug in FPC?
Title: Re: Odd way of calculating the Logarithm
Post by: MarkMLl on April 20, 2021, 10:16:19 pm
Thank you Winni for the answer, but no "version" of rounding will do the job. The 8th number has to go to the 3rd level as all numbers up to 15. The 16th number up to the 31. goes to level 4. This could (and should) be calculated by the trunc-function (trunc(log2(number)) respectively). My question is, why it doesn't. Is this a bug in FPC?

I suggest that you give us examples. If you are convinced that you have a bug then submit it via Mantis, but you'll find the bar quite high.

https://bugs.freepascal.org/bug_report_page.php

MarkMLl
Title: Re: Odd way of calculating the Logarithm
Post by: balazsszekely on April 20, 2021, 10:19:48 pm
@Salazar
With Lazarus Trunk/FPC 3.2.0 works fine for me(see attached screenshot). Or perhaps I misunderstood your question?
Title: Re: Odd way of calculating the Logarithm
Post by: Warfley on April 20, 2021, 10:38:51 pm
I use always an interger based log function for this sort of thing:
Code: Pascal  [Select][+][-]
  1. function ilog2(value: integer): Integer;
  2. begin
  3.   Result := 0;
  4.   While value shr Result > 0 do
  5.     Inc(result);
  6. end;
Untested, but something like that should do the job.
Title: Re: Odd way of calculating the Logarithm
Post by: Salazar on April 20, 2021, 10:51:07 pm
@GetMem

Hello GetMem,

you understood wright. Now I am completely bewildered because on my computer it does not calculate like your snippet shows. See the attachment. The executable  shows 2 instead of 3.

Maybe I did something in the context wrong?

As an example I chose:

Code: Pascal  [Select][+][-]
  1. { TfrmExampleMain }
  2.  
  3. procedure TfrmExampleMain.btnCalculateClick(Sender: TObject);
  4. begin
  5.   pnlResult.Caption := IntToStr(Trunc(Log2(8));
  6. end;

It is the same behaviour with "FloatToStr" instead of "IntToStr". I added the Math-Unit to the Uses-Clause.

Unfortunately I could not add the executable in the attachement because it exceeds the limit of 500 kb.

I don't know whether it compiles differently with other Lazarus-/FPC-Versions.
Title: Re: Odd way of calculating the Logarithm
Post by: Salazar on April 20, 2021, 10:57:29 pm
I use always an interger based log function for this sort of thing:
Code: Pascal  [Select][+][-]
  1. function ilog2(value: integer): Integer;
  2. begin
  3.   Result := 0;
  4.   While value shr Result > 0 do
  5.     Inc(result);
  6. end;
Untested, but something like that should do the job.

That sounds like a solution to the problem, Warfley. I will try it, thank you!

Nevertheless I'd like to know, why my mentioned code does not work as expected.
Title: Re: Odd way of calculating the Logarithm
Post by: Bart on April 20, 2021, 11:01:09 pm
Depending on your OS and CPU (bitness?) the code used to calculate log2 may differ and so might the result type.
On win32 the result type will be extended, on win64 it will be double.
Since on all systems the calculation involves floating point operations there will in most cases not be a mathematically correct return value, since most numbers cannot be represented exactly in a floating point.
So, while mathematically the result of Log2(8) equals 3 exactly, the actual result returned by the CPU might either be 2.99999999999999999999999 or 3.000000000000000000000001 or similar.

Compiled for win32 (i386 procesor) Log2(8) returns: 3.00000000000000000000E+0000, the return value being of type exyended,
Compiled for win64 (x86_64 processor) Log2(8) returns: 2.9999999999999996E+000, the return value of being type double.

i386 has dedicated asm function for Log2, x86_64 does not (in fpc 3.2.0), it calculates it as log2:=ln(x)*1.4426950408889634079;    { 1/ln(2) }

Trunc() then returns the expected result: 2 in case of 2.999999999999999, 3 in case of 3.000000000000000001.

Bart
Title: Re: Odd way of calculating the Logarithm
Post by: Salazar on April 20, 2021, 11:14:12 pm
@Bart

Thank you Bart, for this great detailed answer!

So the reason, why my code broke at some point must have been, when I changed from Lazarus Version 32bit to 64bit. I could not imagine this consequence  :o.

May I ask you what "dedicated function" means. As I understand from your answer it must be some "better version" of calculating log2 in a unique way for each number. Am I wright? Why isn't this implemented in 64bit?
Title: Re: Odd way of calculating the Logarithm
Post by: Bart on April 20, 2021, 11:28:12 pm
On i386 Log2(x) is calculated like this:

Code: Pascal  [Select][+][-]
  1. function log2(x : float) : float;assembler;
  2.   asm
  3.     fld1
  4.     fldt x
  5.     fyl2x
  6.     fwait
  7.   end;

Bart
Title: Re: Odd way of calculating the Logarithm
Post by: jamie on April 21, 2021, 12:49:06 am
Code: Pascal  [Select][+][-]
  1. Function ILog2(x:Integer):Integer; nostackframe Assembler;
  2. Asm
  3.  bsr x,%eAx;
  4. End;
  5.  
  6. procedure TForm1.Button1Click(Sender: TObject);
  7. begin
  8.   Caption := Ilog2(8).Tostring;
  9. end;                                      
  10.  

Does that work for you?
That should work in both 32 and 64 bit of intel that is.

Not sure about the results but I am guessing that will generate the results you are looking for.

The 8 tested value results in 3 here.

Title: Re: Odd way of calculating the Logarithm
Post by: engkin on April 21, 2021, 01:13:36 am
Jamie, FPC has BsrByte, BsrWord, BsrDWord, and BsrQWord.
Title: Re: Odd way of calculating the Logarithm
Post by: engkin on April 21, 2021, 01:59:46 am
Ok, so why aint any one using them ?

I do use them, like here (https://forum.lazarus.freepascal.org/index.php/topic,39880.msg278293.html#msg278293).
Title: Re: Odd way of calculating the Logarithm
Post by: PascalDragon on April 21, 2021, 09:11:28 am
May I ask you what "dedicated function" means. As I understand from your answer it must be some "better version" of calculating log2 in a unique way for each number. Am I wright? Why isn't this implemented in 64bit?

No, there is not, because Double which is the highest available floating point type available on Win64 as well as any non-x86 system simply cannot represent 3 exactly (as well as many other values). So no matter what you do, Trunc of that will simply return 2 (in case its Double representation is less than 3) and that is correct.
Title: Re: Odd way of calculating the Logarithm
Post by: MathMan on April 21, 2021, 11:00:06 am
May I ask you what "dedicated function" means. As I understand from your answer it must be some "better version" of calculating log2 in a unique way for each number. Am I wright? Why isn't this implemented in 64bit?

No, there is not, because Double which is the highest available floating point type available on Win64 as well as any non-x86 system simply cannot represent 3 exactly (as well as many other values). So no matter what you do, Trunc of that will simply return 2 (in case its Double representation is less than 3) and that is correct.

I beg to differ here. The value 3 can be represented exact as a finite binary floating point value (in fp32 single, fp64 double or fp80 extended). What can not be represented exact as a finite binary floating point value is e.g. 0.1 (aka one tenth).

MathMan
Title: Re: Odd way of calculating the Logarithm
Post by: dseligo on April 21, 2021, 11:10:10 am
recently I discovered that Lazarus/FreePascal calculates
Code: Pascal  [Select][+][-]
  1. Trunc(Log2(8))
as 2.

You could do it like this:
Code: Pascal  [Select][+][-]
  1. Trunc(Currency(Log2(8)))
Title: Re: Odd way of calculating the Logarithm
Post by: PascalDragon on April 21, 2021, 01:52:16 pm
May I ask you what "dedicated function" means. As I understand from your answer it must be some "better version" of calculating log2 in a unique way for each number. Am I wright? Why isn't this implemented in 64bit?

No, there is not, because Double which is the highest available floating point type available on Win64 as well as any non-x86 system simply cannot represent 3 exactly (as well as many other values). So no matter what you do, Trunc of that will simply return 2 (in case its Double representation is less than 3) and that is correct.

I beg to differ here. The value 3 can be represented exact as a finite binary floating point value (in fp32 single, fp64 double or fp80 extended). What can not be represented exact as a finite binary floating point value is e.g. 0.1 (aka one tenth).

I stand corrected.

I've looked at the problem again and what might work is to tweak the 1/ln(2) constant that is used inside log2 for each of the supported maximum floating point type. E.g. the current one is 1.4426950408889634079 which is correct for Extended, but for Double the value t1.4426950408889635 results in more correct values (at least they're overestimating instead of underestimating). Similar for Single and probably also for log10.
Title: Re: Odd way of calculating the Logarithm
Post by: Salazar on April 21, 2021, 02:33:13 pm
@dseligo
This works with a minimal interference in the existig code. Thank you!

@PascalDragon
I understand, that - in general - decimal numbers can not be represented exactly in the binary system. But why is the calculation in question different in Win32 and Win64?
Is it so, that for some numbers the calculation gives the expected result and for others not - dependent from the conversation from binary to decimal? If this is the case then for some numbers the outcome of trunc(log2(number)) might be as expected in Win32 - for others in Win64.

Please confer what Bart wrote earlier in this post. From his post I got the impression, that the calculation allways leads to expected results in Win32.

I hope that the thread does not get to sophisticated here, but I am interested in understanding what's really going on.
Title: Re: Odd way of calculating the Logarithm
Post by: MathMan on April 21, 2021, 04:54:53 pm
I've looked at the problem again and what might work is to tweak the 1/ln(2) constant that is used inside log2 for each of the supported maximum floating point type. E.g. the current one is 1.4426950408889634079 which is correct for Extended, but for Double the value t1.4426950408889635 results in more correct values (at least they're overestimating instead of underestimating). Similar for Single and probably also for log10.

I suspected that much, when I saw the code excerpt provided by Bart. Is it possible to define floats in hex format (within the scope of system libraries at least)? This could eliminate rounding errors introduced by StrToFloat conversions. Because in theory the specific constant here would need to have decimal representations of 24, 54 or 65 decimal digits - and depending on the lib for conversion to float these may get rounded different.

MathMan
Title: Re: Odd way of calculating the Logarithm
Post by: Paolo on April 21, 2021, 06:22:32 pm
Quote
in the binary system. But why is the calculation in question different in Win32 and Win64?

Not fully sure, no checked, but I suspect that the different results are due to different precision applied during computation. Win32 shold use extended, win64 double and when come back on the final variable it is rounded accordingly to the var size. The same situation for delphi code.
Title: Re: Odd way of calculating the Logarithm
Post by: PascalDragon on April 22, 2021, 09:26:45 am
I understand, that - in general - decimal numbers can not be represented exactly in the binary system. But why is the calculation in question different in Win32 and Win64?
Is it so, that for some numbers the calculation gives the expected result and for others not - dependent from the conversation from binary to decimal? If this is the case then for some numbers the outcome of trunc(log2(number)) might be as expected in Win32 - for others in Win64.

Because Win32 has access to the Extended type, Win64 does not. You would have the same result on any platform that does not support Extended which is any non-x86 (i8086, i386, x86_64) platform and the Win64 one.

I've looked at the problem again and what might work is to tweak the 1/ln(2) constant that is used inside log2 for each of the supported maximum floating point type. E.g. the current one is 1.4426950408889634079 which is correct for Extended, but for Double the value t1.4426950408889635 results in more correct values (at least they're overestimating instead of underestimating). Similar for Single and probably also for log10.

I suspected that much, when I saw the code excerpt provided by Bart. Is it possible to define floats in hex format (within the scope of system libraries at least)? This could eliminate rounding errors introduced by StrToFloat conversions. Because in theory the specific constant here would need to have decimal representations of 24, 54 or 65 decimal digits - and depending on the lib for conversion to float these may get rounded different.

The system libraries are not in any way "magic" regarding this. A hexadecimal value will be interpreted as an ordinal and then converted to the floating point type. What might work is a record.
TinyPortal © 2005-2018