thomasokken / free42

Free42 : An HP-42S Calculator Simulator
https://thomasokken.com/free42/
GNU General Public License v2.0
280 stars 54 forks source link

extend log1p, expm1 for complex type #55

Closed achan001 closed 8 months ago

achan001 commented 1 year ago

Hi, Thomas Okken

This is my C implementation (for lua) Some calculations is to preserve sign of ±0. You can ignore them for Free42/Plus42

static Complex clog1p(Complex z) {
  Complex y = z - (-1);             /* ±0 - 0 = ±0 */
  return Clog(y) - ((y-1-z)/y+0);   /* ±0 + 0 = 0 */
}  

static Complex cexpm1(Complex z) {
  double k = expm1(Real(z)), t = Imag(z), s = sin(t);
  t = sin(t/2); t=-2*t*t; /* t = -versin(Imag(z)) */
  /* expm1(z) = (k+1) * (1+t+s*I) - 1 */
  s *= k+1;  /* (k+1) * ±0 = ±0 */
  return cmplx(-t > fabs(k) ? k+k*t+t : t+k*t+k, s);  
}  
achan001 commented 1 year ago

This is a minor issue.

From FCN menu, LN1+ (not showing X), E↑X- (not showing 1) I had to lookup source code, core_tables.cc, to figure what it is called.

Perhaps make some alias, so what's shown in FCN, we can type it in, with XEQ "..." ?

achan001 commented 1 year ago

Another version of expm1(z), using SINCOS, worth considering

static Complex cexpm1(Complex z) {
  double k = expm1(Real(z)), s, c, t;
  SINCOS(Imag(z)/2, s, c);
  t = -2*s*s;       /* -versin(Imag(z)) */
  /* expm1(z) = (k+1) * (1+t + (2*s*c)*I) - 1 */
  return cmplx(-t > fabs(k) ? k*t+k+t : k*t+t+k, 2*s*(k*c+c));    
}
thomasokken commented 1 year ago

Thanks! I like the idea of extending LN1+X and E^X-1 to complex numbers, but I wonder if I should add a backward-compatibility setting in the preferences, to provide the option to preserve the original HP-42S real-only behavior for these functions. Something to consider for Free42 3.1 and Plus42 1.1, perhaps...

Regarding the names of these functions: for function names that don't fit in the 19 pixels available on a menu key, the HP-42S function table indicates which characters to omit, by setting the high bit of the characters in question. This is used to indicate, for example, that ENTER should be shortened to ENTR. When no character is flagged to be omitted in menu keys, the name is simply shortened by dropping characters from the end until the function name fits.

In the cases of LN1+X and E^X-1, the last character of each of those names is flagged, and that is actually redundant, because the last character would be dropped anyway. I don't know why it was done that way, and the names you see in the Free42 function table were simply copied from the original unchanged. But if you use Plus42 and increase the number of display columns to 24 or more, then LN1+X and E^X-1 will fit in the menu softkeys in their entirety.

achan001 commented 1 year ago

Thanks.

This explained why LN1+X name = "LN1+\330" ASCII code \0130 = "X"

Interestingly, table had an entry with high bit on all characters

NULL = "\316\325\314\314" XEQ "NULL" produce an error: Label Not Found

docmd_null, appeared in core_commands1.cc, and does not appear to be used anywhere (from a simple grep of source code). Is it dead code?

thomasokken commented 1 year ago

NULL is a "hidden" function, meaning it can't be executed using XEQ. This is controlled by FLAG_HIDDEN in command_spec.flags.

The way all four characters of the name NULL are marked to be omitted, allows it to be used for unused (blank) menu keys.

It is used in many places; just search the code for CMD_NULL, and watch what happens when you press and hold a blank menu key in one of the function or application menus. Also, the NULL you see, when you press and hold a function key for two seconds, is the NULL function.

thomasokken commented 1 year ago

Also note that most of the calculator's functions (i.e. the functions with names starting with docmd_) are never called directly, they are called indirectly using the function number to index into the function table. This is done by the handle() function, at the end of core_tables.cc.

thomasokken commented 11 months ago

I would like to add this functionality in Free42 3.1 and Plus42 1.1. For the complex cases, I'll add new functions, C.LN1+X and C.E↑X-1, to avoid compatibility issues from changing the behavior of LN1+X and E↑X-1. Which version of your code would you recommend using?

achan001 commented 11 months ago

Is there compatibility issues to worry about?

There is no code exist that complex numbers LN1+X or E↑X-1 work. All of them would produce "Invalid Type".

thomasokken commented 11 months ago

My concern is that there might be programs that rely on Invalid Data errors, where extended versions of these instructions would return complex results instead.

achan001 commented 11 months ago

This is a rare case, since program just die after "Invalid Type" error. (there is no try ... except ... finally mechanism for recovery)

"Invalid Type" error already suggested it may be valid in the future. (this is very different than un-recoverable "Divide by Zero" error)

We certainly don't want new names for complex functions, C.EXP, C.LN, ... It's just more natural to think of LN1+X = more accurate LN(1+x)

Just give an example, lua 5.1 does not have log_b(x), only log(x) Lua 5.2 (and above) added optional base: log_b(x) = log(x, b)

We could argue some weird code write log(x) as log(x, x) and expected log(x), and now suddenly it changed to log(x,x) = ln(x)/ln(x), but that's rare.

If we needed guaranteed compatibility, code is going to be dragged down considerably. Any keyword introduced may conflict with program or variable name. Improved algorithm is out, because it does not match old behavior. You cannot even fix bugs, because some users expected it ...

thomasokken commented 11 months ago

This is a rare case, since program just die after "Invalid Type" error. (there is no try ... except ... finally mechanism for recovery)

There is: flag 25

thomasokken commented 11 months ago

If we needed guaranteed compatibility, code is going to be dragged down considerably. Any keyword introduced may conflict with program or variable name. Improved algorithm is out, because it does not match old behavior. You cannot even fix bugs, because some users expected it ...

I don't understand what point you are trying to make here. In any case, it is incorrect.

Changing the behavior of LN1+X and E^X-1 to allow them to accept complex arguments, or in the case of LN1+X, to allow it to accept arguments <= -1, that's what would cause incompatibility, because programs might rely on errors to be raised in those cases, using flag 25 to detect them.

The only way to add complex support for LN1+X and E^X-1 while guaranteeing compatibility is to use separate functions, and that's what I'm proposing. Nothing is getting "dragged down" by doing this.

achan001 commented 11 months ago

I guess C.LN1+X and C.E↑X-1 is OK It just felt more natural without C.***

It is your call.

thomasokken commented 9 months ago

Apologies for the delay, I've been a bit too busy... I'll look into it further after the 1.1 release.

thomasokken commented 8 months ago

I implemented the complex-capable functions as C.LN1+X and C.E^X-1: 44216aed74438579564d355eb05a82e2a2712876 They will be in the next releases of Free42 and Plus42.

achan001 commented 8 months ago

Thanks!