bjornbm / dimensional

Dimensional library variant built on Data Kinds, Closed Type Families, TypeNats (GHC 7.8+).
BSD 3-Clause "New" or "Revised" License
102 stars 16 forks source link

Splitting into multiple units #82

Open dmcclean opened 9 years ago

dmcclean commented 9 years ago

For some of the legacy units it would be nice to have functionality like that of (/~) which in some sense "splits" a quantity into multiple units.

Something like:

split :: (RealFrac a, Integral b) => (Unit d a, Unit d a) -> Quantity d a -> (b, a)
split (a, b) x = -- something that involves properFraction

so that split (foot, inch) (12.37 *~ meter) evaluates to (40, 7.00787) because 12.37 meters is 40 feet 7.00787 inches.

Also would like the same thing but for triples, split3 :: (RealFrac a, Integral b) => (Unit d a, Unit d a, Unit d a) -> Quantity d a -> (b, b, a) for converting to degrees/minutes/seconds of arc.

One hairy thing is that it's unclear what the semantics should be when the second unit is larger than the first. I suppose it makes some sense to allow split (foot, mile) (12.37 *~ meter) to be (40, 0.000110604). Nobody would ever want to do it on purpose, but it isn't actually wrong.

dmcclean commented 9 years ago

(Beware of dragons when the quantity is negative, and be sure to document that case well.)

dmcclean commented 9 years ago

Maybe the general one is:

split :: (RealFrac a, Integral b) => Unit d a -> Quantity d a -> (b, Quantity d a)

Then you can repeat the process as many times as you want.

toDMS :: (RealFrac a) => PlaneAngle a -> (Int, Int, a)
toDMS x = let (d, x')  = split degreeOfArc x
              (m, x'') = split minuteOfArc x'
              s        = x'' /~ secondOfArc
           in (d, m, s)
dmcclean commented 9 years ago

There is a serious thorn here.

Suppose you are doing DMS, and you want to format the seconds with 3 digits after the decimal place, using something like showFFloat (Just 3).

If this split method gives you 12 minutes and 59.99999999 seconds, it is going to get shown as 12 min 60.000 sec.

(There may even be cases where numerical issues can push you over that.)

I'm not sure what is a clean and polymorphic way to prevent this.

dmcclean commented 8 years ago

I still don't know of a polymorphic way to do this and avoid the rounding issue in my previous comment. So I'm leaning towards doing it monomorphically. I just need to decide on what is the most useful representation type to use for the "remainder" bit of it.

Rational, Scientific, something like that? Maybe it is Rational, but we then need options to control the conversion from things like Double to Rational that are appropriate for display. It seems like the options commonly used are: as much info as we have, a certain number of digits after decimal point, a certain denominator, or a certain number of "signficant figures"?

Any clean way to implement all that or existing libraries to lean on? Or should I just make data FormatOptions = ... and start writing?

bjornbm commented 8 years ago

Could Data.Decimal be useful? (I don't know if it adds anything you cannot get from, e.g., Scientific.)

dmcclean commented 8 years ago

Possibly. I think I am making progress on the polymorphic way of doing it by including some functions in my PresentationFormat d a type. See #163 for another stepping stone on the way to making this really do what you would want it to do.