Author Topic: Can you please show me where the error is? I do not know why wrong values come  (Read 164 times)

Code: [Select]
REDIM P(0, 0) AS INTEGER
a& = _NEWIMAGE(800, 600, 256)
SCREEN a&
text$ = "Qb64"
TextToArray P(), text$


MaxX = UBOUND(p, 1): MaxY = UBOUND(p, 2)
MinX = LBOUND(p, 1): MinY = LBOUND(p, 2)
PRINT
PRINT "new array values:"; MaxX, MaxY, MinX, MinY 'array IS resized, but contains nonsense. the expected output are two same inscriptions

FOR y = MinY TO MaxY
    PRINT
    FOR x = MinX TO MaxX
        IF P(x, y) THEN COLOR 15 ELSE COLOR 8
        PRINT P(x, y);
NEXT x, y



SUB TextToArray (array( x , y) AS INTEGER, text AS STRING)
    v& = _NEWIMAGE(LEN(text$) * 8, 16, 256)
    _DEST v&
    _PRINTSTRING (0, 0), text$, v&
    _DEST 0
    _SOURCE v&
    FOR y = 0 TO 15
        PRINT
        FOR x = 0 TO 8 * LEN(text$) - 1
            REDIM _PRESERVE array(x, y) AS INTEGER
            IF POINT(x, y) THEN array(x, y) = 1
            '  _DEST 0
            IF array(x, y) THEN COLOR 15 ELSE COLOR 8
            PRINT array(x, y);
    NEXT x, y
    _DEST 0: _SOURCE 0
END SUB

Coding is relax (At least sometimes)

Online FellippeHeitor

  • QB64 Developer
  • LET IT = BE
    • QB64.org
It's a known bug, Petr. No solution yet.

https://github.com/Galleondragon/qb64/issues/27

I see. And will it work if I type it using TYPE?
Coding is relax (At least sometimes)

So solved, with type it works:

Code: [Select]
TYPE P
    X AS INTEGER
    Y AS INTEGER
    V AS INTEGER
END TYPE
REDIM P(0) AS P
a& = _NEWIMAGE(800, 600, 256)
SCREEN a&
text$ = "Qb64"
TextToArray P(), text$


'MaxX = UBOUND(p, 1): MaxY = UBOUND(p, 2)
'MinX = LBOUND(p, 1): MinY = LBOUND(p, 2)
'PRINT
'PRINT "new array values:"; MaxX, MaxY, MinX, MinY 'array IS resized, but contains nonsense. the expected output are two same inscriptions

i = 0
FOR y = 0 TO P(0).Y - 1
    PRINT
    FOR x = 0 TO P(0).X - 1
        IF P(i).V THEN COLOR 15 ELSE COLOR 8
        PRINT P(i).V;
        i = i + 1
NEXT x, y



SUB TextToArray (array() AS P, text AS STRING)
    v& = _NEWIMAGE(LEN(text$) * 8, 16, 256)
    _DEST v&
    _PRINTSTRING (0, 0), text$, v&
    _DEST 0
    _SOURCE v&
    FOR y = 0 TO 15
        PRINT
        FOR x = 0 TO 8 * LEN(text$) - 1
            REDIM _PRESERVE array(i) AS P
            IF POINT(x, y) THEN array(i).V = 1
            '  _DEST 0
            IF array(i).V THEN COLOR 15 ELSE COLOR 8
            PRINT array(i).V;
            i = i + 1
    NEXT x, y
    array(0).X = x
    array(0).Y = y
    _DEST 0: _SOURCE 0
END SUB


Thank for help, Fellippe.
Coding is relax (At least sometimes)

Online FellippeHeitor

  • QB64 Developer
  • LET IT = BE
    • QB64.org
My pleasure.

BTW, if you want a map of all builtin characters, I took them from QB64's internals in this sample, so you don't need to use temp images or POINT:

https://github.com/FellippeHeitor/Snippets/blob/master/largeFont16.bas

