Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Possibly v1.4 #156

Merged
merged 6 commits into from
Nov 9, 2023
Merged

Possibly v1.4 #156

merged 6 commits into from
Nov 9, 2023

Conversation

MathCookie17
Copy link
Collaborator

Added the following features:

  • An argument for hyper-4 functions that, when set to true, has them use the linear approximation even for bases <= 10, ensuring consistent behavior in situations where consistent behavior is needed (such as in an incremental game that uses tetration)
  • Support for XFY format in fromString
  • mod/modular function
  • Super roots of arbitrary length (but only for the linear approximation, as super roots don't really work without a consistent definition of tetration)

Added the following features:
* An argument for hyper-4 functions that, when set to true, has them use the linear approximation even for bases <= 10, ensuring consistent behavior in situations where consistent behavior is needed (such as in an incremental game that uses tetration)
* Support for XFY format in fromString
* mod/modular function
* Super roots of arbitrary length (but only for the linear approximation, as super roots don't really work without a consistent definition of tetration)
@Patashu
Copy link
Owner

Patashu commented Oct 13, 2023

Cool, I will take a look at this when I am lucid (hopefully in 1-3 days).

Just while it's on my mind, #22 (comment) onwards is the last of my 'todo list' for the library, with the intent of 'it's better to implement arbitrary tetration/inverses with 100% accuracy before then aiming to make it more performant' (doing binary search or better algorithms to implement inverses with 100% accuracy if a direct way to calculate them is not yet known). Especially with your idea of having a flag to calculate it in a quick-and-dirty-way, then everyone can be happy.

Obviously I don't expect you to do all of this for me, but if you did it'd be one less thing on my bucket list.

@MathCookie17
Copy link
Collaborator Author

It’s been a while - are you still going to look at this at some point?

As a response to what you said: I don’t actually understand your “analytic” version of decimal-degree tetration, otherwise I probably would have tried to extend it. I can’t really help you with extending that to more bases, at least not right now.

@Patashu
Copy link
Owner

Patashu commented Oct 22, 2023

Sorry, last week was really terrible for me, haha. Let's see if this week treats me better.

Extending the current 'more analytic' critical section to more bases is a bit beyond me, too - but by welding the code of http://myweb.astate.edu/wpaulsen/tetcalc/tetcalc.html onto break_eternity.js, then three specific bases (2, 10, e) could be made 100% accurate, and their inverses also 100% accurate by using binary search (or a better approximation, something likew Newton's algorithm, to converge faster) to a desired number of decimal places.

Basically on the premise that if you're going to implement something it should be actually correct - then after that, offering a less precise but faster alternative is fine, and you can choose what you want more.

I'd love to have arbitrary bases accurate too but that math knowledge is beyond me. Maybe some day though.

@MathCookie17
Copy link
Collaborator Author

For the record, the main reason I added a “always use the linear approximation” flag for tetration is because it’s weird that bases below 10 and bases above 10 use different rules. There are some cases where you need the behavior to be consistent across all bases (this is especially true for super-roots: if I recall correctly, using your analytic tetration, 10^^2.5 is around e294, but 10.000001^^2.5, which reverts to linear approximation, is in the e1400s, so the 2.5th super-root of, say, 1e500… would be NaN, because e500 is in the gap. That’s part of the reason that my super-root implementation always uses the linear approximation, as it would have unwieldy behavior when accounting for the gap. slog doesn’t have this problem because it keeps the base constant and searches for the tetra-exponent, but sroot is searching for the base), and since we don’t have a way to do analytic for all bases, the only option is to do linear for all bases when consistency across different bases is needed.

@Patashu
Copy link
Owner

Patashu commented Oct 23, 2023

Good point; I agree with your reasoning.

@@ -338,7 +338,7 @@ function d_lambertw(z: Decimal, tol = 1e-10): Decimal {
export type DecimalSource = Decimal | number | string;

/**
* The Decimal's value is simply mantissa * 10^exponent.
* The value of the Decimal is sign * 10^10^10...^mag, with (layer) 10s. If the layer is not 0, then negative mag means it's the reciprocal of the corresponding number with positive mag.
Copy link
Owner

Choose a reason for hiding this comment

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

damn how long has this comment been swimming around lmao

@@ -601,6 +601,14 @@ export default class Decimal {
return D(value).reciprocate();
}

public static mod(value: DecimalSource, other: DecimalSource): Decimal {
Copy link
Owner

Choose a reason for hiding this comment

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

sure, chuck 'em in, complete the set

@@ -1302,6 +1316,31 @@ export default class Decimal {
}
}

//handle XFY format
Copy link
Owner

Choose a reason for hiding this comment

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

sure - looks like XpY but you put the two parts the other way around? (I also note it has different () handling to XfY but it'd be trivial to fix if it's a problem)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yeah, I figured parentheses would be useful to have for both parts

@@ -1841,6 +1880,28 @@ export default class Decimal {
return this.recip();
}

//Taken from OmegaNum.js, with a couple touch-ups
public mod(value: DecimalSource): Decimal {
Copy link
Owner

Choose a reason for hiding this comment

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

it might be worth documenting which kind of modulo this is intended to be: https://en.wikipedia.org/wiki/Modulo#In_programming_languages

Looks like it's Euclidean whereas Javascript's is Truncated.

Copy link
Owner

Choose a reason for hiding this comment

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

Looks like it has some precision problems even when both numbers are layer 0:

new Decimal("1e14").mod(1.567).toString()
'0.875'
1e14 % 1.567
0.8812874504366692

If both numbers can be expressed as Javascript doubles, then you could turn to doubles, mod and return the result (after turning it from Truncated to Euclidean of course).

Copy link
Owner

Choose a reason for hiding this comment

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

Found a bug in the handling of mod when the 'this is too small compared to decimal' case happens, you don't do the euclidean modulo's requirement for sign multiplication:

new Decimal("1e-10000").mod("2").toString()
'1e-10000'
new Decimal("1e-10000").mod("-2").toString()
'1e-10000'

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Ah, I thought this matched JavaScript's - I failed to consider that the behavior could be different when the second number is negative. I'm willing to change it - would you rather have modulo match JavaScript's version or OmegaNum's version?

Copy link
Owner

Choose a reason for hiding this comment

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

Hard to say - my preference is actually Floored, which matches neither of them. Making it match the Javascript behaviour would make sense, but it's not like break_eternity is specifically a Javascript library, it just happens that this implementation is in Javascript.

To try to make a more objective decision, I looked at languages I think it's more likely this language would be ported to and tallied up how they implement %/mod:

Truncated: C/C++, C#, Go, Java, JavaScript, Rust, SQL, WebAssembly, GDScript
Euclidean:
Floored: Lua, Python, Ruby

So I think Truncated is the 'objective' correct choice, though you could do Floored if you're feeling spicy.

}

payload = D(payload);
const oldheight = height;
height = Math.trunc(height);
const fracheight = oldheight - height;

if (this.gt(Decimal.dZero) && this.lte(1.44466786100976613366)) {
if (this.gt(Decimal.dZero) && this.lte(1.44466786100976613366) && (oldheight > 10000 || !linear)) {
Copy link
Owner

Choose a reason for hiding this comment

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

I understand everything else, but what's the spiciness happening around here with the checks to 10000?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Basically, if we're using the linear approximation, we want all bases, even those less than e^(1/e), to be accurate. The way you handled bases less than e^(1/e) works fine for higher tetra-exponents, but it was resulting in inaccuracies for lower, non-whole tetra-exponents compared to the linear approximation (it's fine for your analytic tetration). 10000 was the height at which your script stops and does the final approximation, so it's the value I used here for "if the height is greater than this, the approximate way should work". The !linear check ensures that the analytic behavior isn't changed. Basically, the linear approximation can and should just be done the normal way, even on small bases, as long as the height isn't high enough that the values would converge anyway.

@@ -2532,7 +2599,8 @@ export default class Decimal {
copy = Decimal.pow(base, copy);
result -= 1;
} else if (copy.lte(Decimal.dOne)) {
return Decimal.fromNumber(result + Decimal.slog_critical(base.toNumber(), copy.toNumber()));
if (linear) return Decimal.fromNumber(result + copy.toNumber() - 1);
Copy link
Owner

Choose a reason for hiding this comment

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

(checked that this is the old linear critical section, and it is)

@@ -2723,9 +2791,311 @@ export default class Decimal {
return lnx.div(lnx.lambertw());
}

//Super-root, one of tetration's inverses - what number, tetrated to height (height), equals this?
Copy link
Owner

Choose a reason for hiding this comment

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

Wow, this saves me a lot of work, nice one!

The unit test looks robust at testing this. I tried a variation of the unit test, but all it told me was that if tetrating a small number returns 1 then obviously sroot can no longer tell you what small number you originally had (you'd need a format of number that's like '1 plus or minus a very tiny amount', which is not something that a number go up library needs to support, though an arbitrary precision library would consider it).

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Super root was the main reason I made all this - I really wanted it included. (Adding a linear approximation argument to tetration was my second reason for making this)

@Patashu
Copy link
Owner

Patashu commented Oct 26, 2023

Ok, done reading - left some comments for you to take a look at. When you're happy and I'm happy I'll merge it and npm a new build. Apologies again for the delay.

@MathCookie17
Copy link
Collaborator Author

Alright, I'll make changes when I find time. I'm pretty busy these next couple days, but I'll try to get to this soon.

@MathCookie17
Copy link
Collaborator Author

OK, I made the changes to mod. Is there anything else I need to do?

@Patashu
Copy link
Owner

Patashu commented Nov 5, 2023

Nope, I'll take a look today, thanks!

@Patashu
Copy link
Owner

Patashu commented Nov 6, 2023

It looks like your pull request has lines like

<<<<<<< Updated upstream

in it. Did you resolve some merge conflicts incorrectly?

@MathCookie17
Copy link
Collaborator Author

Yeah, I saw those and I have no idea what they are. What was I supposed to do, and what should I do now? This is my first time contributing to someone else’s GitHub project, so I don’t know how merge conflicts work.

@Patashu
Copy link
Owner

Patashu commented Nov 6, 2023

You need to look at the two different pieces of code and merge them into one single piece of code that is correct (has all the changes you want and none of the ones you don't want), then you delete the <<< === >>> types of lines.

These are created automatically when git doesn't know which of these two pieces of code is the one you actually wanted to be here, usually because there's a 'merge conflict' where the thing you were trying to change had an unrelated change in the mean time.

@MathCookie17
Copy link
Collaborator Author

Alright, merge conflicts have been fixed!

@Patashu
Copy link
Owner

Patashu commented Nov 9, 2023

Mod bugs look fixed now, so I'll merge and npm publish. Thanks!

@Patashu Patashu merged commit 6169647 into Patashu:master Nov 9, 2023
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.

2 participants