math package has a log2 function, have you tried with the following mathematical model with rounding to the nearest number?
B^y = 10^x
y = x*logB(10) --- [1]
From [1],
y = ‖x*(log2(10) / log2(B))‖
y = ‖x*c, c = log2(10) / log2(B)‖ --- [2]
∴,
base2,
y = x*c, c = log2(10) / log2(2)
y = x*c, c = log2(10)
y = ‖x*log2(10)‖
base5,
y = x*log5(10)
y = ‖x*c, c = log2(10) / log2(5)‖
base8,
y = x*log8(10)
y = x*c, c = log2(10) / log2(8)
y = ‖x*c, c = log2(10) / 3‖
base16,
y = x*log16(10)
y = x*c, c = log2(10) / log2(16)
y = ‖x*c, c = log2(10) / 4‖
There are 2 calculation stages:
calculate the c constant based on the target base B.
calculate the y with the given x.
The division op’ cost is quite expensive for calculating c. A pre-built lookup table is one way to mitigate all divisions to multiplication.
On code, it should looks something like this (untested):
func ExponentToBase(base uint, x float64) float64 {
var c float64
switch {
case x == 0:
return 0 // indice 0 is always equal to 0 regardless base
case base == 0:
panic("base cannot be 0")// base cannot be 0
case base == 1:
panic("base cannot be 1") // log2(1) is 0 and that means log2(10) is divided by 0
case base == 2:
c = ... // lookup pre-calculated log2(10)
case base == 3:
c = ... // lookup
...
case base == 8:
c = ... // lookup pre-calculated log2(10) / 3
...
case base == 16:
c = ... // lookup pre-calculated log2(10) / 4
...
default:
c = math.log2(10) / math.log2(float64(base)) // calculate constant as last resort
}
return math.Round(x*c)
}
A hexadecimal floating-point literal consists of a 0x or 0X prefix, an integer part (hexadecimal digits), a radix point, a fractional part (hexadecimal digits), and an exponent part ( p or P followed by an optional sign and decimal digits). One of the integer part or the fractional part may be elided; the radix point may be elided as well, but the exponent part is required. (This syntax matches the one given in IEEE 754-2008 §5.12.3.) An exponent value exp scales the mantissa (integer and fractional part) by 2exp.
Thanks @calmh. No worries about literal parsing. It is the computational world that is worrying for me.
I tested on my side using the ISO6093NR3 notation, looks good so far and you’re right, the division computation is very costly.
Upgrading the code would be:
func ExponentToBase(base uint, x float64) uint64 {
var c float64
switch {
case x == 0:
return 1 // x^0 = y^0 = 1
case base == 0:
panic("base cannot be 0")// base cannot be 0
case base == 1:
panic("base cannot be 1") // log2(1) = 0. ∴, log2(10) / 0
case base == 2:
c = 3.321928094887362 // log2(10)
case base == 8:
c = 1.1073093649624541 // log2(10) / 3
case base == 10:
return uint64(x)
case base == 16:
c = 0.8304820237218405 // log2(10) / 4
default:
c = math.log2(10) / math.log2(float64(base)) // calculate
}
return uint64(math.Round(x*c))
}
I also created the anti-log using the same pattern. However, it was c' is now 1/c
func BaseToExponent(base uint, x float64) uint64 {
var c float64
switch {
case x == 0:
return 1 // x^0 = y^0 = 1
case base == 0:
panic("base cannot be 0")// base cannot be 0
case base == 1:
panic("base cannot be 1") // log2(1) = 0. ∴, log2(10) / 0
case base == 2:
c = 0.3010299956639812 // 1 / log2(10)
case base == 8:
c = 0.9030899869919435 // 3 / log2(10)
case base == 10:
return uint64(x)
case base == 16:
c = 1.2041199826559248 // 4 / log2(10)
default:
c = math.log2(float64(base)) / math.log2(10) // calculate
}
return uint64(math.Round(x*c))
}
Looks good so far. Will work on reducing the division cost.