DocTestSetup = quote
using Unitful
end
!!! note Logarithmic scales are new to Unitful and should be considered experimental.
Unitful provides a way to use logarithmically-scaled quantities as of v0.4.0. Some
compromises have been made in striving for logarithmic quantities to be both usable and
consistent. In the following discussion, for pedagogical purposes, we will assume prior
familiarity with the definitions of dB and dBm.
Left- or right-multiplying a pure number by a logarithmic "unit", whether dimensionful or dimensionless, is short-hand for constructing a logarithmic quantity.
julia> 3u"dB"
3 dB
julia> 3u"dBm"
3.0 dBm
julia> u"dB"*3 === 3u"dB"
true
Currently implemented are dB, B, dBm, dBV, dBu, dBμV, dBSPL, dBFS, cNp,
Np.
One can also construct logarithmic quantities using the @dB, @B, @cNp, @Np macros to
use an arbitrary reference level:
julia> using Unitful: mW, V
julia> @dB 10mW/mW
10.0 dBm
julia> @dB 10V/V
20.0 dBV
julia> @dB 3V/4V
-2.498774732165999 dB (4 V)
julia> @Np ℯ*V/V # ℯ = 2.71828...
1.0 Np (1 V)
These macros are exported by default since empirically macros are defined less often than variables and generic functions. When using the macros, the levels are constructed at parse time. The scales themselves are callable as functions if you need to construct a level that way (they are not exported):
julia> using Unitful: dB, mW, V
julia> dB(10mW,mW)
10.0 dBm
In calculating the logarithms, the log function appropriate to the scale in question is used
(log10 for decibels, log for nepers).
There is an important difference in these two approaches to constructing logarithmic
quantities. When we construct 0dBm, the power in mW is calculated and stored,
resulting in a lossy floating-point conversion. This can be avoided by constructing
0 dBm as @dB 1mW/mW.
It is important to keep in mind that the reference level is just used to calculate the logarithms, and nothing more. When there is ambiguity about what to do, we fall back to the underlying linear quantities, paying no mind to the reference levels:
julia> using Unitful: mW
julia> (@dB 10mW/1mW) + (@dB 10mW/2mW)
20 mW
With a few exceptions, dimensionful logarithmic units, such as dBm behave just like the underlying
linear unit for purposes of arithmetic (i.e. arithmetic operations commute with linear). However,
note that they will still be displayed logarithmically. In contrast, arithmetic on dimensionless
logarithmic units (i.e. gains/attenuations) such as dB behaves logarithmically. This will be explored in more detail below.
The @dB and @Np macros will fail if either a dimensionless number or a ratio of
dimensionless numbers is used. This is because the ratio could be of power quantities or of
root-power quantities, leading to ambiguities. After all, usually it is the ratio that is
dimensionless, not the numerator and denominator that make up the ratio. In some cases
it may nonetheless be convenient to have a dimensionless reference level. By providing an
extra Bool argument to these macros, you can explicitly choose whether the resulting ratio
should be considered a "root-power" or "power" ratio. You can only do this for dimensionless
numbers:
julia> @dB 10/1 true # is a root-power (amplitude) ratio
20.0 dBFS
julia> @dB 10/1 false # is not a root-power ratio; is a power ratio
10.0 dB (power ratio with reference 1)
Note that dBFS is defined to represent amplitudes relative to 1 in dB, hence the
custom display logic.
Also, you can of course use functions instead of macros:
julia> using Unitful: dB, mW
julia> dB(10,1,true)
20.0 dBFS
julia> dB(10mW,mW,true)
ERROR: ArgumentError: when passing a final Bool argument, this can only be used with dimensionless numbers.
[...]
Logarithmic quantities with no reference level specified typically represent some amount of
gain or attenuation, i.e. a ratio which is dimensionless. These can be constructed as,
for example, 10*dB, which displays similarly (10 dB). The type of this kind of
logarithmic quantity is:
Unitful.Gain
One might expect that any gain / attenuation factor should be convertible to a scalar,
that is, to x == y/z if you had 10*log10(x) dB. However, it turns out that in dB, a ratio
of powers is defined as 10*log10(y/z), but a ratio of voltages or other root-power
quantities is defined as 20*log10(y/z). Clearly, converting back from decibels to a scalar
is ambiguous, and so we have not implemented automatic promotion to avoid incorrect
results. You can use Unitful.uconvertp to interpret a Gain as a ratio of power
quantities (hence the p in uconvertp), or Unitful.uconvertrp to interpret as
a ratio of root-power (field) quantities.
In this package, quantities with units like dBm are considered to have the dimension of
power, even though the expression P(dBm) = 10*log10(P/1mW) is dimensionless and formed
from a dimensionless ratio. Practically speaking, these kinds of logarithmic quantities are
fungible whenever they share the same dimensions, so it is more convenient to adopt this
convention (people refer to dBm/Hz as a power spectral density, etc.) Presumably, one
would like to have 10dBm isa Unitful.Power for dispatch too. Therefore, in the following
discussion, we will shamelessly (okay, with some shame) speak of dimensionful logarithmic
quantities, or Levels for short:
Unitful.Level
Actually, the defining characteristic of a Level is that it has a reference level,
which may or may not be dimensionful. It usually is, but is not in the case of e.g. dBFS.
Finally, for completeness we note that both Level and Gain are subtypes of LogScaled:
Unitful.LogScaled
For dimensionaless logarithmic units, addition behaves as one might expect:
julia> 10u"dB" + 10u"dB"
20 dB
I.e. the gains add. However, as hinted at above, dimensionful logarithmic units, behave as their corresponding linear unit:
julia> 10u"dBm" + 10u"dBm"
13.010299956639813 dBm
julia> linear(10u"dBm") + linear(10u"dBm")
20.0 mW
julia> uconvert(u"dBm", ans)
13.010299956639813 dBm
Note that this may seem strange from an arithmetic perspective, as written, but the arithmetic is entirely consistent. It can be helpful to think of the arithmetic as being performed on the linear units, with the logarithmic units simply being a display hint (although the quantity being stored is indeed the displayed logarithmic value).
Multiplication by a scalar is consistent with the addition rules above:
julia> 3u"dB" * 2
6 dB
julia> 3u"dB" + 3u"dB"
6 dB
julia> 2 * 0u"dB"
0 dB
julia> 0u"dBm" * 2
3.010299956639812 dBm
julia> 0u"dBm" + 0u"dBm"
3.010299956639812 dBm
Logarithmic quantities can only be multiplied by scalar, linear units, or quantities, but not logarithmic "units" or quantities. When a logarithmic quantity is multiplied by a linear quantity, the logarithmic quantity is linearized and multiplication proceeds as usual:
julia> (0u"dBm") * (1u"W")
1.0 mW W
The previous example returns a floating point value because in constructing the level
0 dBm, the power in mW is calculated and stored, entailing a floating point
conversion. This can be avoided by constructing 0 dBm as @dB 1mW/mW:
julia> (@dB 1u"mW"/u"mW") * (1u"W")
1 mW W
We refer to a quantity with both logarithmic "units" and linear units as a mixed quantity. For mixed quantities, the numeric value associates with the logarithmic unit, and the quantity is displayed in a way that makes this explicit:
julia> (0u"dBm")/u"Hz"
[0.0 dBm] Hz^-1
julia> (0u"dB")/u"Hz"
[0 dB] Hz^-1
julia> 0u"dB/Hz"
[0 dB] Hz^-1
Since dimensionful logarithmic units still behave as their corresponding linear unit, working with dimensionful units is entirely consistnet.
The behavior of addition and multiplication are summarized in the following table, with entries marked by † indicate prohibited operations. This table is populated automatically whenever the docs are built.
using Latexify, Unitful
head = ["100", "20dB", "1Np", "10.0dBm", "10.0dBV", "1mW"]
side = ["+"; "**" .* head .* "**"]
quantities = uparse.(head)
tab = fill("", length(head), length(head))
for col = eachindex(head), row = 1:col
try
tab[row, col] = string(quantities[row] + quantities[col])
catch
tab[row, col] = "†"
end
end
mdtable(tab, latex=false, head=head, side=side)
using Latexify, Unitful
head = ["10", "Hz^-1", "dB", "dBm", "1/Hz", "1mW", "3dB", "3dBm"]
side = ["*"; "**" .* head .* "**"]
quantities = uparse.(head)
tab = fill("", length(head), length(head))
for col = eachindex(head), row = 1:col
try
tab[row, col] = string(quantities[row] * quantities[col])
catch
if quantities[row] === u"1/Hz" && quantities[col] === u"3dB"
tab[row, col] = "† ‡"
else
tab[row, col] = "†"
end
end
end
mdtable(tab, latex=false, head=head, side=side)
‡: 1/Hz * 3dB could be allowed, technically, but we throw an error if it's unclear whether
a quantity is a root-power or power quantity:
julia> u"1/Hz" * u"3dB"
ERROR: undefined behavior. Please file an issue with the code needed to reproduce.
On the other hand, if it can be determined that a power quantity or root-power quantity is being multiplied by a gain, then the gain is interpreted as a power ratio or root-power ratio, respectively:
julia> 1u"mW" * 20u"dB"
100.0 mW
julia> 1u"V" * 20u"dB"
10.0 V
One final question to answer is how arithmetic behaves when it involves both dimensionless and dimensionful logarithmic units. The answer here is that in mixed arithmetic, both dimensionless and dimensionful units are treated logarithmically. This is done for convenience and can break commutativity and associativity, so should be probably avoided in generic code.
julia> 10u"dBm" + 20u"dB"
30.0 dBm
julia> (10u"dBm" + 10u"dBm") + 20u"dB"
33.01029995663981 dBm
julia> 10u"dBm" + (10u"dBm" + 20u"dB")
30.043213737826427 dBm
julia> 10u"dBm" * 20u"dB"
ERROR: ArgumentError: Multiplying a level by a Gain is disallowed. Use addition, or `linear` depending on context.
julia> linear(10u"dBm") * 20u"dB"
1000.0 mW
julia> linear(10u"dBm") + 20u"dB"
ERROR: ArgumentError: Adding a gain to a linear quantity is disallowed. Use multiplication or convert to `Level` first
As alluded to earlier, conversions can be tricky because so-called logarithmic units are not units in the conventional sense.
You may use linear to convert to a linear scale when you have a Level or
Quantity{<:Level} type. There is a fallback for Number, which just returns the number.
julia> linear(@dB 10u"mW"/u"mW")
10 mW
julia> linear(20u"dBm/Hz")
100.0 mW Hz^-1
julia> linear(30u"W")
30 W
julia> linear(12)
12
Linearizing a Quantity{<:Gain} or a Gain to a real number is ambiguous, because the real
number may represent a ratio of powers or a ratio of root-power (field) quantities. We
implement Unitful.uconvertp and Unitful.uconvertrp which may be
thought of as disambiguated uconvert functions. There is a one argument version that
assumes you are converting to a unitless number. These functions can take either a Gain
or a Real so that they may be used somewhat generically.
julia> uconvertrp(NoUnits, 20u"dB")
10.0
julia> uconvertp(NoUnits, 20u"dB")
100.0
julia> uconvertp(u"dB", 100)
20.0 dB
julia> uconvertp(u"Np", ℯ^2)
1.0 Np
julia> uconvertrp(u"Np", ℯ)
1//1 Np
This package displays logarithmic quantities using shorthand like dBm where available.
This should probably not be done in polite company. To quote "Guide for the Use of the
International System of Units (SI)," NIST Special Pub. 811 (2008):
The rules of Ref. [5: IEC 60027-3] preclude, for example, the use of the symbol dBm to indicate a reference level of power of 1 mW. This restriction is based on the rule of Sec. 7.4, which does not permit attachments to unit symbols.
The authorities say the reference level should always specified. In practice, this hasn't
stopped the use of dBm and the like on commercially available test equipment. Dealing with
these units is unavoidable in practice. When no shorthand exists, we follow NIST's advice in
displaying logarithmic quantities:
When such data are presented in a table or in a figure, the following condensed notation may be used instead: -0.58 Np (1 μV/m); 25 dB (20 μPa).
Unitful.@logscale
Unitful.linear
Unitful.reflevel
Unitful.uconvertp
Unitful.uconvertrp