Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Line of sight
#16
na_th_an Wrote:I usually code in C, and I can assure you that this code works as intended Smile

Code:
#include <stdio.h>

void main (void) {
    unsigned char lsb = 0;
    unsigned char msb = 60;
    unsigned int address;
    
    unsigned char x = 0, cx = 100;
    int dx;
    
    address = lsb + (msb << 8);
    printf ("%d\n", address);
    
    dx = x - cx;
    printf ("%d\n", dx);
}

15360 and -100 are printed on screen. This works in any C compiler I've tried so far (z88dk and gcc).
This is a bit... let's say complicated: ANSI C does not define a default "int" size (which is supposed to be the default for the target platform). The << operator (shift) are defined on INT types. The question here is: z80 "int" (platform default) should be 8 or 16bit? There's no rule to decide that (e.g. could be 16 bits, because it's the pointer size, or 8 bits because it's the architectural/logical/accumulator size, etc. etc.).

FreeBasic (which I abandoned, because it's not BASIC anymore. It's C++ with BASIC keywords: Pointers??, Classes with multiple inheritance, Templating :??Smile follows C++ (so C) convention (BTW, in a previous message, you declared DX variable as Uinteger in ZX BASIC: Uintegers will be always printed unsigned).

na_th_an Wrote:This should print -100:
Code:
Sub printMe (a as Integer)
       Print a
    End Sub

    Dim as uByte x, cx

    x = 0: cx = 100
    printMe (x-cx)

Another question here:
x - cx is an unsigned overflowed number (-100 = 156). Then you convert this unsigned 156 into an integer. Why should it be -100? You're converting from an unsigned to signed. Converting it to -100 means replicating higher bit (bit 7) to the higher byte.

This is because C (and FreeBASIC, alias C with BASIC keywords), uses a different scheme for dx = x - cx expression:
DX = X - CX
  • ZX BASIC: Evaluate Right Part (X - CX). Coerce (typecast) to Left part type. Store result in Left Part (variable)
  • C/C++/freebasiC++: (I guess) Coerce (typecast) 1st element of Right part (x) to Left part type. Then evaluate expression. Then store in Left part (variable).
I'm not sure on the 2nd point why is it pre-coercing to int. I would like to get more info on this.

NOTE: I was disenchanted with FreeBasic. BASIC Was not designed with that in mind (some of those concepts didn't even exist when BASIC was designed) but for simplicity's sake. Of course there are many things that can be added while preserving simplicity, not only OOP, but iterators, dynamic containers (List, Dictionaries, etc) and much much more. All of them allows more abstraction, or to write a program with fewer lines, and no pointers at all (BTW you can already mimic "pointers" with peek and poke)
Reply
#17
I agree with you about freeBasic. Still, the behaviour is the same in Turbo Basic, Visual Basic, gnu C, Visual C, Turbo C and every typed language I've tried. The expression "type" seems to be "expanded" before evaluating. The C snippet I posted in my previous post works the same in Borland Turbo C for MSDOS (int = 16 bits), gcc for Windows (DevCpp, int = 32 bits), gcc for linux (gnu gcc, int = 32 bits) and z88dk (int = 16 bits). Anyhow, it works exactly the same if you use "short" instead of int, which tends to be 16 bits always.

As far as I know, C coercion works this way: prior to evaluating an expresions, all operands are automaticly type-casted to the width of the "int" type for integer values, or to the "double" type for floating point values. Then, the expresion is evaluated and, finally, the result is automaticly casted to the lvalue type. This is true for C-like languages (which includes C++, Java or C#) and for the Microsoft BASIC standard (and the plethora of derived products).

To further ilustrate such behaviour, I've typed this code and compiled it under linux with gcc:

Code:
#include <stdio.h>

int main (void) {
  unsigned int a1;
  unsigned short a2;
  short a3;
  int a4;
  unsigned char b, c;

  b = 255; c = 255;
  a1 = b + (c << 8);
  a2 = b + (c << 8);
  a3 = b + (c << 8);
  a4 = b + (c << 8);

  printf ("%d, %d, %d, %d\n", a1, a2, a3, a4);
}

Note how a1 = unsigned, 32 bits integer, a2 = unsigned, 16 bits integer, a3 = signed, 16 bits integer. When I compile it and run it, I get:

Code:
65535, 65535, -1, 65535

As expected: during calculations, operands are automaticly casted to the int size (32 bits here), then casted to the lvalue type, and are signed accordingly.

Anyways, this is always your choice, as you are the one who's developing the compiler, but, in my opinion, if the BASIC language was, as you said, designed for simplicity's sake, having to explicitly cast your variables to meet the desired results seems somewhat contradicting. In fact, if C, which is much "lower level" have this feature, BASIC should have it more than anything.

Every typed language/platform I've used along the years behaves this way. And not just C, but BASIC flavours, Pascal, and Java. I even remember that from University, when we were writing our compiler project, that we were explicitly told to take especial care of such cases in the compiler we were writing (which was to compile a random language the teacher designed).

In particular, the case I found when calling a command or a function. I find it very intuitive that, if Draw (for example) allows negative parameters, Draw x-cx,y-cy should work even though every variable involved is unsigned. Specially on a high level language, which I expect to take care of things for me (exactly the way it manages Strings automaticly).

But again, that's a matter of your choice. I just wanted to make my point Smile
Reply
#18
Well, ZX Basics does coercion AFTER the right value has been computed.
E.g. the sentence: (<type> tag follows variable id to describe it)
Code:
LET a<int> = b<int>/c<float>
is computed as follows:
Code:
coerce (expand) b<int> => b<float>
compute tmp<float> = b<float> / c<float>
coerce (truncate) tmp<float> => tmp<int>
store tmp<int> => a<int>
For our case:
Code:
LET a<int> = b<ubyte>/c<ubyte>
Which is done as follows:
Code:
compute tmp<ubyte> = b<ubyte> / c<ubyte>
coerce (expand) tmp<ubyte> => tmp<int>
store tmp<int> => a<int>

It seems that other compilers do:
Code:
coerce (expand) b<ubyte> => b<int>
coerce (expand) c<ubyte> => c<int>
compute tmp<int> = b<int> / c<int>
store tmp<int> => a<int>
However, it seems to me that if the sentence is:
Code:
LET a<float> = b<int>/c<int>
in this case, it performs as I did with ZX Basic :?:

Note: I'm no trying to convince you. I'm trying to understand why it's this way (I always supposed it was as I did!), so I could implement this behavior in ZX Basic. Anyway, using CAST you can enforce this behavior.
Reply
#19
na_th_an Wrote:prior to evaluating an expresions, all operands are automaticly type-casted to the width of the "int" type for integer values, or to the "double" type for floating point values. Then, the expresion is evaluated and, finally, the result is automaticly casted to the lvalue type.

Even z88dk does this? I'm surprised. It would inherently slow down the fastest code if all variables were expanded into something like long type before being dropped back to a byte form. This is particularly crucial on such a small machine as the spectrum - you'd be costing bytes AND time. I think forcing it wish CAST is a reasonable option, generally.

At the very least, let's keep this behaviour command line optional, if the compiler changes - I'd expect smaller, faster code that works fine for most cases to come out of it.
Reply
#20
britlion Wrote:
na_th_an Wrote:prior to evaluating an expresions, all operands are automaticly type-casted to the width of the "int" type for integer values, or to the "double" type for floating point values. Then, the expresion is evaluated and, finally, the result is automaticly casted to the lvalue type.
Even z88dk does this? I'm surprised. It would inherently slow down the fastest code if all variables were expanded into something like long type before being dropped back to a byte form. This is particularly crucial on such a small machine as the spectrum - you'd be costing bytes AND time. I think forcing it wish CAST is a reasonable option, generally.

At the very least, let's keep this behaviour command line optional, if the compiler changes - I'd expect smaller, faster code that works fine for most cases to come out of it.
I think *exactly* the same. Converting to <int> on the Speccy will slow it down. On the other hand, na_th_an, the rule of pre-typecasting to floats is not true.
Try this C code:
Code:
int a = 4, b = 5:
float c = a / b;
c should be 0, not 0.8. Can you check it?
Reply
#21
I meant that floats are casted to doubles, in your example you are using ints and floats. That's not what I said:

Quote:all operands are automaticly type-casted to the width of the "int" type for integer values, or to the "double" type for floating point values

That's called type promotion. Of course, int is not automaticly casted to float 'cause you could lose precision in some cases (as in some integers can't be represented "exactly" in floating point). You have to explicitly cast int to float and float to int. The only automatic "expansion" is between numbers of the same "kind" (integers with integers, reals with reals). This done 'cause it's the best case scenario for calculations. If overflow happens, it should happen at the end of the evaluation, and not in between, when possible.

And yes, z88dk does this, just check the examples I posted. If the result is to be stored on an integer variable, or is to be passed to a function with an integer parameter, byte values are automaticly type-casted to int. And it isn't such a big deal, taking in account that the 30+ games we have released run quite nicely Wink

Anyhow, I just thought that this was somehow "natural" (for example, that you could shift left an unsigned byte not having to cast it to int yourself), at least it's what I'm used to (and I mean, I've been coding for 25 years now), and I thought that a compiler for a language like BASIC (which is considered to be a high level language) should implement. After all, it automaticly handles Strings already. Following the same logic, you could let us have to deal with strings ourselves, saving time and space in many cases Wink

I've been using the compiler since it's out so it's not a big deal for me. I just thought that it may be confusing to newcomers who are used to code in other languages. Specially in the case of calling a reserved word such as Draw or Print. I bet that anybody doing a Print a-b with a = 5 and b = 7 would expect a -2 to appear on screen, regardless of the type of a and b, and not a 254. A 254 is what I would expect to appear in an unsigned 8 bits "c" variable if a did a c=a-b, not as a result of printing a simple expression. It's what I find "natural".
Reply
#22
na_th_an Wrote:That's called type promotion. Of course, int is not automaticly casted to float 'cause you could lose precision in some cases (as in some integers can't be represented "exactly" in floating point). You have to explicitly cast int to float and float to int. The only automatic "expansion" is between numbers of the same "kind" (integers with integers, reals with reals). This done 'cause it's the best case scenario for calculations. If overflow happens, it should happen at the end of the evaluation, and not in between, when possible.
That was the info I was trying to find out. Thanks! :wink:
Reply
#23
britlion Wrote:I've added distance.bas to the ZX Basic Wiki in the library section. May it be useful in your games!
I've just updated distance.bas, based on Suggestions by Alcoholics Anonymous. He sped it up by around 12.5% - which could be important in games. Shorter, too...

<!-- w --><a class="postlink" href="http://www.boriel.com/wiki/en/index.php/ZX_BASIC:Distance.bas">www.boriel.com/wiki/en/index.php/ZX_BASIC:Distance.bas</a><!-- w -->
Reply
#24
britlion Wrote:
britlion Wrote:I've added distance.bas to the ZX Basic Wiki in the library section. May it be useful in your games!
I've just updated distance.bas, based on Suggestions by Alcoholics Anonymous. He sped it up by around 12.5% - which could be important in games. Shorter, too...

<!-- m --><a class="postlink" href="http://www.boriel.com/wiki/en/index.php/ZX_BASIC:Distance.bas">http://www.boriel.com/wiki/en/index.php ... stance.bas</a><!-- m -->
Awesome! Confusedhock:
Reply
#25
the distance.bas does 250 distance checks in 8 seconds? and the standard rom does it in 122 minutes??
Reply
#26
Oh no. It does more like 65,000 (it's actually doing 250 loops of 250 values) of them in under 7 seconds with the new version, actually.

and yes, if you did it in basic, using the standard formula of SQR (x^2 + y^2) you'd definitely be well over 2 hours. Probably a LOT more than 2 hours, given the other overheads of basic.

This doesn't actually calculate SQR (X^2+y^2); but does it a completely different way - and it only gives an estimate, not a perfect result. This is probably "close enough" for most games, and as you can see, a lot faster.

You can get a guaranteed "closer than 1" answer, if you use the iSqrt routine to calculate isqrt(x^2+y^2) - because isqrt gives you the whole number part of the square root. (so isqrt(9)=3, but isqrt(10)=3 as well). Using this routine tested in at about 50 seconds for the 65,000 tests.

Finally, there's fsqrt in the wiki library. This did the job in 44 minutes; about 1/3 the time of the ROM sqr routine. fsqrt gives exactly the same answer as the rom routine - in fact it even uses the rom calculator, but it uses a much better method of getting a square root. You get the full floating point answer in something like 1/3 to 1/6 of the time.

The real problem is the ROM SQR routine is just very inefficient. But, to be fair to the zx spectrum designers, it's really small too. 7 bytes, if I recall.
Reply
#27
thats pretty fast Confusedhock:
could be used for real time games even
Reply
#28
In fact we have used it (the original C version I posted earlier) in a game we have yet to release in the IA of one kind of enemy: if the player is close enough, such kind of enemy start chasing the player. If the player "escapes" (goes a little bit further) the enemy stops chasing and return to its spawning spot. And it works a threat! Smile
Reply
#29
slenkar Wrote:thats pretty fast Confusedhock:
could be used for real time games even

As NA_TH_AN points out, that was exactly the point!

iSqrt and distance are definitely aimed at games - there are several routines in the library that are clearly optimised for speed, not size.
Reply


Forum Jump:


Users browsing this thread: 2 Guest(s)