Thank you, I'll look at it right away.
Coding is relax (At least sometimes)

Yeah, that's exactly what I need. And it will be even faster. Great. Thanks!
Coding is relax (At least sometimes)

What's going on here is nothing more than what you'd normally expect to encounter, with this code.  It's not a bug; it's just the way memory basically works.

Let's see if I can explain what's going on under the hood for you...

Let's take an array of 2x4 dimensions. 

DIM a(1 TO 2, 1 TO 4)

Now, let's fill that array with data from 1 to 8...

FOR x = 1 TO 2
    FOR y = 1 to 4
        c = c + 1
        a(x,y) = c
NEXT y, x


Now, since memory is stored in a single block of uninterrupted data, the way this would look in memory is "12345678"

If we were to graph it to X/Y coordinates, the way it'd look would be like this:
15
26
37
48

X is across, Y is down, and it's stored in memory in a TOP to BOTTOM, LEFT TO RIGHT (Y,X) format.

Now, if we add another column of data, (REDIM _PRESERVE a(1 TO 3, 1 TO 4)),  what we'd see would be something similar to this in memory "123456780000"

Graphing this to X/Y coordinates, what we'd have would be something which looks like the following:
150
260
370
480

Notice that in this case X1, Y1 is STILL 1.   X1, y4 is STILL 4.   Our data remains the same and unchanging, with new data placed at the end of it.  _PRESERVE has worked perfectly as anybody would imagine it should, in this case.

Now, let's take that original 2x4 array and instead of adding an column, let's add a row:   REDIM _PRESERVE a(1 TO 2, 1 TO 5).   

As a memory block, we'd STILL preserve the old data "12345678" and we'd add space for the new data "00", which would sit in memory as "1234567800".

Now, the issue here is how that preserved memory would now map to our array.  Remember, we index(read) it UP TO DOWN, LEFT TO RIGHT, so what it'd look like would be:
16
27
38
40
50

X1, Y1 is still 1.  X4, Y4 is still 4....   The thing is, X1, Y5 is NOW 5..   X2, Y1 is no longer 5; it now points to 6...

It's at this point that people start to scratch their heads and say, "What the heck happened to my data??  It's all scrambled!

Except it's NOT.  It's still preserved as that same block of 1-8, with two new 0s added to the end of it.   The thing that we've changed is our INDEX system which we use to refer to that data.   

IF somebody needs to increase rows instead of columns, REDIM _PRESERVE *isn't* going to be the tool for the job.  All it basically does is keep the old array exactly as it was, and then add (or remove) space to the end of it.

And, "1234056780" is a much different dataset than "1234567800" is....

If your data starts as "12345678" in memory, ALL REDIM _PRESERVE will ever do is either add blank spaces to the end of that data (such as "12345678000000000000"), or else it'll remove a few spaces from memory (such as "1234"). 

It's NOT going to shuffle memory back and forth and insert gaps as necessary for you to keep the indexes the same -- and thank goodness it doesn't!!  Can you imagine the hit to performance EVERY program would suffer, if that was the default behavior for _PRESERVE??   It'd make the process so slow that it'd generally be worthless for many common needs.

If you need to REDIM multidimensional arrays, and keep the indexes exactly as they are, you'll have to write your own routine to do that.  Or -- if at all possible -- structure your data so you can add columns to the end of your data, instead of adding rows.   (Change Y, but leave X alone in an array dimmed in an (X,Y) format.)

*********************
*********************

And that, is basically the answer to your, "Can you please show me where the error is? I do not know why wrong values come."

