[RESOLVED] Are some basic math functions missing in AGS

Started by Joacim Andersson, Mon 25/11/2024 14:12:49

Previous topic - Next topic

Joacim Andersson

The Maths object has all the trigonometry functions, and you can raise a number to a power, logarithmic functions, and the like. Still, I'm missing some fundamental functions like Abs(), Max(), and Min() to get the absolute number and the largest or smallest number of two values (calculations). Are they missing, or am I just missing something in the documentation?

Crimson Wizard

They were never added.

On another hand it's easy to write them in script. People were writing modules for these math functions that they can use in multiple projects.

eri0o

#2
Here

Code: ags
import float AbsF(float a);
import float MaxF(float a, float b);
import float MinF(float a, float b);
import float ClampF(float v, float min, float max);

Code: ags
float AbsF(float a)
{
  if(a<0.0) return -a;
  return a;
}

float MaxF(float a, float b)
{
  if (a > b)
    return a;
  return b;
}

float MinF(float a, float b)
{
  if (a < b)
    return a;
  return b;
}

float ClampF(float v, float min, float max)
{
  return MinF(max, MaxF(v, min));
}

The Engine Script API is an API to the engine, where Script code can implement things. For small things I don't think it's worth it to go through the Engine Script API - haven't actually timed but my guess is that the in script implementation will be faster.

Crimson Wizard

I would not mind if these are added to script api, it's just that nobody bothered to do this.

Something to keep in mind though, since ags does not support generics or templates, there have to be separate variants for ints and floats.

eri0o

Maths is only floats, so in theory they would have to be floats.

Crimson Wizard

#5
Quote from: eri0o on Mon 25/11/2024 18:47:27Maths is only floats, so in theory they would have to be floats.

There's no restriction for Maths class to work only with floats. It has only functions with float parameters now by coincidence.
Functions like Max, Min, Clamp and Abs need to have variants for ints too, because there's no implicit cast between int and float in AGS, and users will have to call IntToFloat in order to simply use Max, which is annoying.


EDIT:
There was this module by monkey0506:
https://www.adventuregamestudio.co.uk/forums/modules-plugins-tools/module-mathsplus-v1-0-abs-absint-ceil-floor-max-maxint-min-minint/

He made Abs, Max and Min to work with floats, and AbsInt, MaxInt and MinInt to work with ints.
I argued that majority of users would need these for integers more often, and suggested to do it other way around (e.g. Abs / AbsF, Min / MinF etc).
He said that it's for consistency with existing function that have float parameters.
(this discussion may be found in the linked thread)
This may be polled again among users here.


The naming situation may improve if we support function overloading (i had plans to look into this soon, unless fernewelten does this first).

Joacim Andersson

#6
Of course, I am aware that implementing these functions yourself is effortless. I was just wondering why these simple functions didn't exist already.

Quote from: eri0o on Mon 25/11/2024 18:47:27Maths is only floats, so in theory they would have to be floats.

I don't think I agree with that statement, after all, most functions in AGS deal with int rather than float. Which in some cases is rather annoying, in the game I'm currently developing I wanted the Wait function to wait for a fraction of a second or something like a second and a half, and I found myself having these kinds of calls.
Code: ags
FloatToInt(IntToFloat(GetGameSpeed()) * 0.5 + someValue);

Quote from: Crimson Wizard on Mon 25/11/2024 19:11:24there's no implicit cast between int and float
Yes, why is there none? I understand why a float shouldn't be implicitly cast as an int, but why can't an int be implicitly cast as a float? After all, all ints fit within a float.

Quote from: Crimson Wizard on Mon 25/11/2024 19:11:24The naming situation may improve if we support function overloading (i had plans to look into this soon, unless fernewelten does this first).
Function overloading would be a great addition to the language, and in this case it would solve the lack of generics/templates in the language.

Crimson Wizard

Quote from: Joacim Andersson on Mon 25/11/2024 20:46:48Yes, why is there none? I understand why a float shouldn't be implicitly cast as an int, but why can't an int be implicitly cast as a float?

I have two guesses:
- the original compiler and script executor were limited in this regard, and no one bothered to fix this still;
- original author of AGS have decided that implicit casts will confuse beginners with suddenly changing values. Majority of users in AGS are people who barely have any programming experience, or not at all, and a number of design choices were seemingly made having that in mind.

Joacim Andersson

Quote from: Crimson Wizard on Mon 25/11/2024 20:58:11- original author of AGS have decided that implicit casts will confuse beginners with suddenly changing values. Majority of users in AGS are people who barely have any programming experience, or not at all
I would argue that, for a non-programmer, it's even more confusing why 2+0.5 throws an error than not.

Snarky

Quote from: Joacim Andersson on Mon 25/11/2024 20:46:48After all, all ints fit within a float.

That's not true. Both int and float are 32-bit types, so both have just as many possible different values (2^32). Since many of those possible float values are not integers, it follows that not all ints can be stored as a float. In other words, casting from int to float will sometimes produce the wrong value (either a different integer or a non-integer).

Crimson Wizard

#10
Quote from: Snarky on Mon 25/11/2024 22:08:16
Quote from: Joacim Andersson on Mon 25/11/2024 20:46:48After all, all ints fit within a float.

