Skip to content

Added Complex Numbers extension - #612

Open
salagata wants to merge 5 commits into
PenguinMod:mainfrom
salagata:main
Open

Added Complex Numbers extension#612
salagata wants to merge 5 commits into
PenguinMod:mainfrom
salagata:main

Conversation

@salagata

Copy link
Copy Markdown

I added my first new extension Complex Numbers
Complex Number Type for do complex analysis functions.
Ideal for things were rotation is involved like bullet hells, or simulating things like fluids, illumination or camps

salagata added 2 commits July 28, 2026 01:36
Adds my extension (salagata/reisen) Complex Numbers.
Complex Number Type for do complex analysis functions, ~~better implementation than the one made by jwklong in Mathemathics extension lmao~~
Added a new entry for 'Complex Numbers' with detailed metadata.
@sakurabuilder

sakurabuilder Bot commented Jul 28, 2026

Copy link
Copy Markdown

✅ Preview ready

Your changes are live at: https://zesty-maple-c0bb4c32.skr.mubilop.com/

Built from 76e7fa7

salagata added 3 commits July 28, 2026 10:44
Added the new extension for complex numbers with improved implementation and additional metadata.
Comment on lines +339 to +340
const real = absolute * Degrees.cos(argument)
const imaginary = absolute * Degrees.sin(argument)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There work is pointless, as it gets discarded immediately when put into the constructor, which does this conversion on its own.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seemingly happens quite a lot in this extension when working with polar form.

@salagata salagata Aug 4, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you're right in this one.
The constructor is supposed to receive 2 or 4 arguments, according the form of the number you're working with.
If you pass the absolute value and phase, if you don't pass the real and imaginary part, it will calculate it, if not, then it will borrow the ones passed.
But It seems like I did the fromPolar() function without thinking on that, I will fix it when I can

