Как я могу обрезать число float64 с определенной точностью? - PullRequest
0 голосов
/ 25 января 2019

Я хочу усечь 1.234567 в трехзначное число с плавающей запятой, но результат не тот, который я хочу.

Например: 1.234567 => 1.234

package main

import (
    "strconv"
    "fmt"
)

func main() {
    f := 1.234567
    fmt.Println(strconv.FormatFloat(f, 'f', 3, 64)) //1.235
    fmt.Printf("%.3f", f) //1.235
}

Может кто-нибудь сказать мне, как это сделать в Go?

Ответы [ 2 ]

0 голосов
/ 28 января 2019

Наивный способ (не всегда правильный)

Для усечения мы могли бы воспользоваться math.Trunc(), который выбрасывает цифры дроби.Это не совсем то, что мы хотим, мы хотим сохранить некоторые цифры дроби.Таким образом, чтобы достичь того, что мы хотим, мы можем сначала умножить входное значение на степень 10, чтобы сместить искомые дробные цифры в «целочисленную» часть, и после усечения (вызов math.Trunc(), который выбросит оставшиеся дробные цифры)мы можем разделить на ту же степень 10, которую мы умножили в начале:

f2 := math.Trunc(f*1000) / 1000

Оборачивая это в функцию:

func truncateNaive(f float64, unit float64) float64 {
    return math.Trunc(f/unit) * unit
}

Тестирование:

f := 1.234567
f2 := truncateNaive(f, 0.001)
fmt.Printf("%.10f\n", f2)

Вывод:

1.2340000000

Пока все хорошо, но обратите внимание, что мы выполняем арифметические операции внутри truncateNaive(), что может привести к нежелательным округлениям, которые могут изменить вывод функции.

Например, если входное значение равно 0.299999999999999988897769753748434595763683319091796875 (оно точно представлено значением float64, см. proof ), выходное значение должно быть 0.2999000000, но это будет что-то другое:

f = 0.299999999999999988897769753748434595763683319091796875
f2 = truncateNaive(f, 0.001)
fmt.Printf("%.10f\n", f2)

Вывод:

0.3000000000

Попробуйте это на Go Playground .

Этот неправильный вывод, вероятно, не приемлем в большинстве случаев (кроме случаев, когда выпосмотрите на это так, что ввод very близко к 0.3 - разница меньше 10 -16 - к которой вывод составляет 0.3 ...).

Использование big.Float

Чтобы правильно обрезать все действительные значения float64, промежуточные операции должны быть точными.Для этого недостаточно использовать float64.Есть способы разделить входные данные на значения 2 float64 и выполнить над ними операции (чтобы точность не терялась), что было бы более эффективным, или мы могли бы использовать более удобный способ, big.Float, которыйможет иметь произвольную точность.

Вот "расшифровка" вышеупомянутой функции truncateNaive() с использованием big.Float:

func truncate(f float64, unit float64) float64 {
    bf := big.NewFloat(0).SetPrec(1000).SetFloat64(f)
    bu := big.NewFloat(0).SetPrec(1000).SetFloat64(unit)

    bf.Quo(bf, bu)

    // Truncate:
    i := big.NewInt(0)
    bf.Int(i)
    bf.SetInt(i)

    f, _ = bf.Mul(bf, bu).Float64()
    return f
}

Тестирование:

f := 1.234567
f2 := truncate(f, 0.001)
fmt.Printf("%.10f\n", f2)

f = 0.299999999999999988897769753748434595763683319091796875
f2 = truncate(f, 0.001)
fmt.Printf("%.10f\n", f2)

Вывод теперь действителен (попробуйте на Go Playground ):

1.2340000000
0.2990000000
0 голосов
/ 27 января 2019

Вам нужно усечь десятичные дроби вручную, либо на уровне строки, либо с помощью math.Floor, например, https://play.golang.org/p/UP2gFx2iFru.

...