I think one can calculate logarithmn using only one known logarithmn value log2(1.5)

If civilization collapses , then how do we calculate logarithm. I think that one can calculate logarithmn using only one knwn logarithm value log2(1.5).

I already know that log10(x) = ln(x)/ln(10)

So if we know log of any base, then we can calculate log10 of any number.

Therefore we should look for a base that is the easiest to calculate and the base is 2. I do wish to calculate the log of base 1 but I leave that puzzle to another day.

Then we have

logB( x + y) = logB(x) + logB(1 + y/x)

so

log2(1.75) = log2(1.5 + 0.25) = log2(1.5) + log2(1 + 0.25/1.5)

= log2(1.5) + log2(1) + log2(1 + (0.25/1.5)/1)

= log2(1.5) + log2(1) + log2(1 + 0.166666666)

= log2(1.5) + log2(1) + log2(1) + log2(1.1666666)

= log2(1.5) + log2(1) + log2(1) + log2(2.333333 / 2 )

= log2(1.5) + log2(1) + log2(1) + log2(2.333333) + log2(1/2)

so all we need is to calculate log2(2.3333333) and we have the answer (since we know log2(1.5)

log2(2.333333) = log2(1.5 + 0.83333333)

and we use the identity ( x = 1.5 )

logB( x + y) = logB(x) + logB(1 + y/x) Again!!!

1 Like

Mathematics will be rediscovered (or just not forgotten), and we can use the same approaches we used to, via iterative methods. See for example, this section of the Wikipedia page on logarithms.

(I’m not sure if this is intended as a joke, but if not, let me point out that the base-1 logarithm does not make sense. For e.g. \log_1(2), you’d need to find a real number x such that 1^x = 2, which obviously does not exist (mathematically speaking, f: \mathbb{R}^+ \to \mathbb{R}^+: t \mapsto 1^t (=1) is not surjective). Also, what would be \log_1(1)? There are many options (f is not injective either).)

So to calculate \log_2(1.75), given knowledge of \log_2(1.5), you only need to be able to calculate \log_2(7/3). But how did this simplify the problem? Also, what makes 1.5 special here? You could equally use any other positive number.

It’s possible that there’s some merit to your approach, if you can show that you are indeed simplifying the problem / obtaining a partial solution (e.g. digits in the binary or decimal expansion). If so, you’ll have (re)discovered an iterative method to calculate logarithms, like the ones linked to above.

5 Likes

Note that you can also use Taylor series and reductions to bring the number close to 1.

Future archeologists will unearth some slide rules made of stainless steel. Like this rare beautiful circular slide rule from Hungary:


(image courtesy of the Slide Rule Museum, apologies to nerds like me who will end up wasting hours looking at them).

14 Likes

The proof of the pudding is in the eating.

maxiter = 64

function mylog2(level::Int64,x::Float64)
    global maxiter
    if level > maxiter
        return 0.0
    end
    level += 1  # Increase the depth of function call
    local adjustment::Int64=0
    while x >= 3.0
        adjustment += 1
        x /= 2.0
    end
    while x < 1.5
        adjustment -= 1
        x *= 2.0
    end
    # at this point the value of x >= 1.5 and x < 3.0
    if x == 1.5 # do the case when x is exactly 1.5
        return Float64(adjustment) + log(2,1.5)
    end
    # at this point x > 1.5
    # logB( x + y) = logB(x) + logB(1 + y/x)
    # log2( 1.5 + d) = log2(1.5) + log2(1 + d/1.5)
    #                = log2(1.5) + log2(1) + log2(1 + (d/1.5)/1)
    #                = log2(1.5) + log2(1 + d/1.5)
    #
    #                  let z = 1 + d/1.5
    # if z >= 1.5
    #                = log2(1.5) + mylog2(level,z)
    # otherwise
    #                = log2(1.5) + 0.5 * mylog2(level,z^2)
    #
    local d,z
    d = x - 1.5
    z = 1 + d/1.5
    if z >= 1.5
        return Float64(adjustment) + log(2,1.5) + mylog2(level,z)
    else
        return Float64(adjustment) + log(2,1.5) + 0.5 * mylog2(level,z*z)
    end
end

println("log2(4.5)     = ",log(2,4.5) )
println("mylog2(0,4.5) = ",mylog2(0,4.5) )

#  Sample output
#
# log2(4.5)     = 2.1699250014423126
# mylog2(0,4.5) = 2.169925001442312
1 Like

And this is the magic number log2(1.5)

julia> log(2,1.5)
0.5849625007211562

That indeed seems to work. Note that there is nothing special about 1.5: if you replace it with any other number x > 0 the approach is still valid. In particular, if you take x = 1, you don’t need to protect the value of \log_2(1.5) during the apocalypse, as you now simply have \log_2(x) = 0. This further makes the algorithm a bit easier, as z = x and you will always take the else branch.
In fact, with x = 1 you’ll have rediscovered the iterative approach described here.

3 Likes

The book by Briggs, Arithmetica Logarithmica (1624), had a large part with methods for calculating logarithms.

5 Likes

I had one of these circular slide rules but, alas, only a plastic one. I fear it will no longer be useful to future archaeologists!

1 Like

Here is the version 2 of the source code. Yes, changing the konst from 1.5 to 1.4 works. It works as long as konst is greater than 1.0

it even works when konst is 2.0 which means that is nothing special about log2(1.5)

const maxiter = 64
const konst = 1.5

function mylog2(level::Int64,x::Float64)
    global maxiter
    global konst
    if level > maxiter
        return 0.0
    end
    level += 1  # Increase the depth of function call
    local adjustment=0.0
    while x >= 2*konst
        adjustment += 1.0
        x /= 2.0
    end
    while x < konst
        adjustment -= 1.0
        x *= 2.0
    end
    # at this point the value of x >= 1.5 and x < 3.0
    if x == konst # do the case when x is exactly 1.5
        return adjustment + log(2,konst)
    end
    # at this point x > 1.5
    # logB( x + y) = logB(x) + logB(1 + y/x)
    # log2( 1.5 + d) = log2(1.5) + log2(1 + d/1.5)
    #                = log2(1.5) + log2(1) + log2(1 + (d/1.5)/1)
    #                = log2(1.5) + log2(1 + d/1.5)
    #
    #                  let z = 1 + d/1.5
    # if z >= 1.5
    #                = log2(1.5) + mylog2(level,z)
    # otherwise
    #                = log2(1.5) + 0.5 * mylog2(level,z*z)
    #
    local d,z
    d = x - konst
    z = 1 + d/konst
    if z >= konst
        return adjustment + log(2,konst) + mylog2(level,z)
    else
        local numofsquaring = 0
        local newz = z
        while newz < konst
            newz = newz*newz
            numofsquaring += 1
        end
        return adjustment + log(2,konst) + 0.5^numofsquaring * mylog2(level,newz)
    end
end

mylog2(x) = mylog2(0,x)  # stub to have single arguement

println("log2(4.5)     = ",log(2,4.5) )
println("mylog2(0,4.5) = ",mylog2(4.5) )

#  Sample output
#
# log2(4.5)     = 2.1699250014423126
# mylog2(0,4.5) = 2.169925001442312

what is the optimal value of your konst? :sweat_smile:

Hi,

I think you are abusing and overusing global and local, it’s not very Julia idiomatic style.

This results in the same:

function _mylog2(level::Integer, x; maxiter=64, konst=1.5)
    if level > maxiter
        return 0.0 
    end 
    level += 1  # Increase the depth of function call
    adjustment=0.0

    while x >= 2*konst
        adjustment += 1.0 
        x /= 2.0 
    end 

    while x < konst
        adjustment -= 1.0 
        x *= 2.0 
    end 
    if x == konst # do the case when x is exactly 1.5
        return adjustment + log(2,konst)
    end 
    d = x - konst
    z = 1 + d / konst

    if z >= konst
        return adjustment + log(2,konst) + _mylog2(level,z)
    else
        numofsquaring = 0 
        newz = z 
        while newz < konst
            newz = newz*newz
            numofsquaring += 1
        end
        return adjustment + log(2,konst) + 0.5^numofsquaring * _mylog2(level,newz)
    end 
end

There is more ways to improve such as being generic to the input type T. and avoiding explicit values such as 2.0 which introduces conversions to Float64.

2 Likes

Here’s how they do it professionally:

2 Likes

Here is version 3 of the source code which can handle different Floating Point Types.

using DoubleFloats

function mylog2(level::Integer,x;maxiter=64,konst=1.5)
    local mytype = typeof(x)
    local myone = one(mytype)
    local mytwo = myone + myone
    local mykonst = convert(mytype,konst)
    if level > maxiter
        return zero(mytype)
    end
    level += 1  # Increase the depth of function call
    local adjustment = zero(mytype)
    while x >= 2*mykonst
        adjustment += myone
        x /= mytwo
    end
    while x < mykonst
        adjustment -= myone
        x *= mytwo
    end
    # at this point the value of x >= 1.5 and x < 3.0
    if x == mykonst # do the case when x is exactly 1.5
        return adjustment + log(2,mykonst)
    end
    # at this point x > 1.5
    local d,z
    d = x - mykonst
    z = myone + d/mykonst
    if z >= convert(mytype,mykonst)
        return adjustment + log(2,mykonst) + mylog2(level,z,maxiter=maxiter,konst=konst)
    else
        local numofsquaring = 0
        local newz = z
        while newz < mykonst
            newz = newz*newz
            numofsquaring += 1
        end
        return adjustment + log(2,mykonst) + (myone/mytwo)^numofsquaring * mylog2(level,newz,maxiter=maxiter,konst=konst)
    end
end

mylog2(x) = mylog2(0,x)  # stub to have single arguement

println("log2(4.5)     = ",log(2,4.5) )
println("mylog2(0,4.5) = ",mylog2(4.5) )
println()
println("log2(4.5)     = ",log(2,big(4.5)) )
println("mylog2(0,4.5) = ",mylog2(DoubleFloat(4.5)) )
println("mylog2(0,4.5) = ",mylog2(big(4.5)) )
#  Sample output
#
# log2(4.5)     = 2.1699250014423126
# mylog2(0,4.5) = 2.169925001442312

“what is the optimal value of your konst?”

The optimum value of konst is 2

We have to remember log2(2) == 1

Here without local (it is not required):

function mylog2(level::Integer, x::T;maxiter=64,konst=T(1.5)) where T
    if level > maxiter
        return zero(T)
    end 
    level += 1  # Increase the depth of function call
    adjustment = zero(T)
    while x >= 2*konst
        adjustment += T(1)
        x /= T(2)
    end 
    while x < konst
        adjustment -= T(1)
        x *= T(2)
    end 
    # at this point the value of x >= 1.5 and x < 3.0
    if x == konst # do the case when x is exactly 1.5
        return adjustment + log(2,konst)
    end 
    # at this point x > 1.5
    d = x - konst
    z = T(1) + d/konst
    if z >= convert(T,  konst)
        return adjustment + log(2,konst) + mylog2(level,z,maxiter=maxiter,konst=konst)
    else
        numofsquaring = 0 
        newz = z 
        while newz < konst
            newz = newz*newz
            numofsquaring += 1
        end
        return adjustment + log(2,konst) + (T(1)/T(2))^numofsquaring * mylog2(level,newz,maxiter=maxiter,konst=konst)
    end 
end
1 Like

Would not bet on that, some plastics last for a really long time, unfortunately.

Thanks for informing me that I can use T where T and I don’t need the local variables.

Here is version 4 of the source code

using DoubleFloats

function mylog2(level::Integer,x::T;maxiter=64,konst=T(1.5)) where T
    # Sanity Checking x and konst must have the same type
    if typeof(konst) != T 
        println("Failed Sanity Checking. x and konst must have the same type.")
        return NaN
    end
    if level > maxiter
        return zero(T)
    end
    level += 1  # Increase the depth of function call
    adjustment = zero(T)
    while x >= 2*konst
        adjustment += one(T)
        x /= T(2)
    end
    while x < konst
        adjustment -= one(T)
        x *= T(2)
    end
    # at this point the value of x >= 1.5 and x < 3.0
    if x == konst # do the case when x is exactly 1.5
        return adjustment + log(2,konst)
    end
    # at this point x > 1.5
    local d,z
    d = x - konst
    z = one(T) + d/konst
    if z >= konst
        return adjustment + log(2,konst) + mylog2(level,z,maxiter=maxiter,konst=konst)
    else
        local numofsquaring = 0
        local newz = z
        while newz < konst
            newz = newz*newz
            numofsquaring += 1
        end
        return adjustment + log(2,konst) + T(0.5)^numofsquaring * mylog2(level,newz,maxiter=maxiter,konst=konst)
    end
end

mylog2(x) = mylog2(0,x)  # stub to have single arguement

println("log2(4.5)     = ",log(2,4.5) )
println("mylog2(0,4.5) = ",mylog2(4.5) )
println()
println("log2(4.5)     = ",log(2,big(4.5)) )
println("mylog2(0,4.5) = ",mylog2(DoubleFloat(4.5)) )
println("mylog2(0,4.5) = ",mylog2(big(4.5)) )

Example:

julia> mylog2(0,DoubleFloat(4.5),konst=1.2)
Failed Sanity Checking. x and konst must have the same type.
NaN

julia> mylog2(0,DoubleFloat(4.5),konst=DoubleFloat(1.2))
2.16992500144231236290747788789562852