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

Is there a way to convert from/to base unit quantities without explicitly specifying the unit? #485

Closed
ts826848 opened this issue Oct 15, 2024 · 2 comments

Comments

@ts826848
Copy link

Been looking around the docs, source code, and GitHub issues for a bit now, so hopefully I didn't miss anything.

I'm experimenting with creating a simplified ndarray-like type that tracks units. This is similar to option 3 in #237, but I'm using a plain f64 backing array with units being indirectly tracked using PhantomData:

struct Quantities<Q> {
    values: Box<[f64]>,
    unit: PhantomData<Q>
}

I started off storing quantities directly in the backing array, but switched to f64 so I can guarantee certain operations could be done without an allocation. For example, multiplication can be guaranteed to be done in-place via something like values.iter_mut().for_each(|l| *l *= r), while with a strongly-typed backing array I'd be relying on the optimizer to ensure something like values.into_iter().map(|l| l * r).collect::<Vec<_>>() does not allocate (and from some exploration on Godbolt it seems that that optimization is a bit more iffy for boxed slices than for Vecs, not to mention boxed slices just have worse ergonomics in this case).

(I'm about 50% sure this is safe due to quantities being normalized, but I'm not that confident there aren't edge cases where this assumption is invalid)

This mostly works, but where things get a bit more interesting is trying to get individual quantities back out in a generic context. I know quantities are normalized so for individual instances I can manually use the right unit, but I can't seem to find a way to do this in a generic context. In effect, I have an f64 representing a normalized quantity and I have the quantity type, but I can't put them together to get a strongly typed quantity:

// Ignore missing bounds
fn is_valid<Q>(quantity: Q) { ... }
impl<Q> Quantities<Q> {
    fn is_all_valid(&self) -> bool {
        self.values.iter().map(|f| Q::new(f)).map(|q| is_valid(q)).all()
                                // ^^^^^^^^^ Doesn't exist; need to manually specify base unit
    }
}

A super-duper-yucky-hacky way to deal with this would be to transmute the f64 to the appropriate quantity but that feels rather gross for something that feels like it should be safe. The approach I've been using is a FromF64 trait that I implement on all the quantity types I use, but I feel it's effectively duplicated work for information uom already defines and I feel there's got to be a cleaner way to do what I want.

I also have a somewhat similar issue with getting an f64 out of a Quantity. Is there a way to do so without having to specify the unit - i.e., a way to get the raw f64 corresponding to the base unit in a generic context? As before transmuteing could work as could a (In)toF64 trait, but I feel there's got to be a cleaner way.

Did I miss anything in the docs/code/issues here? Is there a better approach?

@iliekturtles
Copy link
Owner

See #231 (comment) and #26 (comment). While these are in the context of const, all fields in the Quantity struct are public and allow you to create an instance without going through the new constructor. Let me know if this works.

@ts826848
Copy link
Author

Ah, completely missed that that was an option. I think that would do the trick. Thanks for your help!

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

No branches or pull requests

2 participants