From the link (https://github.com/Galleondragon/qb64/issues/27) which tries to call it a bug report, let me use it as an example to show you what's going on here:

Code: [Select]
REDIM TEST(1 TO 2, 1 TO 3) AS INTEGER
TEST(1, 1) = 100
TEST(1, 2) = 200
TEST(1, 3) = 300
TEST(2, 1) = 400
TEST(2, 2) = 500
TEST(2, 3) = 600


DIM m AS _MEM
m = _MEM(TEST())
PRINT "ORIGINAL, in memory:"
FOR i = 0 TO 5
    PRINT _MEMGET(m, m.OFFSET + 2 * i, INTEGER),
NEXT
PRINT
PRINT "AS X/Y:"
FOR x = 1 TO 2
    FOR y = 1 TO 3
        PRINT TEST(x, y),
    NEXT
    PRINT
NEXT


PRINT
PRINT


REDIM _PRESERVE TEST(1 TO 3, 1 TO 3) AS INTEGER
m = _MEM(TEST())


PRINT "REDIMMED, in memory:"
FOR i = 0 TO 8
    PRINT _MEMGET(m, m.OFFSET + 2 * i, INTEGER),
NEXT
PRINT
PRINT "AS X/Y:"
FOR x = 1 TO 3
    FOR y = 1 TO 3
        PRINT TEST(x, y),
    NEXT
    PRINT
NEXT

END

Where the problem really presents itself is in the user's representation of data.

Listed as X/Y, we see a list of:
TEST(1, 1) = 100
TEST(1, 2) = 200
TEST(1, 3) = 300
TEST(2, 1) = 400
TEST(2, 2) = 500
TEST(2, 3) = 600

BUT, it's stored in memory in Y/X format, which would be better represented as the following list:
TEST(1, 1) = 100
TEST(2, 1) = 400
TEST(1, 2) = 200
TEST(2, 2) = 500
TEST(1, 3) = 300
TEST(2, 3) = 600

And, when we REDIM _PRESERVE, we simply add space to the end of that data in memory, or else we remove space.  In this case, we add space to get:  "100,400,200,500,300,600,0,0,0"

And that maps out with our new  indexes to become:
100,500,0
400,300,0
200,600,0


It's not actually a bug.  It's all working as intended, even if the user in this case expects it to do something much more complicated for them, instead.

Hi Steve. Thank you for exhausting answer. I do not understand why the _PRESERVE field is not memorized as well as the field over REDIM (x, y), but it is written differently. Well, I avoid to PRESERVE for the multidimensional field, I wanted to make the effort easier and not complicate. I finally solved it completely without PRESERVE.
Coding is relax (At least sometimes)

I do not understand why the _PRESERVE field is not memorized as well as the field over REDIM (x, y), but it is written differently.

The reason WHY is for performance's sake. 

Imagine a DIM array(1 TO 2, 1 TO 4), with the data 1-8 stored in it, as I mentioned above.  In memory, it'd be stored as basically "1,2,3,4,5,6,7,8" (though without commas, and in Binary format).

Now, REDIM _PRESERVE array(1 TO 3, 1 TO 4)...

The way it is now, _PRESERVE preserves the data as it currently exists by basically following these steps:

1) Create a new memblock large enough to store the new array.  (3 * 4 * element size, which varies based on data type, with default SINGLE being 4 bytes, in this case.)
2) Copy the data from the old array memblock directly to the new array memblock.
3) Free the old array.
4) Make array() point to the new memblock instead of the old one.

0)So "12345678" is our original 2* 4 data.
1)We create a "000000000000" new chunk of data in memory to hold 12 elements.
2)We _PRESERVE the old data and copy it over to become "123456780000".
3)Free the old memory
4)and change pointers.

Simple, fairly fast, and straight forward.

To make _PRESERVE work across multiple dimensions, think of what it'd have to do:
1) Create a new array of proper size, as before. "000000000000"
2) Calculate how large each chunk of "preserved memory" must be, and move it to the proper place inside the new array....

This step means we move "12" first (one old dimension), so we have "120000000000" in the new array.

Then we calculate the proper position for the next position and move the next chunk of substainable data (the "34"), so we have "120340000000".

Continue to calculate and copy....
"120340560000"
"120340560780"

3)Free old array from memory.
4)Swap pointers.