That's not true. Both int and float are 32-bit types, so both have just as many possible different values (2^32). Since many of those possible float values are not integers, it follows that not all ints can be stored as a float. In other words, casting from int to float will sometimes produce the wrong value (either a different integer or a non-integer).

BTW we have a specific limits of this mentioned in the manual, after a user's request:
https://adventuregamestudio.github.io/ags-manual/SystemLimits.html#integers-and-floats-limits

QuoteIn AGS integers and floats are 32-bit.

Integers are signed and can be from -2147483648 to +2147483647 (from -2^31 to 2^31-1)
Floats are a bit more complicated: an IEEE 754 32-bit base-2 floating-point variable has a maximum value of (2 − 2^(-23)) × 2^127 ≈ 3.4028235 × 10^38.
A float should then be able to reliably represent integer values between -16777216 and 16777216 (from -2^24 to 2^24).

In brief, integers have a strict min and max limit, by exceeding which they "wrap" their values (from positive to negative and vice-versa). Floats, on other hand, do not have a "hard" limit, instead they begin to loose precision as their integer part reaches high numbers. Occasional "even" values may still get stored correctly, but majority of values between them are not guaranteed to be precise.




Quote from: Joacim Andersson on Mon 25/11/2024 20:46:48in the game I'm currently developing I wanted the Wait function to wait for a fraction of a second or something like a second and a half, and I found myself having these kinds of calls.
Code: ags
FloatToInt(IntToFloat(GetGameSpeed()) * 0.5 + someValue);

Just a note, this looks like a good case for a global helper function that converts N float seconds to an number of game frames.

I think Tween module has this in its interface.

eri0o

ints secretly cast to float and vice versa can cause hard to figure it out bugs. I would prefer if they aren't silently casted ever. I would also argue that I prefer to never pay for things I don't use, a lot of things I have to do a lot of times per frame and AGS Script isn't super fast, so I prefer to not add things that could slow it down - more API calls.

Overall it's possible to just write int math when dealing with int, and avoiding hitting the engine when possible - if you aren't going to do a lot on the native side in the engine you will pay more in the call than you would if you just wrote in AGS Script.

Crimson Wizard

Quote from: eri0o on Mon 25/11/2024 23:39:53I would also argue that I prefer to never pay for things I don't use, a lot of things I have to do a lot of times per frame and AGS Script isn't super fast, so I prefer to not add things that could slow it down - more API calls.

Which unnecessary API calls would be there, what case are you refering to?
BTW the conversion operator may be faster than a function call in ags script, being only 1 opcode.

Joacim Andersson

Quote from: Snarky on Mon 25/11/2024 22:08:16
Quote from: Joacim Andersson on Mon 25/11/2024 20:46:48After all, all ints fit within a float.

That's not true. Both int and float are 32-bit types, so both have just as many possible different values (2^32).
That's technically correct, but all signed ints still fit inside of a float, but you might get some rounding errors. But you will always get some rounding errors with floats.

eri0o

A conversion operator would be a hidden additional operator afaict from the discussion. I don't like this approach at all. And I don't think we should add opcodes without thinking too, that loop is a critical part that affects everything.

Crimson Wizard

#15
Quote from: eri0o on Tue 26/11/2024 00:07:39A conversion operator would be a hidden additional operator afaict from the discussion. I don't like this approach at all. And I don't think we should add opcodes without thinking too, that loop is a critical part that affects everything.

Conversion operator may be explicit or implicit, or either, or depending on a case etc, that's a choice in programming language design.

Opcodes are not to be added to random places in program where they are not needed, they are to be added where they are obviously needed - in the current context that would be places where you would use IntToFloat and FloatToInt. The conversion operator would be a replacement for these function calls, not something extra on top of them (or anything else).

For instance we could replace IntToFloat with (float) cast operator, which would be few times faster than a function call, being just 1 opcode. That is just an example, to illustrate what I mean.


Also, I did not just randomly fantasized this idea about opcodes, this was a logical conclusion when I tried to think how it could work.
Then I found examples of conversion opcodes in other programming languages which create bytecode. For example there's a list of opcodes in Java:
https://javaalmanac.io/bytecode/mnemonics/
Quotei2b   145   Convert int to byte
i2c   146   Convert int to char
i2d   135   Convert int to double
i2f   134   Convert int to float
i2l   133   Convert int to long
i2s   147   Convert int to short

Snarky

In Java, when you create a floating-point literal like 0.5 it is a double by default, and double is the recommended type for floating point variables. A double can store any int value exactly, so implicitly "promoting" int values to doubles in mixed expressions is much less problematic than it would be in AGS (which doesn't have double).

Crimson Wizard

#17
Quote from: Snarky on Tue 26/11/2024 06:28:50In Java, when you create a floating-point literal like 0.5 it is a double by default, and double is the recommended type for floating point variables. A double can store any int value exactly, so implicitly "promoting" int values to doubles in mixed expressions is much less problematic than it would be in AGS (which doesn't have double).

Aright, that makes sense. So having implicit conversion in AGS may not be a good thing.

But I brought Java up as an example of a type conversion opcode, not an example of implicit conversion.

jahnocli

Noob here. Using Windows 10. I was looking for the 'modulus' operator, and couldn't find it. Is that missing as well?
Life is a puzzle, a quest and an adventure


SMF spam blocked by CleanTalk