Home
JAQForum Ver 20.06
Log In or Join  
Active Topics
Local Time 14:42 20 May 2024 Privacy Policy
Jump to

Notice. New forum software under development. It's going to miss a few functions and look a bit ugly for a while, but I'm working on it full time now as the old forum was too unstable. Couple days, all good. If you notice any issues, please contact me.

Forum Index : Microcontroller and PC projects : a small oddity in cint() and hex$

     Page 1 of 3    
Author Message
robert.rozee
Guru

Joined: 31/12/2012
Location: New Zealand
Posts: 2294
Posted: 10:09am 09 May 2020
Copy link to clipboard 
Print this post

have just spotted something odd in one of the basic test programs i use, which i've distilled below:


> a=8454399
> ? hex$(a), hex$(int(a))
810100  8100FF
> b=a/1000.0
> ? b
8454.4
> b=b-8454
> ? b
0.399414
> b=b*1000
> ? b
399.414
> ? a
8.45440e+06
> ? cint(a)
8454400
> ? int(a)
8454399
> ? fix(a)
8454399
> ? hex$(a), hex$(fix(a))
810100  8100FF
> ? hex$(a), hex$(cint(a))
810100  810100
>


now apart from the fact that i'm storing a number way too large to be represented exactly in a 6-byte real, why are cint() and int() producing different results when the fractional part is only 0.414?! the behaviour seems to also occur with hex$(), which is where i first noticed it.


cheers,
rob   :-)

addendum: make that 4-byte real!, giving an upper usable limit of 0x800000     i should have read the manual more closely! the initial tripping point was the line of code i was using to assemble three reals (holding values between 0 and 255) into a single integer:

  CV%=(((Int(R) << 8) Or Int(G)) << 8) Or Int(B)       ' ok
' CV%=(((Int(R) << 8) + Int(G)) << 8) + Int(B)         ' ok
' CV%=(((Int(R)*256)+Int(G))*256)+Int(B)               ' ok
' CV%=(((R*256)+G)*256)+B                              ' fails


the last version is the one i was using, not realizing that the interpreter was using fp maths to combine the three values, and only converting to an integer when doing the assignment to CV%
Edited 2020-05-09 22:13 by robert.rozee
 
JohnS
Guru

Joined: 18/11/2011
Location: United Kingdom
Posts: 3678
Posted: 12:19pm 09 May 2020
Copy link to clipboard 
Print this post

Is this on a smaller 'mite / Maximite?

I think you're using something with 32-bit (4-byte) reals and have something like 20 bits for the mantissa, perhaps 19+sign, but "a" won't fit.

edit: I think I mean 23 bits or so (2^23 is 8388608).

Anyway, what happens is that the real's exponent is increased and you lose some precision as the nearest value which can be represented in the limited number of bits is put into the mantissa.

(This occurs sooner or later with all floating point numbers.)

John
Edited 2020-05-09 22:55 by JohnS
 
robert.rozee
Guru

Joined: 31/12/2012
Location: New Zealand
Posts: 2294
Posted: 01:07pm 09 May 2020
Copy link to clipboard 
Print this post

yep, a micromite: 32-bit reals containing 23-bit fraction, 8-bit exponent. 2^23 = 0x800000, or 8388608 decimal as the largest exact decimal

what has thrown me a bit is the wikipedia page that says a 32-bit float can hold "all integers with 7 or fewer decimal digits". i'm just running a test to see if this is true...

and it is! am up to 10270000. this is NOT using cint(), but converting to a string using str$(a, 0, 1) and comparing str$(i%)+".0":
a =8300000
i%=8300000

Do
 If Str$(a,0,1)<>(Str$(i%)+".0") Then Print Str$(a,0,1), i%: End
 a=a+1
 i%=i%+1
 If (i% Mod 10000)=0 Then Print i%
Loop


so it does look like cint() could do better, but i'd not consider it an actual bug.


cheers,
rob   :-)
 