As you can see, there's quite a bit more involved to preserve the data so it's "120340560780", instead of just "123456780000"...

Now, imagine the time this REDIM _PRESERVE process would take, if we REDIM _PRESERVE array(10000,10000,10000,10000).....

How many calculations would that be?  How many copies of chunks of memory into their new, proper positions?

I wouldn't even want to take a guess at how large a performance hit such a routine would cause for our programs!

I really don't see how any routine could calculate those "gaps" in the old data and position it "properly" in the new data, without the performance being so abysmal as to render it worthless, for all intents and purposes.

If written into the language itself, QB64 _PRESERVE would have to be flexible enough to check and move data properly for ALL cases.  It'd need to work for array(a,b,c,d,e,f,g,h,i,j), just as much as it would for a simple array(x,y), and the overhead would be astoundingly terrible.

Personally, I feel it's much better to leave it as it is, and let the user roll their own solution.  Then, they can search for a method which will give them the results they need (as you did above), without adversely destroying the performance they'd expect.

Hi Petr
Hi Fellippe
Hi Steve

1.
yes I remember still this issue that I have talked with Steve in the other forum. www.QB64.net....
What do you think about the original website?

2.
I have understood the memory structure that brings to these results using _PRESERVE...but I remember also how  this new knowledge let me seeing _PRESERVE as a poor useful option....at first glance, but the issue of performance talked by Steve are very important.
The reality is that memory RAM is managed as a single line along that the data are put by ram manager.
However we must agree that at this time of development of QB64 _PRESERVE is a good option/feature for monodimensional arrays on which it works very well!
2.1 so if it is useful for his goals,  the coder can use a set of monodimensional array to play with _PRESERVE  feature of REDIM
2.2 so if it is useful for his goals,  the coder can use a monodimensionale UDT array like has done Petr to play with _PRESERVE feature of REDIM

3.
about

Quote
To make _PRESERVE work across multiple dimensions, think of what it'd have to do:
1) Create a new array of proper size, as before. "000000000000"
2) Calculate how large each chunk of "preserved memory" must be, and move it to the proper place inside the new array....

This step means we move "12" first (one old dimension), so we have "120000000000" in the new array.

Then we calculate the proper position for the next position and move the next chunk of substainable data (the "34"), so we have "120340000000".

Continue to calculate and copy....
"120340560000"
"120340560780"

3)Free old array from memory.
4)Swap pointers.
IMHO I think that this algorithm can become more efficient with some modifications using mathematical models...
but this my sentence lasts a sentence because I don't know how parser of QB64 is written/works, and how it manages REDIM statements.


4.
about
Quote
Personally, I feel it's much better to leave it as it is, and let the user roll their own solution.  Then, they can search for a method which will give them the results they need (as you did above), without adversely destroying the performance they'd expect.

Well if I agree here at the same time I have found a good reason to prefer FOR to _MEM in copying array issue.
To read more please go to the other thread  FOR vs _MEM

Thanks to read, to talk and to feedback.


Hi TempoDiBasic, I've seen your arrays experiments using DIM and _MEM but I have not had time to try it out myself. I did a lot of different things. As soon as more time is available, I like to look at it closely.
Coding is relax (At least sometimes)

Hi Petr
here other further experiments with array multidimensional and REDIM _PRESERVE
going forward understandig  RAM managment in array in QB64

please run these two examples...
the first experiment uses Steve's example to see how data are put in the ram

Code: [Select]
REDIM TEST(1 TO 2, 1 TO 3) AS INTEGER
TEST(1, 1) = 100
TEST(1, 2) = 200
TEST(1, 3) = 300
TEST(2, 1) = 400
TEST(2, 2) = 500
TEST(2, 3) = 600


DIM m AS _MEM
m = _MEM(TEST())
PRINT "ORIGINAL, in memory:"
FOR i = 0 TO 5
    PRINT _MEMGET(m, m.OFFSET + 2 * i, INTEGER); "-";
