Using the std lib, this solution provides three methods. The first two methods (TruncFloat and RoundFloat) meet the criteria:
- Supports very large floats
 
- Supports very small floats
 
- Supports invalid floats
 
- Truncates when you want it to
 
- Rounds when you want it to
 
The third method TruncFloatFast provides more speed, but is limited and provides an error for bad rounds. For most of my applications I prefer robustness over speed, but when speed matters I want to know about bad rounds. I did a quick perf measure on my dev machine.
TruncFLoat: 155 millis per million calls
RoundFloat: 150 millis per million calls
RoundFloatFast: 3 millis per million calls
// prec controls the number of digits (excluding the exponent)
//  prec of -1 uses the smallest number of digits
func TruncFloat(f float64, prec int) (float64, error) {
    floatBits := 64
    if math.IsNaN(f) || math.IsInf(f, 1) || math.IsInf(f, -1) {
        return 0, fmt.Errorf("bad float val %f", f)
    }
    fTruncStr := strconv.FormatFloat(f, 'f', prec+1, floatBits)
    fTruncStr = fTruncStr[:len(fTruncStr)-1]
    fTrunc, err := strconv.ParseFloat(fTruncStr, floatBits)
    if err != nil {
        return 0, err
    }
    return fTrunc, nil
}
// prec controls the number of digits (excluding the exponent)
//  prec of -1 uses the smallest number of digits
func RoundFloat(f float64, prec int) (float64, error) {
    floatBits := 64
    if math.IsNaN(f) || math.IsInf(f, 1) || math.IsInf(f, -1) {
        return 0, fmt.Errorf("bad float val %f", f)
    }
    fRoundedStr := strconv.FormatFloat(f, 'f', prec, floatBits)
    fRounded, err := strconv.ParseFloat(fRoundedStr, floatBits)
    if err != nil {
        return 0, err
    }
    return fRounded, nil
}
func RoundFloatFast(f float64, prec int) (float64, error) {
    mul := math.Pow10(prec)
    if mul == 0 {
        return 0, nil
    }
    product := f * mul
    var roundingErr error
    if product > float64(math.MaxInt64) {
        roundingErr = fmt.Errorf("unsafe round: float64=%+v, places=%d", f, prec)
    }
    return math.Round(product) / mul, roundingErr
}
Test Examples
    f, _ := TruncFloat(0.000000000000000000000000000000019, 32)
    expected := 0.00000000000000000000000000000001
    if f != expected {
        t.Errorf("Expected=%+v Actual=%+v", expected, f)
    }
    f, _ = TruncFloat(2.289, 2)
    expected = 2.28
    if f != expected {
        t.Errorf("Expected=%+v Actual=%+v", expected, f)
    }
    f, _ = TruncFloat(111222333444555666777888999000111222333.123456789, 8)
    expected = 111222333444555666777888999000111222333.12345678
    if f != expected {
        t.Errorf("Expected=%+v Actual=%+v", expected, f)
    }
    f, _ = TruncFloat(1, 0)
    expected = 1.
    if f != expected {
        t.Errorf("Expected=%+v Actual=%+v", expected, f)
    }
    f, _ = TruncFloat(1, 2)
    expected = 1.
    if f != expected {
        t.Errorf("Expected=%+v Actual=%+v", expected, f)
    }
    f, _ = RoundFloat(0.000000000000000000000000000000011, 32)
    expected = 0.00000000000000000000000000000001
    if f != expected {
        t.Errorf("Expected=%+v Actual=%+v", expected, f)
    }
    f, _ = RoundFloat(92234, 14)
    expected = 92234.
    if f != expected {
        t.Errorf("Expected=%+v Actual=%+v", expected, f)
    }
    f, _ = RoundFloat(2.289, 2)
    expected = 2.29
    if f != expected {
        t.Errorf("Expected=%+v Actual=%+v", expected, f)
    }
    f, _ = RoundFloat(111222333444555666777888999000111222333.123456789, 8)
    expected = 111222333444555666777888999000111222333.12345679
    if f != expected {
        t.Errorf("Expected=%+v Actual=%+v", expected, f)
    }
    a := time.Now().UnixMilli()
    for i := 0.; i < 1_000_000; i+= 1.23456789 {
        TruncFloat(i, 2)
    }
    t.Logf("TruncFloat millis=%d", time.Now().UnixMilli()-a)
    
    a = time.Now().UnixMilli()
    for i := 0.; i < 1_000_000; i+= 1.23456789 {
        RoundFloat(i, 2)
    }
    t.Logf("RoundFloat millis=%d", time.Now().UnixMilli()-a)
    
    a = time.Now().UnixMilli()
    for i := 0.; i < 1_000_000; i+= 1.23456789 {
        RoundFloatFast(i, 2)
    }
    t.Logf("RoundFloatFast millis=%d", time.Now().UnixMilli()-a)