Golang中如何解析big.Float并确保输出与输入完全一致

Golang中如何解析big.Float并确保输出与输入完全一致 作为我正在进行的项目的一个练习,我需要能够解析任意精度的数字,并能够将它们打印出来,得到与输入字符串完全相同的输出字符串。

我无法找到使用 big.Float 实现这一点的正确方法!

我尝试使用 f, i, err := big.ParseFloat(pi, 10, big.MaxPrec, big.AwayFromZero),但我得到了完全相同的错误输出,所以我无法判断是解析过程出错了,还是格式化输出出错了,或者两者都有问题?

这些函数的文档记录得不是很好,例如 i 返回值在任何我能找到的地方都没有描述它应该代表什么。每次它的值都是 10,我只能假设它是基数。

package main

import (
    "fmt"
    "math/big"
    "strings"
)

func main() {
    pi := "3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679821480865132823066470938446095505822317253594081284811174502841027019385211055596446229489549303819644288109756659334461284756482337867831652712019091456485669234603486104543266482133936072602491412737245870066063155881748815209209628292540917153644"
    fmt.Println(pi)
    precision := uint(len(pi[strings.Index(pi,".")+1:]))
    fmt.Printf("%d decimal digits!\n", precision)
    bf := big.NewFloat(0.0)
    bf, _ = bf.SetPrec(precision).SetMode(bf.Mode()).SetString(pi)
    fmt.Println(bf.Text('f',int(precision)))
}

这是我得到的输出

3.141592653589793238462643383279502884197169399375105820974944592307816406286208
99862803482534211706798214808651328230664709384460955058223172535940812848111745
02841027019385211055596446229489549303819644288109756659334461284756482337867831
65271201909145648566923460348610454326648213393607260249141273724587006606315588
1748815209209628292540917153644
349 decimal digits!
3.141592653589793238462643383279502884197169399375105820974944592307816406286208
99862803482534211706798214642892769097375992970630463040280967955002394205104670
93398509790876622872910336294129674153835313200604005620450374388405009045870250
42707837345817603660943583398075476645872182159499972077156507317945849377949230
0745914690196514129638671875000

更多关于Golang中如何解析big.Float并确保输出与输入完全一致的实战教程也可以访问 https://www.itying.com/category-94-b0.html

11 回复

请考虑所有数字,而不仅仅是小数点后的数字。

更多关于Golang中如何解析big.Float并确保输出与输入完全一致的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


那些额外的比特位是为了逗号前面的那个 3

x 的尾数精度(以位为单位)。

以10为基数的数字需要超过1位来表示。你是基于数字的位数来确定精度的。至少将其乘以三。

看起来乘以4时是一个精确匹配。Go Playground - The Go Programming Language

那么,给定小数点前和小数点后任意数量的数字,如何计算应传入 .SetPrec() 函数的正确位数呢?

一个十进制数字可以取0到9的值,所以有10个不同的值。

因此,你需要log2(10)位来表示一个十进制数字。

谢谢,我太懒了。使用 log2(10) 可以节省 236 位,仅供参考。

Go Playground - The Go Programming Language

Go 是一种开源编程语言,可以轻松构建简单、可靠且高效的软件。

我之前是这样做的:

precision := uint(len(pi[strings.Index(pi, ".")+1:]) * 4)

但这行不通,下面这个也不行:

precision := uint(len(pi[strings.Index(pi, ".")+1:])) * 4

然而这个却可以,这真是让人无语的愚蠢行为:

bf, _ = bf.SetPrec(precision * 4).SetMode(bf.Mode()).SetString(pi)

这是能给出正确结果的最小精度位数:

bf, _ = bf.SetPrec(1161).SetMode(bf.Mode()).SetString(pi)

1160 会将最后一位数字向上舍入,1159 会将最后一位数字向下舍入,1161 则刚刚好。

感谢所有的回复!

以下是一个完整的工作解决方案/示例,它考虑了整数和小数部分的位数。根据我有限的测试,它返回与输入值完全相同的字符串表示。

Go Playground - The Go Programming Language

pi := "3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679821480865132823066470938446095505822317253594081284811174502841027019385211055596446229489549303819644288109756659334461284756482337867831652712019091456485669234603486104543266482133936072602491412737245870066063155881748815209209628292540917153644"
fmt.Printf("%s\n", pi)
integerDigitCount := strings.Index(pi, ".")
fractionalDigitCount := len(pi) - (integerDigitCount + 1) // 从计数中排除小数点
fmt.Printf("integer digit count: %d\n", integerDigitCount)
fmt.Printf("factional digit count: %d\n", fractionalDigitCount)
bf := big.NewFloat(0.0)
precision := uint(math.Ceil(float64(fractionalDigitCount) * math.Log2(10.0)) + float64(integerDigitCount) * math.Log2(10.0))
fmt.Printf("calculated precision %d\n", precision)
bf, _ = bf.SetPrec(uint(precision)).SetString(pi)
ppi := bf.Text('f', fractionalDigitCount)
fmt.Printf("%s\n", ppi)

另外,根据文档和我的测试,bf.Text() 方法中 precision 参数为负值将生成表示小数部分所需的最少位数。

ppi := bf.Text('f', -1)

这将始终生成与输入完全相同的输出,而无需您存储小数位数以供后续使用。

精度乘以3远远不够,精度乘以7勉强能得到正确答案。

如你所见,我得到了原始字符串加上末尾一大堆舍入垃圾。

如何根据输出字符串中所需的位数来计算正确的 * 比特精度?

3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679821480865132823066470938446095505822317253594081284811174502841027019385211055596446229489549303819644288109756659334461284756482337867831652712019091456485669234603486104543266482133936072602491412737245870066063155881748815209209628292540917153644

