While the approaches in the comments (thanks a lot!) solve the issue, the question triggered me to try to "have my cake and eat it as well", i.e., enter quantities but decide globally whether they are used and what will happen if I just want magnitudes—after all using Quantity slows down things.
A Litte Package For Conditional Quantities
The use of quantities with appropriate units should be mandatory for any mathematical model of reality, so that we may have a chance to validate equations.
While using Quantity is the way to go, there is a price to pay with regard to performance—likely a reason that using Quantity may be avoided and mere comments are used for units, which have the downside, that we can't let Mathematica help us.
So a nice thing to have may actually be to allow the verbose use of Quantity so that users can enter 10 Quantity["Percent"] instead of 0.1 (don't laugh, it happens). But at the same time, we may use ConditionalQuantity as a wrapper to guide conversion with regard to the use in our models.
The following package thus has two main functions:
- Allow to use
On["Quantities"] and Off["Quantities"] to switch between UnitConvert and QuantityMagnitude for quanties as appropriate.
- Tell Mathematica how we would like to have our verbose units be interpreted either for
UnitConvert or QuantityMagnitude.
We can achieve (2) by using:
ConditionalQuantity[quantity] to use QuantityMagnitude as is or to keep quantity as is, i.e., no target units are provided.
ConditionalQuantity[quantity, "Canonical" ] to try to go for the canonical unit, if it exists.
ConditionalQuantity[quantity, "Normal" ] to get rid of all units that are compatible to "DimensionlessUnit" (i.e., replace those units by 1), but leave the other units unchanged.
ConditionalQuantity[quantity, unit] to tell QuantityMagnitude and UnitConvert that unit is the target unit. (Instead of unit we also provide a reference Quantity.)
Code
BeginPackage[ "ConditionalQuantities`" ]
Quantity::inuse = "Quantities in use."
QuantitiesOffQ::usage = "
QuantitiesOffQ[] returns True, if messages related to Quantities have been switched off using Off["Quantities"].
The function will by default be used by ConditionalQuantity to decide whether a magnitude or a quantity is to be returned."
$numericalUnitRules = "
$numericalUnitRules is a list of replacement rules for numerical units like Percent, BasisPoints, Thousand etc."
ConditionalQuantity::usage = "
ConditionalQuantity[quantity] will return QuantityMagnitude[quantity] if QuantitiesOffQ[] is True and quantity, if this is not the case.\n
ConditionalQuantity[quantity, "Canonical" ] will call ConditionalQuantity[quantity, unit], where unit is the canonical unit.\n
ConditionalQuantity[quantity, "Normal" ] will call ConditionalQuantity[quantity, unit], where unit is QuantityUnit[quantity] where all parts
compatible to "DimensionlessUnit" have been replaced by 1.\n
ConditionalQuantity[quantity, refQuantity] will call ConditionalQuantity[quantity, QuantityUnit[refQuantity]].\n
ConditionalQuantity[quantity, unit] will return QuantityMagnitude[quantity,unit] or UnitConversion[quantity, unit] depending on QuantitiesOffQ[].
If units is are not compatible, unit input is disregarded and ConditionalQuantity[quantity] is called."
Begin["Private"]
$numericalUnitRules = Map[
Rule[ #, 1 ]&,
{
"Percent",
"BasisPoints",
"Dozen",
"Hundred",
"Thousand",
"HundredThousand",
"Million",
"Billion",
"Trillion"
}
]
$MessageGroups = Join[ $MessageGroups, { "Quantities" :> { Quantity::inuse } } ]
QuantitiesOffQ[] := HoldPattern[ Quantity::inuse ] /. Messages[ Quantity ] // Not @* FreeQ[ $Off ]
ConditionalQuantity[ q_Quantity ] := If[ QuantitiesOffQ[], QuantityMagnitude @ q, q ]
ConditionalQuantity[ q_Quantity, refq_Quantity ] := ConditionalQuantity[ q, QuantityUnit @ refq ]
ConditionalQuantity[ q_Quantity, "Canonical" ] := Enclose[
With[
{
canonicalUnit = ConfirmQuiet[
QuantityVariableCanonicalUnit @ QuantityVariable @ UnitDimensions @ q
]
}
,
ConditionalQuantity[ q, canonicalUnit ]
]
]
ConditionalQuantity[ q_Quantity, "Normal" ] := With[
{
normalUnit = QuantityUnit[ q ] /. $numericalUnitRules
}
,
If[ normalUnit === 1,
(* then ) ConditionalQuantity[ q, "DimensionlessUnit" ],
( else *) ConditionalQuantity[ q, normalUnit ]
]
]
ConditionalQuantity[ q_Quantity, unit_ ] /; CompatibleUnitQ[ q, unit ] unit := If[ QuantitiesOffQ[],
(* then )
QuantityMagnitude[ q, unit ],
( else *)
UnitConvert[ q, unit ]
]
ConditionalQuantity[ q_Quantity, unit_ ] /; Not @ CompatibleUnitQ[ q, unit ] := ConditionalQuantity[q]
End[]
EndPackage[]
Examples
Assuming that we loaded the package with <<ConditionalQuantities` or Needs, we can now do the following:
On["Quantities"] (* Not really necessary since it is default *)
r := ConditionalQuantity[ Quantity[5., "Percent"/"Years"], "Normal" ];
t := ConditionalQuantity[ Quantity[10, "Years"] ];
initialCapital := ConditionalQuantity[ Quantity[1000, "USDollars"] ];
initialCapital Exp[r t]
(* $1648.72 *)
r
(* 0.05 per year *)
Do[ initialCapital Exp[r t], 1000] // RepeatedTiming
{0.957131, Null}
Off["Quantities"]
initialCapital Exp[ r t ]
(* 1648.72 *)
r
(* 0.05 *)
Do[ initialCapital Exp[r t], 1000] // RepeatedTiming
{0.413421, Null}
LogandExpdo seem to have different opinions on whether "Percent" quantities should go throughNormalor not. UntilExpfigures it out you might be better off always normalizing it yourself:r = Normal[Quantity[5, "Percent"]]/Quantity["Years"]– Jason B. Apr 26 '22 at 11:321. + 5 Quantity["Percent"]evaluates to a pure number,1.05. SoExp[1. + 5 Quantity["Percent"]]evaluates to a pure number.LogandExparen't really involved with that example, since theQuantity[]goes away. For the last example,Log[Quantity[5, "Percent"]]also does not evaluate. I thoughtExpandLogtook dimensionless arguments, although percent could be argued to be dimensionless. PerhapsQuantity[]objects are treated as having dimensions, but0. + Quantity[5, "Percent"]suggests special cases exist. – Michael E2 Apr 26 '22 at 13:20"Percent"; use1/100; (2)initialCapital Exp[1 + r t]/E; (3)initialCapital Exp[0. + r t]; (4)initialCapital Exp[r t/Quantity[100, "Percent"]]– Michael E2 Apr 26 '22 at 13:24"Percent"as a pure number, includingPower. Some exceptions are the trig. functions exceptSecandInverseCDF[NormalDistribution[], r t]. There may be others but I stopped looking. It's odd that five of the six trig. funs. handle"Percent". -- As for aiding humanity: (1b) Strip"Percent"from input and internally avoid percent; put it back in output when appropriate...(5) Report to WRI and wait for them to fix it (probably the slowest workaround, but also the best solution assuming no other). – Michael E2 Apr 26 '22 at 14:04"Percent"as a unit and when to convert to a number, butExp[r t]is an obvious, common case. But given that it doesn't work, I assumed you would report it but not wait for a workaround. Here's another approach to get rid of just"Percent", whether or notxhas any units:UnitConvert[x, Information[x, "Unit"] /. "Percent" | _Information -> 1]. -- It's odd that Mma hasºforDegreebut not%for percent conversion, but interest in financial math didn't start until around V8 (andPercentFormis V12). – Michael E2 Apr 26 '22 at 14:39ExporLoga quantity with units. There may be an equivalence between a percentage and a pure number, but the moment you stateQuantity[5,"Percent"]you are stating a unit not a number. I don't think it's reasonable to expect Mathematica to deal with the concept behind the units, or attempts to enforce normal use or physics. I think that what the OP is asking is not something that would be desirable for Mathematica to provide. – rhermans Apr 26 '22 at 16:20Quantity[ 5, "Percent" ]is"DimensionlessUnit"and there is a reason that it can also be represented by1. All we should ask the WL to do is to consistently treat such quantities. Your appraoch is fair, but it is not the way WL treats all of these cases, which is clearly confusing. – gwr Apr 26 '22 at 16:24CompatibleUnitQ["DimensionlessUnits"]when applying some functions likeExp,PowerandLogbut not others likeTimesandPlus. I say that I don't like these exceptions and that such a task should be on the user. – rhermans Apr 26 '22 at 17:06Off["Quantities"]andOn["Quantities"]. Since Percent, BasisPoints to me is nothing different than a unit multiplier like kilo-, I could well live with it being automatically stripped. But as you say, it should be easy for the user to decide. – gwr Apr 26 '22 at 17:10