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

Add crate that enables the full float APIs in #[no_std] #4002

Closed
wants to merge 13 commits into from

Conversation

robertbastian
Copy link
Member

@robertbastian robertbastian commented Sep 4, 2023

It provides an extension trait for f32 and f64 that replicates the whole std API, backed by libm. This allows us to remove explicit libm calls from our business logic, and use the std implementations on std targets via shadowing.

This PR uses core_float in icu_segmenter, I will also apply it in calendrical_calculations after #3985 is merged.

@robertbastian robertbastian requested review from Manishearth and removed request for aethanyc and makotokato September 4, 2023 13:58
@robertbastian robertbastian changed the title Adding core_float crate that enables the full float APIs in #[no_std] Add core_float crate that enables the full float APIs in #[no_std] Sep 4, 2023
Comment on lines 750 to 755
let r = self % rhs;
if r < 0.0 {
r + rhs.abs()
} else {
r
}
Copy link
Member

Choose a reason for hiding this comment

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

General issue: We should minimize the amount of math code in this crate. If we must add any, it should be well tested. One way to test it is to run it against the standard library implementation on a suitably diverse set of inputs.

Specific issue: Pretty sure the div/rem euclid impls here aren't right. The standard library impl Rem says:

The remainder has the same sign as the dividend and is computed as: x - (x / y).trunc() * y

But rem_euclid is a different thing. It is the remainder relative to the floor, not the trunc. You can't derive that by taking .abs().

Copy link
Member Author

Choose a reason for hiding this comment

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

All code here is copied from std, with calls to intrinsics replaced by libm. You're linking to Rem, which is a different operation to rem_euclid.

Copy link
Member

Choose a reason for hiding this comment

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

Ok r + rhs.abs() I think is correct. We care a lot about rem_euclid for calendrical calculations.

Copy link
Member Author

Choose a reason for hiding this comment

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

rhs.abs() is the more general form, we use rhs in our version because we require rhs > 0

utils/core_maths/src/lib.rs Outdated Show resolved Hide resolved
}

#[inline]
fn powi(self, mut exp: i32) -> Self {
Copy link
Member Author

Choose a reason for hiding this comment

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

this is the only code not copied from std::f64, libm doesn't have a powi so this is from u64::pow.

@robertbastian robertbastian changed the title Add core_float crate that enables the full float APIs in #[no_std] Add core_maths crate that enables the full float APIs in #[no_std] Sep 5, 2023
Cargo.toml Outdated Show resolved Hide resolved
include.workspace = true

[dependencies]
libm = "0.2"
Copy link
Member

Choose a reason for hiding this comment

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

should this dep be cfg(target_os = none)?

Copy link
Member Author

Choose a reason for hiding this comment

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

Sure

Copy link
Member Author

Choose a reason for hiding this comment

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

actually this doesn't work, it's possible to build no_std on a target with an OS.

@robertbastian robertbastian changed the title Add core_maths crate that enables the full float APIs in #[no_std] Add core_float crate that enables the full float APIs in #[no_std] Sep 6, 2023
@sffc
Copy link
Member

sffc commented Sep 6, 2023

I'm not convinced that this PR is a positive change for these reasons:

  1. Currently each component specifies and tests its own set of math operations relevant for that component. I think this status quo is good, because we only add these functions when they are needed, so all the code is covered and tested. With a mega trait1 like this, most of this code is not going to be covered or used.
  2. Related to the above point, this code is not covered by tests. We want high test coverage both to satisfy Coveralls but also because it helps ensure that we are consistent with std. Furthermore, there isn't anything specific to ensure we keep this in sync with rust std.
  3. We are in the business of internationalization. I think we need to be conservative with non-i18n code landing in our repo. Existing utilities like Yoke, ZeroVec, ZeroFrom, TinyStr, and so forth are widely used by most all ICU4X components, and ones like FixedDecimal, ZeroTrie, and Writeable are definitely i18n-adjacent. core_maths is neither widely used (it is needed for only segmenter and calendrical calculations) nor i18n-adjacent (in both cases it is for implementing a non-i18n algorithm, ML and astronomy). Anywhere we need non-i18n utilities (like matrix math), we implement a narrow subset in private modules.

Footnotes

  1. Also, mega traits are slightly dangerous because if you ever take them as a trait object, you get all the code for all their functions with no code slicing available. This could be mitigated by making the mega trait not be object safe.

@robertbastian
Copy link
Member Author

Furthermore, there isn't anything specific to ensure we keep this in sync with rust std.

We don't have to keep it in sync, Rust std is not going to change behaviour overnight.

We have other utility crates that are not used by every component, like crlify, deduplicating_array, even litemap is only used by two components.

This trait is not object safe because it takes self by value.

@robertbastian robertbastian changed the title Add core_float crate that enables the full float APIs in #[no_std] Add crate that enables the full float APIs in #[no_std] Sep 6, 2023
@sffc
Copy link
Member

sffc commented Sep 6, 2023

We have other utility crates that are not used by every component, like crlify, deduplicating_array, even litemap is only used by two components.

crlify is less than 50 lines of code, so the risks and maintenance burden are much less.

deduplicating_array is a bit larger, yes. I think the reason it ended up there was that we needed it as public API because it is in our data struct definition and couldn't figure out a better place to put it. But, we don't need core_maths in our public API.

litemap had previously been a utility used widely, until ZeroMap came along and replaced most of our usages. We have a path toward unifying our map solution in #3128.

@sffc
Copy link
Member

sffc commented Sep 6, 2023

We don't have to keep it in sync, Rust std is not going to change behaviour overnight.

The most likely situation I'm thinking of is Rust std adding new mathematical operations. Not super common but it does seem to happen from time to time (f64::clamp, f64::total_cmp, f64::from_ne_bytes and friends, f64::to_int_unchecked, f64::EPSILON, f64::copysign, f64::div_euclid, and f64::rem_euclid are all examples of APIs added after 1.0), and I don't want the ICU4X team to take on the responsibility to check for that.

@robertbastian
Copy link
Member Author

I think you might be overestimating the maintenance burden. I'm happy to own this crate.

@sffc
Copy link
Member

sffc commented Sep 6, 2023

I would be happy with any of the following options (not in any particular order):

  1. Write tests for this crate that compare your function output with std function output. I definitely do not want to land any more math code with spotty test coverage given Fix FixedDecimal rounding bug; add more tests #3644. If you add tests and @Manishearth is aligned on the crate concept, we can land this.
  2. Make a new repository like robertbastian/coremaths (going through the releasing process) and then we take it in as a third-party dependency.
  3. Close this PR and optionally open a new one polishing up the private component-specific traits.

(however, there are other 1.3 blocking issues that are all higher priority than landing this PR, in my opinion)

@robertbastian
Copy link
Member Author

I will not write tests for std against std, that just does not make sense. It's also not possible to exhaustively test f32xf32 or f64 methods.

This is very simple code, which can easily reviewed by looking at the mathematical definition that each method represents. It is not comparable to the complicated business logic in #3644.

Make a new repository like robertbastian/coremaths (going through the releasing process) and then we take it in as a third-party dependency.

I'm happy to do that but what exactly does that achieve? I'll still want to depend on this from ICU4X.

@robertbastian robertbastian deleted the corefloat branch September 22, 2023 09:14
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