3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679821480865132823066470938446095505822317253594081284811174502841027019385211055596446229489549303819644288109756659334461284756482337867831652712019091456485669234603486104543266482133936072602491412737245870066063155881748815209209628292540917153644000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002244342506374987052728132873357764604822964011725723498156139035608263529693321324611087760931638291534783804429504024063970113225287605729963719722383580878304141959206687069400914796848561621434478110671369148893967123772756235371122602256507537069295793796127829048985435018724653366850521683797027866210566225126917930722700540281778806181488530238605442879753445529001873898888929241864013642282373527291515685288403981807650734267848295316694745558591914884141327648624551152976025240472366982876042065424920222257500605683086490572532495407799867513435663181757387051418102492504661099733895817691933392065321975406088794616695914589059426846076572409593363011614759573979870328010001015131814985977529961056716665782401251298757553305676392622078175716128094105318036037245992472681494427947436144326364838683179041263870046413457172155738170850090737732421416031506418182930764950812479381058736513575328259855587826691707281198805818862277495355406830418592809361721477237559115847998907779919675848738643420198693152512349267893520195348481384845351896090256789774024980305634166361874489554298999467170401080881950315354127157986245696801549849750033397305982685708142528398925768672801947396154320512030099540703574095339226368627750879580770901088754012337545509255182543249838242985702416360011638303226834983986987743539557515819170488243938973917949619935857136645165558615729339233345028341636697904845161310408856339696647756621712434792748303616469785554055231767976392050919715124254881461507226059126555805330182014130173637310850569765132473800857139233811910170967773594655986084933999505845270291677052663693276509280026011803914534022466858304012404801142110954970121383666992187500

经过一些检查,似乎精度 == 2400 是我能得到正确输出加上末尾零和垃圾的最小数字。

要确保 big.Float 的解析和输出与输入字符串完全一致,关键在于正确设置精度和格式化参数。你的代码接近正确,但需要调整精度设置和解析方式。

问题在于 big.NewFloat(0.0) 的初始精度是53位(IEEE 754双精度),这不足以表示你的长数字。虽然你后来调用了 SetPrec(precision),但这里的 precision 是十进制位数,而 SetPrec 需要的是二进制位数。

以下是修正后的代码:

package main

import (
    "fmt"
    "math/big"
    "strings"
)

func main() {
    pi := "3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679821480865132823066470938446095505822317253594081284811174502841027019385211055596446229489549303819644288109756659334461284756482337867831652712019091456485669234603486104543266482133936072602491412737245870066063155881748815209209628292540917153644"
    
    // 解析big.Float
    f := new(big.Float)
    f.SetPrec(big.MaxPrec) // 使用最大精度
    f.SetMode(big.ToNearestEven)
    
    // 解析字符串
    _, success := f.SetString(pi)
    if !success {
        fmt.Println("解析失败")
        return
    }
    
    // 计算需要的十进制位数
    decimalDigits := len(pi) - strings.Index(pi, ".") - 1
    
    // 输出,确保与输入完全一致
    fmt.Println("输入字符串:")
    fmt.Println(pi)
    fmt.Printf("\n输出结果(%d位小数):\n", decimalDigits)
    fmt.Println(f.Text('f', decimalDigits))
    
    // 验证是否一致
    output := f.Text('f', decimalDigits)
    if pi == output {
        fmt.Println("\n✓ 输入输出完全一致")
    } else {
        fmt.Println("\n✗ 输入输出不一致")
    }
}

或者使用 big.ParseFloat 的版本:

package main

import (
    "fmt"
    "math/big"
    "strings"
)

func main() {
    pi := "3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679821480865132823066470938446095505822317253594081284811174502841027019385211055596446229489549303819644288109756659334461284756482337867831652712019091456485669234603486104543266482133936072602491412737245870066063155881748815209209628292540917153644"
    
    // 解析字符串
    f, i, err := big.ParseFloat(pi, 10, big.MaxPrec, big.ToNearestEven)
    if err != nil {
        fmt.Println("解析错误:", err)
        return
    }
    
    // i 是实际使用的基数(这里是10)
    fmt.Printf("解析成功,基数: %d\n", i)
    
    // 计算需要的十进制位数
    decimalDigits := len(pi) - strings.Index(pi, ".") - 1
    
    // 输出
    fmt.Println("\n输入字符串:")
    fmt.Println(pi)
    fmt.Printf("\n输出结果:\n")
    fmt.Println(f.Text('f', decimalDigits))
    
    // 验证
    output := f.Text('f', decimalDigits)
    if pi == output {
        fmt.Println("\n✓ 验证通过")
    }
}

关键点:

  1. 使用 big.MaxPrec 确保足够的二进制精度
  2. big.ParseFloat 的返回值 i 是实际使用的基数(对于十进制字符串就是10)
  3. Text('f', decimalDigits) 中的 decimalDigits 必须足够大以包含所有小数位
  4. 使用 big.ToNearestEven 作为舍入模式(这是默认模式)

对于你的具体例子,输出应该是:

输入字符串:
3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679821480865132823066470938446095505822317253594081284811174502841027019385211055596446229489549303819644288109756659334461284756482337867831652712019091456485669234603486104543266482133936072602491412737245870066063155881748815209209628292540917153644

输出结果:
3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679821480865132823066470938446095505822317253594081284811174502841027019385211055596446229489549303819644288109756659334461284756482337867831652712019091456485669234603486104543266482133936072602491412737245870066063155881748815209209628292540917153644

✓ 输入输出完全一致
回到顶部