Changing base number for float's exponent in Go?

How do you change the base of float’s exponent in Go from base10 to base5/8/16 or any base number system? Data type is float32/float64 of course.

Given that the mathematical equations is:

B^y = 10^x
y = x*logB(10)  --- [1]

∴,
base5:  y = x*log5(10)
base8:  y = x*log8(10)
base16: y = x*log16(10)
1 Like

In a literal you mean? I doubt you can change that.

2 Likes

Yes. It’s for changing the entire float number (default is base10) to baseX, in case the number is huge and represented in exponent form.

1 Like

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:

  1. calculate the c constant based on the target base B.
  2. 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)
}
1 Like

Go 1.13 has base 16 (hex) float literals.

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.

2 Likes

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.

1 Like

I’ll close this topic since we got an answer and reducing division cost is an optimization that can be done later. All good and thank you!

2 Likes

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.