# Logarithmic scales

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`

.

## Constructing logarithmic quantities

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

Addition will be discussed more later.

Note that logarithmic "units" can only multiply or be multiplied by pure numbers and linear units, not other logarithmic units or quantities. This is done to avoid issues with commutativity and associativity, e.g. `3*dB*m^-1 == (3dB)/m`

, but `3*m^-1*dB == (3m^-1)*dB`

does not make much sense. This is because `dB`

acts more like a constructor than a proper unit.

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

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** —

*Type*.

struct Gain{L, S, T<:Real} <: LogScaled{L}

A logarithmic scale-based gain or attenuation factor. This type has one field, `val::T`

. For example, given a gain of `20dB`

, we have `val===20`

. This type differs from `Unitful.Level`

in that `val`

is stored after computing the logarithm.

One might expect that any gain / attenuation factor should be convertible to a pure number, 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 real number 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.

### "Dimensionful" logarithmic 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 `Level`

s for short:

#
** Unitful.Level** —

*Type*.

struct Level{L, S, T<:Number} <: LogScaled{L}

A logarithmic scale-based level. Details about the logarithmic scale are encoded in `L <: LogInfo`

. `S`

is a reference quantity for the level, not a type. This type has one field, `val::T`

, and the log of the ratio `val/S`

is taken. This type differs from `Unitful.Gain`

in that `val`

is a linear quantity.

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** —

*Type*.

abstract type LogScaled{L<:LogInfo} <: Number end

Abstract supertype of `Unitful.Level`

and `Unitful.Gain`

. It is only used in promotion to put levels and gains onto a common log scale.

## Multiplication rules

Multiplying a dimensionless logarithmic quantity by a pure number acts as like it does for linear quantities:

julia> 3u"dB" * 2 6 dB julia> 2 * 0u"dB" 0 dB

Justification by example: consider the example of the exponential attenuation of a signal on a lossy transmission line. If the attenuation goes like $10^{-kx}$, then the (power) attenuation in dB is $-10kx$. We see that the attenuation in dB is linear in length. For an attenuation constant of 3dB/m, we better calculate 6dB for a length of 2m.

Multiplying a dimensionful logarithmic quantity by a pure number acts differently than multiplying a gain/attenuation by a pure number. Since `0dBm == 1mW`

, we better have that `0dBm * 2 == 2mW`

, implying:

julia> 0u"dBm" * 2 3.010299956639812 dBm

Logarithmic quantities can only be multiplied by pure numbers, 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

Mathematical operations are forwarded to the logarithmic part, so that for example, `100*((0dBm)/s) == (20dBm)/s`

. We allow linear units to commute with logarithmic quantities for convenience, though the association is understood (e.g. `s^-1*(3dBm) == (3dBm)/s`

).

The behavior of multiplication is summarized in the following table, with entries marked by † indicate prohibited operations. This table is populated automatically whenever the docs are built.

* | 10 | Hz^-1 | dB | dBm | 1/Hz | 1mW | 3dB | 3dBm |
---|---|---|---|---|---|---|---|---|

10 | 100 | 10 Hz^-1 | 10 dB | 10.0 dBm | 10 Hz^-1 | 10 mW | 30 dB | 13.0 dBm |

Hz^-1 | Hz^-2 | dB Hz^-1 | dBm Hz^-1 | 1 Hz^-2 | 1 mW Hz^-1 | [3 dB] Hz^-1 | [3.0 dBm] Hz^-1 | |

dB | † | † | † | † | † | † | ||

dBm | † | † | † | † | † | |||

1/Hz | 1 Hz^-2 | 1 mW Hz^-1 | † ‡ | 1.9952623149688795 mW Hz^-1 | ||||

1mW | 1 mW^2 | 1.9952623149688795 mW | 1.9952623149688795 mW^2 | |||||

3dB | 6 dB | 6.0 dBm | ||||||

3dBm | 3.9810717055349722 mW^2 |

‡: `1/Hz * 3dB`

could be allowed, technically, but we throw an error its unclear if 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

## Addition rules

We can add logarithmic quantities without reference levels specified (`Gain`

s):

julia> 20u"dB" + 20u"dB" 40 dB

The numbers out front of the `dB`

just add: when we talk about gain or attenuation, we work in logarithmic units so that we can add rather than multiply gain factors. The same behavior holds when we add a `Gain`

to a `Level`

or vice versa:

julia> 20u"dBm" + 20u"dB" 40.0 dBm

In the case where you have differing logarithmic scales for the `Level`

and the `Gain`

, the logarithmic scale of the `Level`

is used for the result:

julia> 10u"dBm" - 1u"Np" 1.3141103619349632 dBm

For logarithmic quantities with the same reference levels, the numbers out in front do not simply add:

julia> 20u"dBm" + 20u"dBm" 23.010299956639813 dBm julia> 2 * 20u"dBm" 23.010299956639813 dBm

This is because `dBm`

represents a power, ultimately. If we have some amount of power and we double it, we'd better get roughly `3 dB`

more power. Note that the juxtaposition `20dBm`

will ensure that 20 dBm is constructed before multiplication by 2 in the above example. If you were to type `2*20*dBm`

, you'd get 40 dBm.

If the reference levels differ but both levels represent a power, we fall back to linear quantities:

julia> 20u"dBm" + @dB 1u"W"/u"W" 1.1 kg m^2 s^-3

i.e. `1.1 W`

.

Rules for addition are summarized in the following table, with entries marked by † indicating prohibited operations. This table is populated automatically whenever the docs are built.

+ | 100 | 20dB | 1Np | 10.0dBm | 10.0dBv | 1mW |
---|---|---|---|---|---|---|

100 | 200 | † | † | † | † | † |

20dB | 40 dB | † | 30.0 dBm | 30.0 dBV | † | |

1Np | 2 Np | 18.685889638065035 dBm | 18.685889638065035 dBV | † | ||

10.0dBm | 13.010299956639813 dBm | † | 11.0 mW | |||

10.0dBV | 16.02059991327962 dBV | † | ||||

1mW | 2 mW |

Notice that we disallow implicit conversions between dimensionless logarithmic quantities and real numbers. This is because the results can depend on promotion rules in addition to being ambiguous because of the root-power vs. power ratio issue. If `100 + 10dB`

were evaluated as `20dB + 10dB == 30dB`

, then we'd get `1000`

, but if it were evaluated as `100+10`

, we'd get `110`

.

Also, although it is possible in principle to add e.g. `20dB + 1Np`

, notice that we have not implemented that because it is unclear whether the result should be in nepers or decibels, and it is also unclear how to handle that question more generally as other logarithmic scales are introduced.

## Conversion

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

## Notation

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).

## Custom logarithmic scales

#
** Unitful.@logscale** —

*Macro*.

@logscale(symb,abbr,name,base,prefactor,irp)

Define a logarithmic scale. Unlike with units, there is no special treatment for power-of-ten prefixes (decibels and bels are defined separately). However, arbitrary bases are possible, and computationally appropriate `log`

and `exp`

functions are used in calculations when available (e.g. `log2`

, `log10`

for base 2 and base 10, respectively).

This macro defines a `MixedUnits`

object identified by symbol `symb`

. This can be used to construct `Gain`

types, e.g. `3*dB`

. Additionally, two other `MixedUnits`

objects are defined, with symbols `Symbol(symb,"_rp")`

and `Symbol(symb,"_p")`

, e.g. `dB_rp`

and `dB_p`

, respectively. These objects serve nearly the same purpose, but have extra information in their type that indicates whether they should be considered as root-power ratios or power ratios upon conversion to pure numbers.

This macro also defines another macro available as `@symb`

. For example, `@dB`

in the case of decibels. This can be used to construct `Level`

objects at parse time. Usage is like `@dB 3V/1V`

.

`prefactor`

is the prefactor out in front of the logarithm for this log scale. In all cases it is defined with respect to taking ratios of power quantities. Just divide by two if you want to refer to root-power / field quantities instead.

`irp`

(short for "is root power?") specifies whether the logarithmic scale is defined with respect to ratios of power or root-power quantities. In short: use `false`

if your scale is decibel-like, or `true`

if your scale is neper-like.

Examples:

julia> using Unitful: V, W julia> @logscale dΠ "dΠ" Decipies π 10 false dΠ julia> @dΠ π*V/1V 20.0 dΠ (1 V) julia> dΠ(π*V, 1V) 20.0 dΠ (1 V) julia> @dΠ π^2*V/1V 40.0 dΠ (1 V) julia> @dΠ π*W/1W 10.0 dΠ (1 W)

## API

#
** Unitful.linear** —

*Function*.

linear(x::Quantity) linear(x::Level) linear(x::Number) = x

Returns a quantity equivalent to `x`

but without any logarithmic scales.

It is important to note that this operation will error for `Quantity{<:Gain}`

types. This is for two reasons:

`20dB`

could be interpreted as either a power or root-power ratio.- Even if
`-20dB/m`

were interpreted as, say,`0.01/m`

, this means something fundamentally different than`-20dB/m`

.`0.01/m`

cannot be used to calculate exponential attenuation.

#
** Unitful.reflevel** —

*Function*.

reflevel(x::Level{L,S}) reflevel(::Type{Level{L,S}}) reflevel(::Type{Level{L,S,T}})

Returns the reference level, e.g.

julia> reflevel(3u"dBm") 1 mW

#
** Unitful.uconvertp** —

*Function*.

uconvertp(u::Units, x) uconvertp(u::MixedUnits, x)

Generically, this is the same as `Unitful.uconvert`

. In cases where unit conversion would be ambiguous without further information (e.g. `uconvert(dB, 10)`

), `uconvertp`

presumes ratios are of root-power quantities.

It is important to note that careless use of this function can lead to erroneous calculations. Consider `Quantity{<:Gain}`

types: it is tempting to use this to transform `-20dB/m`

into `0.1/m`

, however this means something fundamentally different than `-20dB/m`

. Consider what happens when you try to compute exponential attenuation by multiplying `0.1/m`

by a length.

Examples:

julia> using Unitful julia> uconvertp(u"dB", 10) 10.0 dB julia> uconvertp(NoUnits, 20u"dB") 100.0

#
** Unitful.uconvertrp** —

*Function*.

uconvertrp(u::Units, x) uconvertrp(u::MixedUnits, x)

In most cases, this is the same as `Unitful.uconvert`

. In cases where unit conversion would be ambiguous without further information (e.g. `uconvert(dB, 10)`

), `uconvertp`

presumes ratios are of power quantities.

It is important to note that careless use of this function can lead to erroneous calculations. Consider `Quantity{<:Gain}`

types: it is tempting to use this to transform `-20dB/m`

into `0.01/m`

, however this means something fundamentally different than `-20dB/m`

. Consider what happens when you try to compute exponential attenuation by multiplying `0.01/m`

by a length.