JohnS
Guru

Joined: 18/11/2011
Location: United Kingdom
Posts: 3678
Posted: 02:36pm 09 May 2020
Copy link to clipboard 
Print this post

hmm... I thought the PIC32 used IEEE 754, in which case 23 bits are stored explicitly but that means 24 actually.

They tweak the exponent so that the leading bit of the mantissa is a 1.  Then there's no reason to store it because it's always a 1.  (OK, zero is special.)

Now I'm worried as I think your number should work.

Oh... I think the PIC32 does not use 754 but instead some software library.

John
 
robert.rozee
Guru

Joined: 31/12/2012
Location: New Zealand
Posts: 2294
Posted: 04:03am 10 May 2020
Copy link to clipboard 
Print this post

a further data point, the above program found an error at 16777216:

16777216.0       16777217


so last non-error value was 16777215 = 0xFFFFFF.

the 16777... rang a bell, so i went back through old copies of the micromite manual searching for it. there it was, in the version 4.5D manual (pre-64bit integers)  on page 31, "The range of integers (whole numbers) that can be manipulated without loss of accuracy is ±16777100". the line has been omitted from manuals for 4.6 onwards, probably because 64-bit integers became available.

so it looks like it may be a regression, albeit a very minor one.

i'm guessing that hex$() uses cint, while str$() does not.

geoff: can you possibly comment?


cheers,
rob   :-)

addendum: for floats larger than 8388608 (0x800000) it looks like str$(x, 0, 0) returns an incorrect result, while str$(x, 0, 1) returns a correct result:

Print Str$(a,0,1), Str$(a,0,0),, Str$(a)

8388600.0       8388600         8.38860e+06
8388601.0       8388601         8.38860e+06
8388602.0       8388602         8.38860e+06
8388603.0       8388603         8.38860e+06
8388604.0       8388604         8.38860e+06
8388605.0       8388605         8.38860e+06
8388606.0       8388606         8.38861e+06
8388607.0       8388607         8.38861e+06
8388608.0       8388608         8.38861e+06
8388609.0       8388610         8.38861e+06
8388610.0       8388610         8.38861e+06
8388611.0       8388612         8.38861e+06
8388612.0       8388612         8.38861e+06
8388613.0       8388614         8.38861e+06
8388614.0       8388614         8.38861e+06
8388615.0       8388616         8.38861e+06
8388616.0       8388616         8.38862e+06

Edited 2020-05-10 14:21 by robert.rozee
 
robert.rozee
Guru

Joined: 31/12/2012
Location: New Zealand
Posts: 2294
Posted: 06:10am 10 May 2020
Copy link to clipboard 
Print this post

i think the problem may be in the function FloatToInt64 in the source file MMBasic.c:

long long int FloatToInt64(MMFLOAT x) {
   if(x < (-(0x7fffffffffffffffLL) -1) - 0.5 || x > 0x7fffffffffffffffLL + 0.5)
       error("Number too large");
   return (x >= 0 ? (long long int)(x + 0.5) : (long long int)(x - 0.5)) ;
}

(from v5.04.09 MMbasic source code)

my suspicion is that the value returned should instead be:

return (x >= 0 ? (long long int)(x) + 0.5 : (long long int)(x) - 0.5) ;


ie, 0.5 should not be added/subtracted on to the value of x until after the type cast.

actually, i change my mind. while i think the solution is something different to what is above, i am still thinking the original line is creating the problem. it could be that we need to check the magnitude of x and if it is above 0x7FFFFF then only add/subtract 0.25?

nope, that doesn't work. i'm now thinking that for magnitudes of x over $7FFFFF we should simply not be adding/subtracting anything. the granularity up there is only half a bit anyway, which is where we are coming unstuck. try something like this to replace the single return line:


if ((x < -0x7fffff) || (x > 0x7fffff))
   return (long long int)(x)
else
   return (x >= 0 ? (long long int)(x + 0.5) : (long long int)(x - 0.5)) ;



