Search code examples
goarbitrary-precisiondigit-separator

Go/Golang: how to extract least significant digits from big.Float?


In Go/Golang I have a variable of type big.Float with an (arbitrary) precision of 3,324,000 to represent a decimal number of 1,000,000 digits. It's the result of an iteration to calculate pi. Now I want to print out the least significant 100 digits, i.e. digits 999,900 to 1,000,000.

I tried to convert the variable to a string by using fmt.Sprintf() and big.Text(). However, both functions consume a lot of processing time which gets unacceptable (many hours and even days) when further raising the precision.

I'm searching for some functions which extract the last 100 (decimal) digits of the variable. Thanks in advance for your kind support.


Solution

  • The standard library doesn't provide a function to return those digits efficiently, but you can calculate them.

    It is more efficient to isolate the digits you are interested in and print them. This avoids excessive calculations of an extremely large number to determine each individual digit.

    The code below shows a way it can be done. You will need to ensure you have enough precision to generate them accurately.

    package main
    
    import (
        "fmt"
        "math"
        "math/big"
    )
    
    func main() {
        // Replace with larger calculation.
        pi := big.NewFloat(math.Pi)
    
        const (
            // Pi: 3.1415926535897932...
            // Output: 5926535897
            digitOffset = 3
            digitLength = 10
        )
    
        // Move the desired digits to the right side of the decimal point.
        mult := pow(10, digitOffset)
        digits := new(big.Float).Mul(pi, mult)
    
        // Remove the integer component.
        digits.Sub(digits, trunc(digits))
    
        // Move the digits to the left of the decimal point, and truncate
        // to an integer representing the desired digits.
        // This avoids undesirable rounding if you simply print the N
        // digits after the decimal point.
        mult = pow(10, digitLength)
        digits.Mul(digits, mult)
        digits = trunc(digits)
    
        // Display the next 'digitLength' digits. Zero padded.
        fmt.Printf("%0*.0f\n", digitLength, digits)
    }
    
    // trunc returns the integer component.
    func trunc(n *big.Float) *big.Float {
        intPart, accuracy := n.Int(nil)
        _ = accuracy
        return new(big.Float).SetInt(intPart)
    }
    
    // pow calculates n^idx.
    func pow(n, idx int64) *big.Float {
        if idx < 0 {
            panic("invalid negative exponent")
        }
        result := new(big.Int).Exp(big.NewInt(n), big.NewInt(idx), nil)
        return new(big.Float).SetInt(result)
    }