NEXT
PRINT
PRINT "AS X/Y"
FOR x = 1 TO 2
    FOR y = 1 TO 3
        PRINT TEST(x, y); "  -  ";
    NEXT
    PRINT "//";
NEXT
PRINT
PRINT "AS Y/X"
FOR y = 1 TO 3
    FOR x = 1 TO 2
        PRINT TEST(x, y); "  -  ";
    NEXT
    PRINT "//";
NEXT


PRINT
PRINT


REDIM _PRESERVE TEST(1 TO 3, 1 TO 3) AS INTEGER
m = _MEM(TEST())


PRINT "REDIMMED, in memory:"
FOR i = 0 TO 8
    PRINT _MEMGET(m, m.OFFSET + 2 * i, INTEGER); "_";
NEXT
PRINT
PRINT "AS X/Y"
FOR x = 1 TO 3
    FOR y = 1 TO 3
        PRINT TEST(x, y); "  -  ";
    NEXT
    PRINT "//";
NEXT
PRINT
PRINT "AS Y/X"
FOR y = 1 TO 3
    FOR x = 1 TO 3
        PRINT TEST(x, y); "  -  ";
    NEXT
    PRINT "//";
NEXT

PRINT " It seems that outer/newer index follows ram structure of the array..."
END


this experiment uses a 3 dimensional array of integer
Code: [Select]
REDIM TEST(1 TO 2, 1 TO 3, 1 TO 2) AS INTEGER
TEST(1, 1, 1) = 100
TEST(1, 1, 2) = 200
TEST(1, 2, 1) = 300
TEST(1, 2, 2) = 400
TEST(1, 3, 1) = 500
TEST(1, 3, 2) = 600
TEST(2, 1, 1) = 700
TEST(2, 1, 2) = 800
TEST(2, 2, 1) = 900
TEST(2, 2, 2) = 901
TEST(2, 3, 1) = 902
TEST(2, 3, 2) = 903


DIM m AS _MEM
m = _MEM(TEST())
PRINT "ORIGINAL, in memory:"
FOR i = 0 TO 11
    PRINT _MEMGET(m, m.OFFSET + 2 * i, INTEGER); "-";
NEXT
PRINT
PRINT "AS X/Y/Z"
FOR x = 1 TO 2
    FOR y = 1 TO 3
        FOR z = 1 TO 2
            PRINT TEST(x, y, z); "  -  ";
        NEXT
        PRINT "//";
    NEXT
    PRINT "^^";
NEXT
PRINT
PRINT "AS Z/Y/X"
FOR z = 1 TO 2
    FOR y = 1 TO 3
        FOR x = 1 TO 2
            PRINT TEST(x, y, z); "  -  ";
        NEXT
        PRINT "//";
    NEXT
    PRINT "^^";
NEXT


PRINT
PRINT


REDIM _PRESERVE TEST(1 TO 3, 1 TO 3, 1 TO 3) AS INTEGER
m = _MEM(TEST())


PRINT "REDIMMED, in memory:"
FOR i = 0 TO 26
    PRINT _MEMGET(m, m.OFFSET + 2 * i, INTEGER); "_";
NEXT
PRINT
PRINT "AS X/Y/Z"
FOR x = 1 TO 3
    FOR y = 1 TO 3
        FOR z = 1 TO 3
            PRINT TEST(x, y, z); "  -  ";
        NEXT
        PRINT "//";
    NEXT
    PRINT "^^";
NEXT
PRINT
PRINT "AS Z/Y/X"
FOR z = 1 TO 3
    FOR y = 1 TO 3
        FOR x = 1 TO 3
            PRINT TEST(x, y, z); "  -  ";
        NEXT
        PRINT "//";
    NEXT
    PRINT "^^";
NEXT

PRINT " ram manager seems to follow outer/new index of a multidimensional array..."
END


Thank's to read, to share and to feedback
QB64 is great ! And moreover QB64's community!