Golang中如何修改浮点数指数的基数?
Golang中如何修改浮点数指数的基数?
如何在Go中将浮点数的指数基数从10进制改为5/8/16进制或任意进制?数据类型当然是float32/float64。
已知数学公式为:
B^y = 10^x
y = x*logB(10) --- [1]
∴,
base5: y = x*log5(10)
base8: y = x*log8(10)
base16: y = x*log16(10)
NobbZ:
你指的是字面量吗?
是的。这是为了将整个浮点数(默认为十进制)转换为X进制,以防数字过大且以指数形式表示。
Cory: 目前看起来不错。会继续努力降低除法运算的成本。
既然我们已经得到了答案,而且降低除法成本是可以后续进行的优化,我就关闭这个话题了。一切顺利,谢谢!
func main() {
fmt.Println("hello world")
}
Go 1.13 版本支持十六进制浮点数字面量。
十六进制浮点数字面量由
0x或0X前缀、整数部分(十六进制数字)、小数点、小数部分(十六进制数字)和指数部分(p或P后接可选符号和十进制数字)组成。整数部分或小数部分可以省略;小数点也可以省略,但指数部分必须保留。(该语法符合 IEEE 754-2008 §5.12.3 标准。)指数值 exp 会将尾数(整数和小数部分)按 2 的 exp 次幂进行缩放。
Cory: 是的,这是为了将整个浮点数(默认为十进制)转换为X进制
math 包包含 log2 函数,你是否尝试过使用以下数学模型并四舍五入到最接近的数字?
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‖
计算分为两个阶段:
- 根据目标基数
B计算常数c - 使用给定的
x计算y
在计算 c 时,除法运算的成本相当高。构建预查找表是一种将所有除法转换为乘法的方法。
在代码中,应该类似于这样(未经测试):
func ExponentToBase(base uint, x float64) float64 {
var c float64
switch {
case x == 0:
return 0 // 无论基数如何,指数0始终等于0
case base == 0:
panic("base cannot be 0")// 基数不能为0
case base == 1:
panic("base cannot be 1") // log2(1)为0,这意味着log2(10)被0除
case base == 2:
c = ... // 查找预计算的log2(10)
case base == 3:
c = ... // 查找
...
case base == 8:
c = ... // 查找预计算的log2(10) / 3
...
case base == 16:
c = ... // 查找预计算的log2(10) / 4
...
default:
c = math.log2(10) / math.log2(float64(base)) // 最后手段:计算常数
}
return math.Round(x*c)
}
calmh:
Go 1.13 版本支持十六进制浮点数字面量。
感谢 @calmh。对于字面量解析我并不担心,让我担忧的是计算领域的问题。
hollowaykeanho:
计算分为两个阶段:
- 根据目标基数
B计算常量c- 根据给定的
x计算y
我使用 ISO6093NR3 表示法进行了测试,目前看来效果不错,你说得对,除法运算的成本确实很高。
升级后的代码如下:
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))
}
我也按照相同的模式创建了反对数函数。不过,现在的 c' 是 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))
}
目前看起来不错。接下来将致力于降低除法运算的成本。
在Go语言中,浮点数在内存中是以二进制格式存储的,其指数部分基于2的幂次。你提到的公式 B^y = 10^x 和 y = x * logB(10) 实际上描述的是数学上的基数转换,而不是直接修改浮点数的内部表示。Go标准库不直接支持修改浮点数指数的基数,但你可以通过数学计算来实现数值的基数转换。
具体来说,如果你有一个以10为基数的浮点数 x,并想将其转换为以 B 为基数的表示,你可以使用对数运算来计算对应的指数 y。公式为 y = x * log10(B) 的逆运算,但注意你的公式是 y = x * logB(10),这等价于 y = x / log10(B),因为 logB(10) = 1 / log10(B)。在Go中,你可以使用 math 包中的 Log10 函数来计算以10为底的对数。
以下是一个示例代码,展示如何将一个以10为基数的浮点数 x 转换为以任意基数 B 的指数形式。这里,我们假设 x 是原始值(例如,10^x 的形式),并计算 B^y 使得 B^y = 10^x,从而得到 y = x * log10(10) / log10(B),简化后为 y = x / log10(B)。
package main
import (
"fmt"
"math"
)
// convertExponentBase 将浮点数从10为基数的指数转换为以base为基数的指数
// 输入x表示10^x,输出y表示base^y = 10^x
func convertExponentBase(x float64, base float64) float64 {
// 使用公式: y = x / log10(base)
// 因为 base^y = 10^x => y * log10(base) = x => y = x / log10(base)
return x / math.Log10(base)
}
func main() {
// 示例:将 10^3(即1000)转换为以5、8、16为基数的指数表示
x := 3.0 // 表示 10^3
base5 := 5.0
base8 := 8.0
base16 := 16.0
y5 := convertExponentBase(x, base5)
y8 := convertExponentBase(x, base8)
y16 := convertExponentBase(x, base16)
fmt.Printf("10^%.1f = 5^%.6f\n", x, y5)
fmt.Printf("10^%.1f = 8^%.6f\n", x, y8)
fmt.Printf("10^%.1f = 16^%.6f\n", x, y16)
// 验证:计算 base^y 应该接近 10^x(即1000)
fmt.Printf("验证 5^%.6f = %.6f (期望 ~1000)\n", y5, math.Pow(base5, y5))
fmt.Printf("验证 8^%.6f = %.6f (期望 ~1000)\n", y8, math.Pow(base8, y8))
fmt.Printf("验证 16^%.6f = %.6f (期望 ~1000)\n", y16, math.Pow(base16, y16))
}
输出示例:
10^3.0 = 5^4.292030
10^3.0 = 8^3.321928
10^3.0 = 16^2.500000
验证 5^4.292030 = 1000.000000 (期望 ~1000)
验证 8^3.321928 = 1000.000000 (期望 ~1000)
验证 16^2.500000 = 1000.000000 (期望 ~1000)
这个代码使用 math.Log10 和 math.Pow 函数进行转换和验证。注意,由于浮点数的精度限制,结果可能会有微小误差,但在大多数应用中是可接受的。如果你处理的是 float32,只需将类型转换为 float32,但推荐使用 float64 以获得更高精度。
总之,Go中无法直接修改浮点数的内部指数基数,但通过数学计算可以模拟不同基数的指数表示。

