# Types for log-domain values

**URL:** <https://discourse.julialang.org/t/types-for-log-domain-values/31394>\
**Category:** General Usage\
**Created:** [November 22, 2019, 4:55pm UTC](https://discourse.julialang.org/t/types-for-log-domain-values/31394 "2019-11-22T16:55:16Z")\
**Posts on this page:** 13\
**Page:** 1

<div class="post-metadata">

**Author:** ![maxmouchet](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/maxmouchet/32/7834_2.png) [@maxmouchet](https://discourse.julialang.org/u/maxmouchet)\
**Post date:** [November 22, 2019, 4:55pm UTC](https://discourse.julialang.org/t/types-for-log-domain-values/31394/1 "2019-11-22T16:55:16Z")

</div>

Hi,

I was wondering if there are existing solutions to the following problem.

In statistics, it is common to work with _likelihoods_ (positive reals) or _log-likelihoods_ values.  
Let’s say that we want to implement an algorithm for both types of values. One implementation takes the likelihood as an input, the other the log-likelihood.

The most basic API that comes to my mind is something like this:

```julia
alg(X::AbstractVector{Float64}) = ...
alglog(Xlog::AbstractVector{Float64}) = ...

```

One problem with this approach is that it’s easy to use the wrong implementation, as nothing prevents the user to call `alg(Xlog)`. I thought it would be nice if this could be enforced by the type system, for example:

```julia
struct LogValue{T}
    val::T
    base::Int
end

```

```julia
alg(X::AbstractVector{Float64}) = ...
alg(X::AbstractVector{LogValue{Float64}}) = ...

```

It’s now the responsibility of the function that produces the log-values to wrap them in the correct type. This also clean-ups the API by making use of multiple dispatch.

Has anyone experimented with something like this ? Do you have any other approaches ?

Thanks,  
Maxime

---

<div class="post-metadata">

**Author:** ![jling](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jling/32/212909_2.png) [@jling](https://discourse.julialang.org/u/jling)\
**Post date:** [November 22, 2019, 7:56pm UTC](https://discourse.julialang.org/t/types-for-log-domain-values/31394/2 "2019-11-22T19:56:59Z")

</div>

I think this is a bit over-engineering, like, when you use `cos` in any library, the first thing you do is check if it’s in degree or radian and it’s just a normal thing to do/know if you work in the domain.

---

<div class="post-metadata">

**Author:** ![rdeits](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/rdeits/32/286_2.png) [@rdeits](https://discourse.julialang.org/u/rdeits)\
**Post date:** [November 22, 2019, 8:30pm UTC](https://discourse.julialang.org/t/types-for-log-domain-values/31394/3 "2019-11-22T20:30:40Z")

</div>

I think this sounds like a good idea, and one which will be work uniquely well in Julia. I don’t work in statistics, so I can’t comment directly on your particular domain, but I would suggest taking inspiration from the way Colors.jl works:

Most image processing toolkits treat an image as a collection of ints (perhaps in the range 0-255, perhaps in some other range), or a collection of floats (perhaps in the range 0-1), which leads to inevitable errors when users mix up their representations or ranges. Colors.jl, on the other hand, does two clever things with the type system:

1. It creates `Gray`, `RGB`, `HSV`, etc. wrapper types which clearly separate pixel data from other kinds of data
2. It uses `FixedPointNumbers` to ensure that a `Gray` pixel is always conceptually in the range 0-1, even if it uses an int bewteen 0 and 255 under the hood.

That means that all pixel and image types can have a uniform interface and range, even though their underlying storage might be very different. I’ve found this really pleasant to work with, and there is little to no run-time cost for all of this niceness in Julia.

It seems like you could do something quite similar, creating a `LogLikelihood` struct which wraps the value and base, and then implement various log-likelihood operations on that type.

---

<div class="post-metadata">

**Author:** ![rdeits](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/rdeits/32/286_2.png) [@rdeits](https://discourse.julialang.org/u/rdeits)\
**Post date:** [November 22, 2019, 8:33pm UTC](https://discourse.julialang.org/t/types-for-log-domain-values/31394/4 "2019-11-22T20:33:08Z")

</div>

> [@jling](#):
>
> the first thing you do is check if it’s in degree or radian and it’s just a normal thing to do/know if you work in the domain.

My experience is that what actually happens is I get it wrong all over the place, and I suspect I’m [not alone](https://en.wikipedia.org/wiki/Mars_Climate_Orbiter). I think using separate types for different units is a great idea. But perhaps my experience isn’t universal.

---

<div class="post-metadata">

**Author:** ![oxinabox](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/oxinabox/32/206603_2.png) [@oxinabox](https://discourse.julialang.org/u/oxinabox)\
**Post date:** [November 22, 2019, 10:23pm UTC](https://discourse.julialang.org/t/types-for-log-domain-values/31394/5 "2019-11-22T22:23:45Z")

</div>

I would lock the base as `e`  
becauuse almost everything anyone work on is log base `e`.

Then you can do things that massively

```julia
struct LogNum{T<:Number} <: Number
    logval::T
end

convert(::Type{<:LogNum{T}, ::T) where {T} = LogNum(log(x))

value(x::LogNum) = exp(x.logval) 
convert(::Type{T}, x::LogNum{T}) = value(x)
# bunch nore convert and promote methods

Base.:*(x::LogNum, y::LogNum) = LogNum(x.logval + y.logval)
Base.:^(x::LogNum, y::Real) = LogNum(y * x.logval)

```

It would be great. Now you can just take products of probabilities and they don’t underflow for ages.

If we had the ability to overload numbers like this earlier,  
probability math would be much nicer.

---

<div class="post-metadata">

**Author:** ![StevenSiew](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stevensiew/32/218393_2.png) [@StevenSiew](https://discourse.julialang.org/u/StevenSiew)\
**Post date:** [November 22, 2019, 10:44pm UTC](https://discourse.julialang.org/t/types-for-log-domain-values/31394/6 "2019-11-22T22:44:43Z")

</div>

If you are prepared to do more work/coding then you can have a better precision for your REAL number by separating the Integer part (of your logarithm) from the fractional part.

```julia
    # Integer Plus Fraction is used to perform calculations
    # with base 10 logarithmns
    struct IpfType
        int::BigInt
        frac::Float64 # 0.0 <= frac < 1.0
    end

    function +(a::IpfType,b::IpfType)::IpfType
        c = a.frac + b.frac
        if c < 1 
            return IpfType(a.int + b.int,c)
        end
        if floor(c) == 1.0
            result = a.int + b.int + 1
            c = c - 1.0
            return IpfType(result,c)
        end
    end

```

I learn this trick by physically using log tables in high school.  
[http://www.sliderules.info/a-to-z/log-tables.htm](http://www.sliderules.info/a-to-z/log-tables.htm)

---

<div class="post-metadata">

**Author:** ![dpsanders](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dpsanders/32/3573_2.png) [@dpsanders](https://discourse.julialang.org/u/dpsanders)\
**Post date:** [November 22, 2019, 11:08pm UTC](https://discourse.julialang.org/t/types-for-log-domain-values/31394/7 "2019-11-22T23:08:43Z")

</div>

Doesn’t the fly in the ointment come when adding?

---

<div class="post-metadata">

**Author:** ![StevenSiew](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stevensiew/32/218393_2.png) [@StevenSiew](https://discourse.julialang.org/u/StevenSiew)\
**Post date:** [November 22, 2019, 11:13pm UTC](https://discourse.julialang.org/t/types-for-log-domain-values/31394/8 "2019-11-22T23:13:07Z")

</div>

DPSANDERS: Doesn’t the fly in the ointment come when adding?

Convert back to normal numbers, add them then convert back to IpfType

But really, if you need to add, a sliderule is the wrong type of tool to use.

---

<div class="post-metadata">

**Author:** ![oxinabox](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/oxinabox/32/206603_2.png) [@oxinabox](https://discourse.julialang.org/u/oxinabox)\
**Post date:** [November 22, 2019, 11:15pm UTC](https://discourse.julialang.org/t/types-for-log-domain-values/31394/9 "2019-11-22T23:15:11Z")

</div>

> [@dpsanders](#):
>
> Doesn’t the fly in the ointment come when adding?

Yep, can’t have all the things.

But for probability additions not that great anyway.

---

<div class="post-metadata">

**Author:** ![StevenSiew](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stevensiew/32/218393_2.png) [@StevenSiew](https://discourse.julialang.org/u/StevenSiew)\
**Post date:** [November 22, 2019, 11:21pm UTC](https://discourse.julialang.org/t/types-for-log-domain-values/31394/10 "2019-11-22T23:21:05Z")

</div>

I found that you can add in logarithm

 ![56%20AM](https://global.discourse-cdn.com/julialang/original/3X/2/1/21ba876b85d9d38f153189193725526affaf6f4a.png)

---

<div class="post-metadata">

**Author:** ![oxinabox](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/oxinabox/32/206603_2.png) [@oxinabox](https://discourse.julialang.org/u/oxinabox)\
**Post date:** [November 23, 2019, 12:23am UTC](https://discourse.julialang.org/t/types-for-log-domain-values/31394/11 "2019-11-23T00:23:36Z")

</div>

> [@StevenSiew](#):
>
> ```julia-auto
> struct IpfType
> int::BigInt
> frac::Float64 # 0.0 < frac < 1.0 end
> 
> ```

Sorting the integery part as a `BigInt` isn’t great they are multiple orders of magnitude slower than an `Int128`.  
Probably just go type-parametric, or `Int`, or `Int128`

For fraction part it is a bit of a waste all that representational capacity above 1 and below zero.  
Can instread store fractional part as a `UInt`, as the represtation of the value `frac/typemax(UInt)`

---

<div class="post-metadata">

**Author:** ![baggepinnen](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/baggepinnen/32/693_2.png) [@baggepinnen](https://discourse.julialang.org/u/baggepinnen)\
**Post date:** [November 23, 2019, 12:33am UTC](https://discourse.julialang.org/t/types-for-log-domain-values/31394/12 "2019-11-23T00:33:14Z")

</div>

Yep, I also get it wrong all the times. A vector of eigenvalues can represent many thing, poles of rational functions, zeros, poles and zeros in continuous or discrete time domain. They all look the same, but you need to dispatch to different methods depending on domain. Better wrap them in types to avoid repeatedly shooting yourself in the foot with the same foot gun over and over.

---

<div class="post-metadata">

**Author:** ![Tamas\_Papp](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tamas_papp/32/25949_2.png) [@Tamas\_Papp](https://discourse.julialang.org/u/Tamas_Papp)\
**Post date:** [November 24, 2019, 6:59am UTC](https://discourse.julialang.org/t/types-for-log-domain-values/31394/13 "2019-11-24T06:59:25Z")

</div>

This is possible to do in Julia with very little overhead: eg see this done for units in

> **[GitHub - PainterQubits/Unitful.jl: Physical quantities with arbitrary units](https://github.com/PainterQubits/Unitful.jl)**
>
> Physical quantities with arbitrary units. Contribute to PainterQubits/Unitful.jl development by creating an account on GitHub.

That said, the approach has _some_ cost to the user in terms of taking extra care with the implementation (lifting etc) and implementing the common algebraic operations. Whether that is worth it to you depends on your tolerance for bugs and other, complementary approaches to ensure correct code (eg testing, reviews).

Personally, I would just use log probabilities all the time, and rely on unit testing (which one would do anyway) to ensure correctness.