Comment on lines +381 to +385
if(z.length == 4) {
return new ComplexNumber.Type(z[0],z[1],z[2],z[3])
} else {
return new ComplexNumber.Type(z[0],z[1])
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like a case where spread syntax would be more readable.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought here the code would be more explanatory but it seems like it might be a better idea to use the spread syntax.
Actually I was thinking to use spread syntax but...

Comment on lines +584 to +599
{
opcode: "polarToComplex",
text: this.formatMessage("[POLAR] to rectangular form"),
arguments: {
POLAR: ComplexNumber.Argument
},
blockType: Scratch.BlockType.REPORTER
},
{
opcode: "complexToPolar",
text: this.formatMessage("[COMPLEX] to polar form"),
arguments: {
COMPLEX: ComplexNumber.Argument
},
blockType: Scratch.BlockType.REPORTER
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The should probably specify in the block name that they get stringified. Could also be combined into one block with a menu.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great idea, I haven't thought on it. I will use a drop-down menu for this one.
Well, I thought to use terms like "stringified" would look rare for people who doesn't know what "to stringify" is. Maybe I can add "as text" at the end though.

static fromPolar(absolute, argument) {
const real = absolute * Degrees.cos(argument)
const imaginary = absolute * Degrees.sin(argument)
return new ComplexNumberType(real, imaginary, absolute, clampAngleDegrees(argument))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not particularly important, but could you be a bit more consistent with your choice of terminology: argument vs. phase.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will stay with phase, but my mind sometimes makes me change it with argument.
I'll try to be more consistent.

Comment on lines +836 to +843
{
text: this.formatMessage("positive"),
value: "positive"
},
{
text: this.formatMessage("negative"),
value: "negative"
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$\sqrt{b^2-4ac} > -b$ (IE sometimes both roots can be negative or positive) isn't always necessarily true, so labeling these as being "negative" or "positive" doesn't make much sense. It makes even less sense when applied to the complex plane.

There really isn't a satisfactory way to label roots (because they're unordered), so I guess it makes sense you'd do this.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I already know about the signs, but "Positive" and "Negative" are supposed to be the sign of the square root of the discriminant, the \sqrt{b^2-4ac} thing, the "Positive" and "Negative" are supposed to be the sign of \pm. Maybe i might change this for something... less confusing?

},
{
opcode: "squareRoot",
text: this.formatMessage("squareroot of [A]"),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

either square root to sqrt but not squareroot.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OOPS, my mind sometimes crashes at trying to remember the way to spell... well, square root in English. First I think it is "square root", then I remember "sqrt" but I need the long one, then I incorrectly think it must be "squareroot".
I will fix this error.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah. I was running out of time, so this banner is a placeholder I will change whether I'm free.
Thank you for the advice!

Comment thread static/extensions/salagata/reisenComplex.js
Comment on lines +1036 to +1125
power(args) {
const A = ComplexNumberType.toComplex(args.A);
const power = Math.round(Scratch.Cast.toNumber(args.B));

const firstReal = A.real, firstImaginary = A.imaginary;
let pair = [firstReal, firstImaginary];

if(power == 0) {
return new ComplexNumberType(1,0)
}
if(power == 1) {
return A
}
if(power == -1) {
const u = A.real ** 2 + A.imaginary ** 2;
return new ComplexNumberType(A.real / u, -A.imaginary / u);
}

const absPower = Math.abs(power);

for (let _ = 1; _ < absPower; _++) {
pair = [
pair[0] * firstReal - pair[1] * firstImaginary,
pair[0] * firstImaginary + pair[1] * firstReal
];
}

if(power < -1) {
const u = pair[0] ** 2 + pair[1] ** 2;
return new ComplexNumberType(pair[0] / u, -pair[1] / u);

}

return new ComplexNumberType(
pair[0], pair[1]
);
}


squareRoot(args) {
const A = ComplexNumberType.toComplex(args.A);
const r = Math.hypot(A.real, A.imaginary);

return new ComplexNumberType(
Math.sqrt(1/2 * (r + A.real)),
(A.imaginary >= 0 ? 1 : -1) * Math.sqrt(1/2 * (r - A.real)),
);
}

power2(args) {
const A = ComplexNumberType.toComplex(args.A);
const power = Scratch.Cast.toNumber(args.B);

if(power == 0) {
return new ComplexNumberType(1,0)
}
if(power == 1) {
return A
}

const r = A.absolute ** power;
const phi = A.argument * power;

return new ComplexNumberType(
r * Degrees.cos(phi),
r * Degrees.sin(phi),
r, phi
);
}

nRoot(args) {
const A = ComplexNumberType.toComplex(args.A);
const subRadical = Scratch.Cast.toNumber(args.B);

if(subRadical == 0) {
return Infinity
}
if(subRadical == 1) {
return A
}

const r = subRadical == 2 ? Math.sqrt(A.absolute) : (A.absolute ** (1/subRadical));
const phi = A.argument / subRadical;

return new ComplexNumberType(
r * Degrees.cos(phi),
r * Degrees.sin(phi),
r, phi
);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You very much can do complex powers:

  • assume you have complex numbers $a+bi$ and $c+di$
  • $e^{\ln(a+bi)} = a+bi$
  • $\therefore (a+bi)^{c+di} = e^{\ln(a+bi)^{c+di}} = e^{(c+di)\ln(a+bi)}$
  • since every complex number as a polar form, and the polar form can be used to find the natural logarithm: $a+bi = r \angle \phi \implies \ln(a+bi) = r+\phi i$ (where $\phi$ is in radians)
  • $e^{(c+di)\ln(a+bi)} = e^{(r+\phi i)(c+di)} = e^{rc-d\phi+(c\phi+rd)i} = e^{rc-d\phi}e^{(c\phi+rd)i}$
  • $\therefore (a+bi)^{c+di} = e^{rc-d\phi}\angle(c\phi+rd)$ (in radians)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was also thinking on doing complex powers, but I was running out of time and I a haven't thought it might be useful, also I've thought people can implement their own using the blocks that we already have.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's true. There is an e^ function in the functions block (operators, not this extension), but I'd expect the functionality to appear in an extension dedicated to complex mathematics.

Comment on lines +1259 to +1260
const positive = new ComplexNumberType(-b / (2 * a), Math.sqrt(Math.abs(det)) / (2 * a));
const negative = new ComplexNumberType(-b / (2 * a), Math.sqrt(Math.abs(det)) / (2 * a));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are literally the same. I think you meant to add a minus sign infront of the complex part in the "negative" solution.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes it is?. And, it is what is actually representing?
Why it looks like the same in your comment?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes it is?. And, it is what is actually representing? Why it looks like the same in your comment?

what? no they are literally the same snippet copy-pasted, i know i dont know anything about complex numbers but i can certainly agree that -1 != +1 and vice versa for any and all dimensions of numbers

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AH SORRY, I was looking at the wrong line, I will fix it as soon as I can...

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And, it is what is actually representing?

I think you already know that the determinate needs to either be added or subtracted, in this case both are adding the determinate, resulting in the same value being gotten for both roots, rather than different ones.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SORRY I COMPLETELY KNOW, I just got a bit confused?, I will fix it as quicker I can

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I hadn't seen your reply to that while I was writing my own reply (GitHub for some reason doesn't load new review comments until you reload the page). I didn't mean to rush you.

Comment on lines +1023 to +1034
divide2(args) {
const A = ComplexNumberType.toComplex(args.A);
const B = ComplexNumberType.toComplex(args.B);
const u = B.real ** 2 + B.imaginary ** 2;

return new ComplexNumberType(
(A.real * B.real + A.imaginary * B.imaginary) / u,
(A.imaginary * B.real - A.real * B.imaginary) / u
, A.absolute / B.absolute
, untransposeAngle(-(A.argument - B.argument))
);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A bit more of a general problem I have with this extension, but having 2 blocks that do, from an outside perspective, identical things is a really bad idea.

@salagata salagata Aug 4, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One is supposed to operate with the real and imaginary part divide(), the other one is supposed to operate with the absolute value and phase without being subject to the floating-point error at the moment of calculating the real and imaginary part divide2()

Having two blocks is for operate separately what do you want to keep from the original complex number, or whether you're using the rectangular or polar form (indicated with "using the polar form").

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What I mean is that throughout this extension there's a division (mind the pun) between 2 entirely separate systems that don't have much impact on how people using the extension will use it. Having the extension have several different blocks that seem to perform the same operation with often minimal (if any) difference in how they appear can be very confusing.

I think it'd be better if you didn't make much of a distinction between complex numbers originally defined in polar form and numbers originally defined in the form $a+bi$. If you think the method using the absolute value and argument are more precise than doing just straight division, then maybe that should be the default way it gets done, rather than an alternate block that uses a secondary system.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will see if i do an alternative system, i.e. , maybe choosing the mode when you want to do the, i.e. division.
As I said, sometimes it can't be uncertain if people will use the polar or rectangular form, i.e., in programs where the polar form is often better for calculate, i.e. rotations. Or in graphs, where the rectangular form is better.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright.

@Steve0Greatness Steve0Greatness left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I realize I haven't looked at the extensions.js changes yet, so here's a review there.

Comment thread src/lib/extensions.js
code: "salagata/reisenComplex.js",
banner: "salagata/reisenComplex_placeholder.svg",
creator: "salagata",
tags: ["new","complex", "math", "graphics", "customtype", "utility"],

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Graphics? Please explain what this has to do with graphics.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Things where rotations are involved. Since complex numbers are perfect for rotations, rotating a complex number is just multiplying a polar number with an absolute value of 1 (r=1) and with a defined angle (\theta), instead of rotating vectors, they use A Matrix for rotate. The complex numbers are better for this kind of things involving rotation in graphics.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will see if i do an alternative system, i.e. , maybe choosing the mode when you want to do the, i.e. division.
As I said, sometimes it can't be uncertain if people will use the polar or rectangular form, i.e., in programs where the polar form is often better for calculate, i.e. rotations. Or in graphs, where the rectangular form is better

Comment thread src/lib/extensions.js
},
{
name: "Complex Numbers",
description: "Complex Number Type for do complex analysis functions, better implementation than the one made by jwklong in Mathemathics extension lmao.",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Descriptions are to describe your extension, for this, there's really no reason to mention another extension in your case.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

... sorry I just ... N-NVM forget I did that-

Comment thread src/lib/extensions.js
Comment on lines +327 to +328
code: "salagata/reisenComplex.js",
banner: "salagata/reisenComplex_placeholder.svg",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just about directory arrangement: since your extension is under your own directory, you don't need to add a prefix to the filename (note that the ID should still have a prefix, just that the filename doesn't need one, the directory structure already essentially does that for you).

Comment thread src/lib/extensions.js
tags: ["new","complex", "math", "graphics", "customtype", "utility"],
creatorAlias: "Reisen the Inaba",
notes: "Additional help by jwklong extensions",
unstable: false,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It not being unstable is assumed by default, so you don't need to (and in fact probably shouldn't) specify it.

@salagata

salagata commented Aug 4, 2026

Copy link
Copy Markdown
Author

I realize I haven't looked at the extensions.js changes yet, so here's a review there.

Thanks for the advice, I will consider all of them for the next time I'm free

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One last quick little note: extension thumbnails should be designed for a 2:1 aspect ratio (IE if you have 30 pixels in the width, your height would be 15), this current thumbnail isn't in that aspect ratio, so it doesn't quite appear correctly as of right now.

Screenshot of preview (see above comment from the sakurabuilder bot) showing how the thumbnail has a bit chopped off from the top and bottom

Notice how the top and bottom of the image are cut off. I'm aware this image is meant as a placeholder, but it's just a note for when you have a final thumbnail.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants