Golang中strconv.ParseFloat可能存在潜在bug

Golang中strconv.ParseFloat可能存在潜在bug 在检查我们的一个微服务时,我注意到它无法接受像“0x15e-2”这样的字符串。

该服务内部使用了 strconv.ParseFloat,而上面的字符串直接取自 Go 参考文档(The Go Programming Language Specification - The Go Programming Language)。

错误信息是:

strconv.ParseFloat: parsing "0x15e-2": invalid syntax

一个重现错误的最小示例:

Go Playground - The Go Programming Language

这是 Go 规范中的错误,还是 ParseFloat 内部的错误?


更多关于Golang中strconv.ParseFloat可能存在潜在bug的实战教程也可以访问 https://www.itying.com/category-94-b0.html

6 回复

这一行确实应该被标记为无效数字,就像它下面的那些一样。

更多关于Golang中strconv.ParseFloat可能存在潜在bug的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


那么为什么这部分内容会出现在浮点数章节呢?

你好 @christophberger

感谢你的回复。

我想在这种情况下,文档肯定有问题。

你在哪里看到这个?我看到的唯一出现这个数字的地方是在那行说明它被解释为整数减法的代码上:

0x15e-2      // == 0x15e - 2 (integer subtraction)

你好 @Scarjit

我认为错误发生的原因是十六进制数的指数必须以“p”开头。字母“e”是模糊的,因为它也是一个有效的十六进制数字。

图片

Go Playground - The Go Programming Language

根据 Go 语言规范,0x15e-2 确实是有效的浮点数字面量,它表示十六进制浮点数。规范中明确说明十六进制浮点数的格式为 0x 前缀后接十六进制数字,可选的指数部分(pP 后接十进制指数)。

然而,strconv.ParseFloat 函数在解析十六进制浮点数字符串时,要求指数部分必须使用 pP 作为指数标记,而不是 eE。在 0x15e-2 中,e 被解释为十六进制数字的一部分(即数字 15e),而 -2 被当作独立的指数部分,但缺少了必需的 pP 标记,因此导致解析错误。

正确的十六进制浮点数表示应为 0x15p-2,其中 p-2 表示乘以 2 的 -2 次方。以下是一个示例代码,演示了正确的解析方式:

package main

import (
    "fmt"
    "strconv"
)

func main() {
    // 正确的十六进制浮点数字符串
    val, err := strconv.ParseFloat("0x15p-2", 64)
    if err != nil {
        fmt.Println("解析错误:", err)
    } else {
        fmt.Printf("解析结果: %v\n", val) // 输出: 解析结果: 5.25
    }

    // 尝试解析 "0x15e-2" 会失败
    val2, err2 := strconv.ParseFloat("0x15e-2", 64)
    if err2 != nil {
        fmt.Println("解析错误:", err2) // 输出: 解析错误: strconv.ParseFloat: parsing "0x15e-2": invalid syntax
    } else {
        fmt.Printf("解析结果: %v\n", val2)
    }
}

如果你需要解析 0x15e-2 这种格式的字符串(其中 e 是十六进制数字的一部分,-2 是十进制指数),你需要先将其转换为标准格式。例如,0x15e-2 应理解为十六进制整数 15e(即十进制 350)乘以 10 的 -2 次方,但这不符合 Go 的十六进制浮点数规范。这种情况下,你需要自定义解析逻辑:

package main

import (
    "fmt"
    "strconv"
    "strings"
)

func parseCustomHexFloat(s string) (float64, error) {
    // 假设字符串格式为 "0x<hex>e<decimal>"
    if !strings.HasPrefix(s, "0x") {
        return 0, fmt.Errorf("invalid prefix")
    }
    s = s[2:] // 移除 "0x"
    
    // 分割十六进制数字和指数部分
    parts := strings.Split(s, "e")
    if len(parts) != 2 {
        return 0, fmt.Errorf("invalid format")
    }
    
    hexPart := parts[0]
    expPart := parts[1]
    
    // 解析十六进制部分为整数
    intVal, err := strconv.ParseInt(hexPart, 16, 64)
    if err != nil {
        return 0, err
    }
    
    // 解析指数部分
    exp, err := strconv.ParseInt(expPart, 10, 64)
    if err != nil {
        return 0, err
    }
    
    // 计算最终值: intVal * 10^exp
    result := float64(intVal)
    for i := int64(0); i < exp; i++ {
        result *= 10
    }
    for i := int64(0); i > exp; i-- {
        result /= 10
    }
    return result, nil
}

func main() {
    val, err := parseCustomHexFloat("0x15e-2")
    if err != nil {
        fmt.Println("解析错误:", err)
    } else {
        fmt.Printf("解析结果: %v\n", val) // 输出: 解析结果: 3.5
    }
}

总结:strconv.ParseFloat 的行为符合 Go 语言规范,它要求十六进制浮点数使用 pP 作为指数标记。0x15e-2 不是有效的输入,因为 e 不是合法的指数标记。如果你需要解析此类字符串,需要实现自定义的解析函数。

回到顶部