cheers,
rob   :-)

addendum: here is the test code:

a =16777208
i%=16777208

' comment out the next two lines to check how the high range behaves.
' the high range is expected to fail for values above 0xFFFFFFFF,
' though perhaps should throw an error when it does.
a =8388600
i%=8388600

z%=i%+16

Do
 Print i%, Str$(a,0,1), Str$(a,0,0),, a
 a=a+1
 i%=i%+1
Loop Until i%>z%

Edited 2020-05-10 16:57 by robert.rozee
 
zeitfest
Guru

Joined: 31/07/2019
Location: Australia
Posts: 396
Posted: 08:20am 10 May 2020
Copy link to clipboard 
Print this post

8388609 (signed) is too large an integer to fit exactly into the 24 bit range so MMBasic bumps it to 8388610.


(At a guess..it is a while since I thought about this stuff)

The page about single precision I think you mention, goes on ...

Precision limitations on integer values
Integers between 0 and 16777216 can be exactly represented (also applies for negative integers between -16777216 and 0)
Integers between 2^24=16777216 and 2^25=33554432 round to a multiple of 2 (even number)

....


For practical math a bit is used for the sign, ie the 16... million possible values in 24 bits are mapped to the signed numeric integer range with the lowest 8 or so million in the negative zone.
Numbers outside the range start to be approximated/rounded with larger gaps.

Hence the rounding skips past 8388609 to 8388610.

ref

A good reason to use strong typed variables  !!  
Edited 2020-05-10 18:37 by zeitfest
 
robert.rozee
Guru

Joined: 31/12/2012
Location: New Zealand
Posts: 2294
Posted: 08:52am 10 May 2020
Copy link to clipboard 
Print this post

but if you look at the output:
8388613.0       8388614         8.38861e+06
8388614.0       8388614         8.38861e+06
8388615.0       8388616         8.38861e+06
8388616.0       8388616         8.38862e+06


8388615 and 8388616 resolve to different numbers using Str$(a,0,1).

the trick, as john pointed out earlier on, is that the leading '1' of a (binary) float is not encoded, but instead assumed to exist. this is how 24-bits are encoded into a 23-bit wide space, facilitated by there being an exponent used to 'move' the decimal point (or, in this case, binary point).

the sign is held in a separate bit, giving a total of 25 bits and a range of +/-0xFFFFFF.


from:
https://en.wikipedia.org/wiki/Single-precision_floating-point_format


cheers,
rob   :-)

(edit: replaced 'enabled' with 'facilitated' to make the wording clearer)
Edited 2020-05-10 20:33 by robert.rozee
 
robert.rozee
Guru

Joined: 31/12/2012
Location: New Zealand
Posts: 2294
Posted: 10:56am 10 May 2020
Copy link to clipboard 
Print this post

  robert.rozee said  nope, that doesn't work. i'm now thinking that for magnitudes of x over $7FFFFF we should simply not be adding/subtracting anything. the granularity up there is only half a bit anyway, which is where we are coming unstuck


my mistake, the granularity above $7FFFFF is 1 bit. i'm also thinking that the fault may in fact be in the pic32 C compiler - the observed behaviour is almost as if it is applying some sort of odd/even rounding for fractional values over $7FFFFF. because a type cast is being employed, the actual mechanism used by the compiler is obscured. either way, eliminating the 0.5 for values over +/-$7FFFFF feels right.


cheers,
rob   :-)

addendum: not a fault in the compiler, it is designed to do this:
https://en.wikipedia.org/wiki/IEEE_754#Roundings_to_nearest

it is applying the IEEE754 standard of rounding 'half-to-even', "if the number falls midway, it is rounded to the nearest value with an even least significant digit; this is the default for binary floating point". this rounding is happening within the addition/subtraction, before the type cast.

MYSTERY SOLVED!
Edited 2020-05-11 08:19 by robert.rozee
 
zeitfest
Guru

