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

Implement Neri-Schneider calculations #147

Merged
merged 16 commits into from
Jan 20, 2025
Merged

Implement Neri-Schneider calculations #147

merged 16 commits into from
Jan 20, 2025

Conversation

nekevss
Copy link
Member

@nekevss nekevss commented Jan 6, 2025

This PR changes the current date calculations to one's layed out by Neri-Schneider's Euclidean affine functions paper.

There is still some documentation that needs to be completed before this is ready to merge. I was also hoping to double check the assembly but I'm sort of viewing that as part of the documentation. In the meantime, I'm posting this as a draft for feedback.

@cassioneri
Copy link
Contributor

@nekevss Thanks for your interest in our work and for this implementation. Lorenz and I are always glad to see our algorithms to be implemented by others in different computer systems.

I took the liberty to inspect the assembly generated by your implementation through compiler explorer. Here is the link. It looks quite nice already but it can be improved a bit further.

In the paper we define a constant s which shifts the interval of supported dates. More specifically, we set s=82 to comply with the C++ Standard that requires support for years in [-32767, 32767]. This was just an example and, as you rightly figure out, can be changed to support different ranges. However, in your code s is a variable to allow users to set their own preference and for Temporal you set s=680. Using a variable might have performance costs as indicated by comparing the assembly in the link above to the one obtained when s is a constant as shown here. (Notice the definition of const CYCLE_SHIFTS at line 34.)

IMHO, it's better to define s as a constant and the best value for it is 3670. This choice places 1970/Jan/01 roughly in the middle of the interval of supported dates, namely, [-1468000/Mar/01, 1471805/Jun/05]. Unfortunately, this idea came to my mind only after the paper was published. I used this value in my implementation for SpiderMonkey. You can see it here with a lengthy comment explaining this choice.

src/neri_schneider.rs Outdated Show resolved Hide resolved
@nekevss nekevss force-pushed the impl-neri-schneider branch from f625541 to bae8b93 Compare January 20, 2025 01:04
nekevss and others added 2 commits January 19, 2025 19:13
Updating per review from @cassioneri to optimize instructions

Co-authored-by: Cassio Neri <[email protected]>
@nekevss nekevss marked this pull request as ready for review January 20, 2025 01:23
Copy link
Member

@jedel1043 jedel1043 left a comment

Choose a reason for hiding this comment

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

Really impressive work! I'm excited to see that we're finally getting rid of those pesky f64 in our calculations 😅

//! | September 14, 275,760 | 100_719_469 | 200,065,429 |
//!
//! However, this shift has also been implemented by Cassio Neri, who
//! recommends using a [shift of 3670][] which places the Epoch in the
Copy link
Member

Choose a reason for hiding this comment

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

Broken link?

Comment on lines 33 to 41
// NOTE: Temporal must support the year range [-271_821, 275_760]
//
// This means the epoch day range must be epoch_days.abs() <= 100_000_001
//
// Neri-Schneider mention shifting for a range of 32_767, so the shift
// will need to be much greater.
//
// (-271_821 / 400).ciel() = s // 680
//
Copy link
Member

Choose a reason for hiding this comment

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

Maybe integrate this into the documentation above? Seems important enough to specify the complete range that temporal_rs supports.

const TWO_POWER_THIRTY_NINE: u64 = 549_755_813_888; // 2^39 constant
const TWO_POWER_THIRTY_TWO: u64 = 4_294_967_296; // 2^32 constant
const TWO_POWER_SIXTEEN: u32 = 65_536; // 2^16 constant
const DAYS_IN_GREGORIAN_CYCLE: i32 = DAYS_IN_A_400Y_CYCLE as i32;
Copy link
Member

Choose a reason for hiding this comment

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

I'd just use the same constant and convert to i32 explicitly. Seems simpler than having two separate but equal constants.


/// Calculate Rata Die value from gregorian
pub fn epoch_days_from_gregorian_date(year: i32, month: i32, day: i32) -> i32 {
let shift = SHIFT_CONSTANT * DAYS_IN_GREGORIAN_CYCLE + EPOCH_COMPUTATIONAL_RATA_DIE;
Copy link
Member

Choose a reason for hiding this comment

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

Make this a const? It only depends on const values, after all.

const SHIFT_CONSTANT: i32 = 3670;

/// Calculate Rata Die value from gregorian
pub fn epoch_days_from_gregorian_date(year: i32, month: i32, day: i32) -> i32 {
Copy link
Member

Choose a reason for hiding this comment

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

I'm a bit confused by the types here. If I understood the code correctly, it relies on month and day being positive and greater than 1, so why do we want to use signed integers in this case?

Copy link
Member Author

Choose a reason for hiding this comment

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

Ooooh, good point. Actually, I think this might have been a bug on the implementation side connected to IsoDate::balance

Copy link
Member

Choose a reason for hiding this comment

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

I'm a bit skeptical about exposing this as a public module. I would expect this to be just internal details of our utils module.

Copy link
Member

Choose a reason for hiding this comment

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

This is still exposed as a public (crate) module. I think the best option here is to have neri_schneider be a submodule of utils, then just avoid exposing implementation details to the rest of the library.

Copy link
Member Author

Choose a reason for hiding this comment

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

Ah, I misread that for some reason. Making it a submodule of utils sounds fine to me.

@nekevss nekevss requested a review from jedel1043 January 20, 2025 18:57
Copy link
Member

@jedel1043 jedel1043 left a comment

Choose a reason for hiding this comment

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

Really nice implementation! There are still some details that we could definitely improve — explain what "rata die", "computational calendar", "first equation", etc. mean, maybe change some names to be more descriptive — but I think this is good enough that we can merge it and then iteratively improve on it.

@jedel1043 jedel1043 merged commit 903d64b into main Jan 20, 2025
6 checks passed
@jedel1043 jedel1043 deleted the impl-neri-schneider branch January 20, 2025 21:33
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