IEEE 754 двоичные числа с плавающей запятой неточные для денег - PullRequest
0 голосов
/ 27 сентября 2019

У меня проблема, когда я использую math.Floor с переменной с плавающей запятой (округление вниз / усечение прецизионной части).Как я могу сделать это правильно?

package main

import (
    "fmt"
    "math"
)

func main() {
    var st float64 = 1980
    var salePrice1 = st * 0.1 / 1.1
    fmt.Printf("%T:%v\n", salePrice1, salePrice1) // 179.9999
    var salePrice2 = math.Floor(st * 0.1 / 1.1)
    fmt.Printf("%T:%v\n", salePrice2, salePrice2) // 179
}

Детская площадка: https://play.golang.org/p/49TjJwwEdEJ

Выход:

float64:179.99999999999997
float64:179

Я ожидаю, что 1980 * 0.1 / 1.1 будет 180, но фактический результат равен 179.

Ответы [ 2 ]

3 голосов
/ 27 сентября 2019

Исходный вопрос:


Неправильный номер этажа в golang

У меня проблема при использовании Math.Floor с переменной float (округлить вниз / усечь прецизионную часть),Как я могу сделать это правильно?

package main

import (
    "fmt"
    "math"
)

func main() {
    var st float64 = 1980
    var salePrice1 = st * 0.1 / 1.1
    fmt.Printf("%T:%v\n", salePrice1, salePrice1)
    var salePrice2 = math.Floor(st * 0.1 / 1.1)
    fmt.Printf("%T:%v\n", salePrice2, salePrice2)
}

Я ожидаю, что выход 1980 * 0,1 / 1,1 будет 180, но фактический выход составляет 179 ".

Детская площадка:

Вывод:

float64:179.99999999999997
float64:179

Проблема XY заключается в том, что вы пытаетесь решить проблему, а не в своей реальной проблеме: Проблема XY .


Понятно, что это расчет денег за salePrice1.Денежные вычисления используют точные десятичные вычисления, а не неточные двоичные вычисления с плавающей точкой.

Для денежных расчетов используйте целые числа.Например,

package main

import "fmt"

func main() {
    var st int64 = 198000 // $1980.00 as cents

    fmt.Printf("%[1]T:%[1]v\n", st)
    fmt.Printf("$%d.%02d\n", st/100, st%100)

    var n, d int64 = 1, 11
    fmt.Printf("%d, %d\n", n, d)

    var salePrice1 int64 = (st * n) / d // round down

    fmt.Printf("%[1]T:%[1]v\n", salePrice1)
    fmt.Printf("$%d.%02d\n", salePrice1/100, salePrice1%100)

    var salePrice2 int64 = ((st*n)*10/d + 5) / 10 // round half up

    fmt.Printf("%[1]T:%[1]v\n", salePrice2)
    fmt.Printf("$%d.%02d\n", salePrice2/100, salePrice2%100)

    var salePrice3 int64 = (st*n + (d - 1)) / d // round up

    fmt.Printf("%[1]T:%[1]v\n", salePrice1)
    fmt.Printf("$%d.%02d\n", salePrice3/100, salePrice3%100)
}

Детская площадка: https://play.golang.org/p/HbqVJUXXR-N

Вывод:

int64:198000
$1980.00
1, 11
int64:18000
$180.00
int64:18000
$180.00
int64:18000
$180.00

Ссылки:

What EveryУченый-компьютерщик должен знать об арифметике с плавающей точкой

Как рассчитывать деньги (десятичные, большие. Плавать)

Общая десятичная арифметика

2 голосов
/ 27 сентября 2019

Попробуйте это :

    st := 1980.0
    f := 0.1 / 1.1
    salePrice1 := st * f
    salePrice2 := math.Floor(salePrice1)
    fmt.Println(salePrice2) // 180

Это большая тема:
Для учетных систем : ответ Плавающийуменьшение точечной ошибки .

(Примечание: один из методов смягчения заключается в использовании int64, uint64 или big.Int)

И см .:
Что должен знать каждый компьютерщик об арифметике с плавающей точкой https://en.wikipedia.org/wiki/Double-precision_floating-point_format https://en.wikipedia.org/wiki/IEEE_floating_point


Давайте начнем с:

fmt.Println(1.0 / 3.0) // 0.3333333333333333

IEEE 754 двоичного представления:

fmt.Printf("%#X\n", math.Float64bits(1.0/3.0)) // 0X3FD5555555555555

Двоичное представление IEEE 754 для 1.1:

fmt.Printf("%#X\n", math.Float64bits(1.1))        // 0X3FF199999999999A
fmt.Printf("%#X\n", math.Float64bits(st*0.1/1.1)) // 0X40667FFFFFFFFFFF

Теперь, давайте:

st := 1980.0
f := 0.1 / 1.1

Двоичное представление IEEE 754 для f:

fmt.Printf("%#X\n", math.Float64bits(f)) // 0X3FB745D1745D1746

И:

salePrice1 := st * f
fmt.Println(salePrice1) // 180
fmt.Printf("%#X\n", math.Float64bits(salePrice1)) // 0X4066800000000000
salePrice2 := math.Floor(salePrice1)
fmt.Printf("%#X\n", math.Float64bits(salePrice2)) // 0X4066800000000000

Работа с числами с плавающей точкой на компьютере не такая же, как с ручкой и бумагой ( Ошибки вычисления с плавающей точкой ):

    var st float64 = 1980
    var salePrice1 = st * 0.1 / 1.1
    fmt.Println(salePrice1) // 179.99999999999997

salePrice1 равно 179,999999999999997, а не 180,0, поэтому целое значение, меньшее или равное 179,999999999999997, равно 179:

См. Документы для func Floor(x float64) float64:

Floor возвращает наибольшее целое значение, меньшее или равное x.

См .:

    fmt.Println(math.Floor(179.999))       // 179
    fmt.Println(math.Floor(179.5 + 0.5))   // 180
    fmt.Println(math.Floor(179.999 + 0.5)) // 180
    fmt.Println(math.Floor(180.0))         // 180

Некоторые соответствующие QAs:

Golang точность с плавающей точкой float32 против float64

Как правильно изменить число с плавающей точкой на uint64?

Golangпреобразование float64 в int ошибка

Не работает ли математика с плавающей запятой?

Сравнение с плавающей запятой

Что делает "% b" в fmt.Printf для float64 и что такое Min субнормальный положительный двойной в float64 в двоичном формате?

Есть ли стандартная библиотека для преобразования float64 в строку с исправлениемширина с максимальным количеством значащих цифр?

fmt.Printf с полями ширины и точности в% g ведет себя неожиданно

Почему есть разницамежду умножением с плавающей запятой с литералами и переменными в Go?

Golang Round to Nearesт 0,05

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...