Joined: 31/07/2019
Location: Australia
Posts: 396
Posted: 12:05am 11 May 2020
Copy link to clipboard 
Print this post

(begging forgiveness here as I am not giving this the concentration it deserves, I am fuzzy from flu/whatever etc)

  Quote  for floats larger than 8388608 (0x800000) it looks like str$(x, 0, 0) returns an incorrect result,


Is that still ... ? eg should 8388609 be skipped ?

ed - it seems inconsistent with the statements about handling integers to 16 mill etc.
Edited 2020-05-11 10:13 by zeitfest
 
robert.rozee
Guru

Joined: 31/12/2012
Location: New Zealand
Posts: 2294
Posted: 02:31am 11 May 2020
Copy link to clipboard 
Print this post

the cut-off point is at 0x7FFFFF, but 0x800000 produces the 'correct' result as the 0.5 added on is rounded down. the same holds for 0x800002, 0x800004, etc.

the function FloatToInt64() is used by many functions within MMbasic, but most of them expect 'small valued' parameters so there is not an issue.

the statement from the 4.5D and earlier micromite manuals:

  Quote  "The range of integers (whole numbers) that can be manipulated without loss of accuracy is ±16777100"


dates from when MMbasic only supported 32-bit floats and NO integer type, hence the problematic routine FloatToInt64() would likely not have existed as such. someone would need to either check the old source code, or do a test with a 4.5D or earlier firmware loaded up on an MX150, to determine if the problem existed back then. i suspect it did not.

16777100 is slightly inaccurate, but probably seemed adequate at the time.

incidentally, a similar problem likely also exists with the bigger micromites that support 64-bit floats. in their case the problem exists for floats between (i think!) 0x10000000000000 and 0x1FFFFFFFFFFFFF, as well as the corresponding negative range.

has geoff or peter noticed this thread? i fear it may have been overlooked as i started out without having the full story worked out - initially i knew very little about binary floating point formats.


cheers,
rob   :-)
Edited 2020-05-11 12:33 by robert.rozee
 
robert.rozee
Guru

Joined: 31/12/2012
Location: New Zealand
Posts: 2294
Posted: 10:54am 11 May 2020
Copy link to clipboard 
Print this post

can confirm the same behaviour is present in 4.5D. it is harder to expose, although does shows up in hex$().


cheers,
rob   :-)
 
Geoffg

Guru

Joined: 06/06/2011
Location: Australia
Posts: 3167
Posted: 12:06pm 11 May 2020
Copy link to clipboard 
Print this post

This has turned out to be an amazingly complex issue.

Thanks for all the hard work, I will implement the fix in the next Micromite release and from there it will make its way into Peter's many releases.

Rob did some amazing detective work digging through the source so now I know exactly what change to make.  Thanks Rob.

Geoff
Geoff Graham - http://geoffg.net
 
JohnS
Guru

Joined: 18/11/2011
Location: United Kingdom
Posts: 3678
Posted: 01:51pm 11 May 2020
Copy link to clipboard 
Print this post

Beaten me to it - finally found it's meant to be IEEE 754 as shown in Microchip info

(So yes there should be a 24-bit mantissa and thus Rob's found a bug.)

John
Edited 2020-05-11 23:51 by JohnS
 
zeitfest
Guru

Joined: 31/07/2019
Location: Australia
Posts: 396
Posted: 11:37pm 11 May 2020
Copy link to clipboard 
Print this post

Using a standard 32 bit float : (not Basic)

     PROGRAM z
C
     REAL x
     INTEGER*4 c
     x = 8388609.0
     c = IFIX ( x )
     PRINT x
     PRINT c
     END
\

8388609.000000
8388609

Edited 2020-05-12 09:39 by zeitfest
 
robert.rozee
Guru

Joined: 31/12/2012
Location: New Zealand
Posts: 2294
Posted: 07:02am 12 May 2020
Copy link to clipboard 
Print this post

  zeitfest said  Using a standard 32 bit float : (not Basic)

     PROGRAM z
C
     REAL x
     INTEGER*4 c
     x = 8388609.0
     c = IFIX ( x )
     PRINT x
     PRINT c
     END
\

8388609.000000
8388609


try using c = IFIX ( x + 0.5 )

here is an example written in pascal (lazarus/fpc), running on a 64-bit linux system:

program project1;

{$R *.res}

var i,z:integer;           // 64-bit integer
      a:single;            // 32-bit float

begin
 a:=8388600;
 i:=trunc(a);
 z:=i+16;

 repeat
   write(i:8, trunc(a+0.5):16);
   if i<>trunc(a+0.5) then writeln('error':8)
                      else writeln;
   a:=a+1.0;
   i:=i+1
 until i>z
end.

8388600         8388600
8388601         8388601
8388602         8388602
8388603         8388603
8388604         8388604
8388605         8388605
8388606         8388606
8388607         8388607
8388608         8388608
8388609         8388610   error
8388610         8388610
8388611         8388612   error
8388612         8388612
8388613         8388614   error
8388614         8388614
8388615         8388616   error
8388616         8388616


cheers,
rob   :-)
Edited 2020-05-12 17:07 by robert.rozee
 
JohnS
Guru

Joined: 18/11/2011
Location: United Kingdom
Posts: 3678
Posted: 09:01am 12 May 2020
Copy link to clipboard 
Print this post

I wrote a C program and it shows that a 32-bit float can hold all integers in the (inclusive) range
-16777216 to 16777216

(without needing any adding or whatever of 0.5 or any other value)

John
Edited 2020-05-12 19:01 by JohnS
 
zeitfest
Guru

Joined: 31/07/2019
Location: Australia
Posts: 396
Posted: 12:20am 13 May 2020
Copy link to clipboard 
Print this post

This is for my own peace of mind than anything else, but it is a good thing to check as suggested. It is on a pic32mx170b, using Microchip's C routines/libraries (which I think are excellent) underneath (ed ie the f77 interpreter uses the standard Microchip C/math libraries)

  Quote  
     PROGRAM z
C
     REAL x
     INTEGER*4 c, i
     i = 1
     x = 8388606.0

     DO WHILE ( i < 8 )

       c = IFIX ( x + 0.5 )
       PRINT x, " ", c

       x = x + 1.0
       i = i + 1
     END DO
     END
\

8388606.000000 8388606
8388607.000000 8388607
8388608.000000 8388608
8388609.000000 8388609
8388610.000000 8388610
8388611.000000 8388611
8388612.000000 8388612

Edited 2020-05-13 10:32 by zeitfest
 
JohnS
Guru

Joined: 18/11/2011
Location: United Kingdom
Posts: 3678
Posted: 06:41am 13 May 2020
Copy link to clipboard 
Print this post

You might like to try it for the full range I posted.

John
 
zeitfest
Guru

Joined: 31/07/2019
Location: Australia
Posts: 396
Posted: 08:09am 14 May 2020
Copy link to clipboard 
Print this post

( carefully avoiding any bugs I wrote     )


     PROGRAM z
C
     REAL x
     INTEGER*4 c, i

     i = 1
     x = 8388606.0
     x = 16777214.0

     DO WHILE ( i < 4 )

       c = IFIX ( x + 0.5 )
       PRINT x, " ", c

       x = x + 1.0
       i = i + 1
     END DO

     i = 1
     x = - 16777214.0

     DO WHILE ( i < 4 )

       c = IFIX ( x - 0.5 )
       PRINT x, " ", c

       x = x - 1.0
       i = i + 1
     END DO


     END
\


16777214.000000 16777214
16777215.000000 16777215
16777216.000000 16777216
-16777214.000000 -16777214
-16777215.000000 -16777215
-16777216.000000 -16777216

Edited 2020-05-14 18:14 by zeitfest
 
     Page 1 of 3    
Print this page
© JAQ Software 2024