Из публичного аккаунта:Гофер указывает на север
钱,乃亘古之玄物,有则气粗神壮,缺则心卑力浅
В системе, особенно в системе, связанной с деньгами, деньги являются главным приоритетом, и точность расчета будет предметом этой статьи.
Почему важна точность
«Плюм и тонущая лодка» здесь больше всего подходит для использования. Если платформа электронной коммерции имеет годовой оборот заказов в 1 миллиард, и каждый заказ оплачивается на 1 цент меньше, совокупный убыток составит1000万
! Одним словом, эти потерянные деньги составляют одну десятую от маленькой цели Ванга. Если вы рассчитываетесь с клиентами из-за проблем с точностью, занижение счета приведет к потере клиентов, а завышение приведет к потере денег. Это показывает, что точный расчет денег очень важен!
Почему возникают проблемы с точностью
Классический случай, давайте посмотрим0.1 + 0.2
Равно ли это в компьютере0.3
.
Тем, кто изучал компьютеры в приведенных выше случаях, следует знать, что компьютеры являются двоичными.При использовании двоичного представления чисел с плавающей запятой (стандарт IEEE754) этим методом можно точно представить лишь небольшое количество чисел. Возьмем 0,3 в качестве примера, чтобы увидеть процесс преобразования десятичной системы в двоичную.
Компьютеры ограничены в количестве цифр, поэтому компьютеры не могут получить точные результаты при вычислениях с числами с плавающей запятой. Это жесткое ограничение не может быть нарушено, поэтому необходимо ввести точность, чтобы расчет денег был максимально точным в пределах допустимого диапазона ошибок.
Фактическое представление чисел с плавающей точкой на компьютерах не будет обсуждаться дальше в этой статье. Вы можете ссылаться на следующие соединения для обучения:
Представление с плавающей точкой одноточного сплава:
En. Wikipedia.org/wiki/single…
Представление двойной точности с плавающей запятой:
Итак, Wikipedia.org/wiki/double…
Преобразователь с плавающей запятой:
Расчет с числами с плавающей запятой
или выше0.1 + 0.2
Например,0.00000000000000004
Ошибка совершенно незначительна, мы стараемся сохранить 5 знаков точности для дробной части, см. результаты ниже.
Результаты на данный момент, как и ожидалось. Вот почему его часто используют, чтобы определить, равны ли два числа с плавающей запятой.a - b <= 0.00001
Форма, грубо говоря, это еще одна форма представления, в которой десятичная часть сохраняет 5-значную точность.
Вычислять с целыми числами
Как упоминалось ранее, только небольшое количество плавающих чисел могут быть представлены стандартом IEEE754, в то время как целые числа могут точно представлять номера во всех действительных диапазонах. Поэтому легко подумать об использовании целых чисел, чтобы представлять числа плавающих точек.
Например, если десятичная дробь определена заранее, чтобы сохранить 8-значную точность, то0.1
и0.2
Представлены целыми числами10000000
и20000000
, операции над числами с плавающей запятой преобразуются в операции над целыми числами. или с0.1 + 0.2
Например.
// 表示小数位保留8位精度
const prec = 100000000
func float2Int(f float64) int64 {
return int64(f * prec)
}
func int2float(i int64) float64 {
return float64(i) / prec
}
func main() {
var a, b float64 = 0.1, 0.2
f := float2Int(a) + float2Int(b)
fmt.Println(a+b, f, int2float(f))
return
}
Результаты вывода кода следующие:
Приведенный выше вывод в точности соответствует ожидаемому, поэтому использование целых чисел для представления чисел с плавающей запятой кажется жизнеспособным решением. Однако мы не можем ограничиться одним случаем, и необходимы дополнительные тесты.
fmt.Println(float2Int(2.3))
Вывод приведенного выше кода выглядит следующим образом:
Такой результат столь неожиданный, но вполне закономерный.
На картинке выше показано2.3
чтобы фактически сохранить значение на компьютере, поэтому используйтеfloat2Int
Результат, когда функция выполняет преобразование,229999999
вместо230000000
.
Этот результат явно не соответствует ожиданиям, и все еще есть потеря точности в пределах определенного диапазона точности.Если этот код будет отправлен онлайн, есть большая вероятность, что вы уедете со скоростью света на следующий день. Решить эту задачу тоже очень просто, достаточно ввестиgithub.com/shopspring/decimal
Просто посмотрите на исправленный код ниже.
// 表示小数位保留8位精度
const prec = 100000000
var decimalPrec = decimal.NewFromFloat(prec)
func float2Int(f float64) int64 {
return decimal.NewFromFloat(f).Mul(decimalPrec).IntPart()
}
func main() {
fmt.Println(float2Int(2.3)) // 输出:230000000
}
В настоящее время результат соответствует ожидаемому, и операции с плавающей запятой (сложение, вычитание и умножение) внутри системы могут быть преобразованы в операции с целыми числами, а результат операции требует только одного преобразования с плавающей запятой.
Пока что использование целочисленных вычислений в основном может удовлетворить большинство сценариев, но есть еще две проблемы, на которые необходимо обратить внимание.
1. Целое число указывает, соответствует ли диапазон чисел с плавающей точкой.
2, когда оно представлено целым числом, деление с плавающей запятой по-прежнему необходимо преобразовать в операции с плавающей запятой.
Целое число представляет диапазон чисел с плавающей запятой
отint64
Например, диапазон значений-9223372036854775808~9223372036854775807
, если мы зарезервируем 8 бит для точности дробной части, оставшаяся целая часть по-прежнему имеет 11 бит, то есть, если она представляет только деньги, ее все еще можно хранить на百亿
Этого значения более чем достаточно для многих систем и малых и средних компаний, но диапазон по-прежнему является проблемой, требующей тщательного рассмотрения при использовании этого метода для хранения суммы.
Он представляет целочисленное деление с плавающей запятой
В Go нет неявного преобразования целого числа в число с плавающей запятой, то есть результат деления целого числа на целое число по-прежнему остается целым числом. Когда мы представляем числа с плавающей запятой в виде целых чисел, нам нужно обратить особое внимание на тот факт, что при делении целых чисел будут потеряны все дробные части, поэтому мы должны преобразовать числа с плавающей запятой перед делением.
Максимальная точность для типов с плавающей запятой и целых чисел
int64
Диапазон-9223372036854775808~9223372036854775807
, то когда целое число используется для представления типа с плавающей запятой, значащие десятичные цифры целой части и дробной части не более19
немного.
uint64
Диапазон0~18446744073709551615
, то когда целое число используется для представления типа с плавающей запятой, значащие десятичные цифры целой части и дробной части не более20
Бит, потому что система не хранит отрицательные числа, обозначающие общее количество времени, и т.д.int64
Более рекомендуется использоватьuint64
.
float64
Согласно стандарту IEEE754 и со ссылкой на Википедию, значащие десятичные цифры целой части и дробной части равны15-17
немного.
Давайте посмотрим на пример ниже.
var (
a float64 = 123456789012345.678
b float64 = 1.23456789012345678
)
fmt.Println(a, b, decimal.NewFromFloat(a), a == 123456789012345.67)
return
Вывод приведенного выше кода выглядит следующим образом:
Известный выход,float64
Десятичное число с более чем 17 значащими цифрами не может быть представлено. Что касается эффективных десятичных цифр, Лао Сюй рекомендует использовать целые числа для представления чисел с плавающей запятой.
Постарайтесь сохранить как можно большую точность в расчетах.
О важности точности и максимальной точности, которая может быть представлена целыми числами и типами с плавающей запятой, упоминалось ранее.Давайте возьмем практический пример, чтобы обсудить, следует ли сохранять указанную точность в процессе вычислений.
var (
// 广告平台总共收入7.11美元
fee float64 = 7.1100
// 以下是不同渠道带来的点击数
clkDetails = []int64{220, 127, 172, 1, 17, 1039, 1596, 200, 236, 151, 91, 87, 378, 289, 2, 14, 4, 439, 1, 2373, 90}
totalClk int64
)
// 计算所有渠道带来的总点击数
for _, c := range clkDetails {
totalClk += c
}
var (
floatTotal float64
// 以浮点数计算每次点击的收益
floatCPC float64 = fee / float64(totalClk)
intTotal int64
// 以8位精度的整形计算每次点击的收益(每次点击收益转为整形)
intCPC int64 = float2Int(fee / float64(totalClk))
intFloatTotal float64
// 以8位进度的整形计算每次点击的收益(每次点击收益保留为浮点型)
intFloatCPC float64 = float64(float2Int(fee)) / float64(totalClk)
decimalTotal = decimal.Zero
// 以decimal计算每次点击收益
decimalCPC = decimal.NewFromFloat(fee).Div(decimal.NewFromInt(totalClk))
)
// 计算各渠道点击收益,并累加
for _, c := range clkDetails {
floatTotal += floatCPC * float64(c)
intTotal += intCPC * c
intFloatTotal += intFloatCPC * float64(c)
decimalTotal = decimalTotal.Add(decimalCPC.Mul(decimal.NewFromInt(c)))
}
// 累加结果对比
fmt.Println(floatTotal) // 7.11
fmt.Println(intTotal) // 710992893
fmt.Println(decimal.NewFromFloat(intFloatTotal).IntPart()) // 711000000
fmt.Println(decimalTotal.InexactFloat64()) // 7.1100000000002375
По сравнению с приведенными выше результатами расчетов только второй тип имеет наименьшую точность, и основной причиной потери этой точности являетсяfloat2Int(fee / float64(totalClk))
Точность промежуточных результатов расчета сохраняется только8
Bit, поэтому выдает ошибку в результате. Другие методы расчета максимально высоки при промежуточном расчете, поэтому результаты соответствуют ожиданиям.
Сочетание деления и вычитания
Согласно предыдущему описанию, используйте числа с плавающей запятой и сохраняйте максимальную точность при вычислении деления. Это еще не решает всех проблем, давайте рассмотрим следующий пример.
// 1元钱分给3个人,每个人分多少?
var m float64 = float64(1) / 3
fmt.Println(m, m+m+m)
Вывод приведенного выше кода выглядит следующим образом:
По результатам расчета каждый человек получает0.3333333333333333
юаней, и когда деньги, разделяемые каждым человеком, снова объединяются, они становятся1
юаней, тогда
Этот0.0000000000000001
Юань выпрыгнул из камня! Иногда я действительно не понимаю эти компьютеры.
Этот результат явно не соответствует человеческой интуиции.Чтобы быть более интуитивным, мы объединили вычитание, чтобы завершить это вычисление.
// 1元钱分给3个人,每个人分多少?
var m float64 = float64(1) / 3
fmt.Println(m, m+m+m)
// 最后一人分得的钱使用减法
m3 := 1 - m - m
fmt.Println(m3, m+m+m3)
Вывод приведенного выше кода выглядит следующим образом:
Путем вычитания мы наконец нашли потерянное0.0000000000000001
Юань. Конечно, это всего лишь пример, приведенный Лао Сюй.В реальном процессе расчета может потребоваться пройтиdecimal
Библиотека выполняет вычитание, чтобы деньги не исчезали и не увеличивались из воздуха.
Все вышеизложенное является поверхностным мнением о Лао Сюй.Если у вас есть какие-либо сомнения и ошибки, пожалуйста, вовремя укажите на них.Я искренне надеюсь, что эта статья может быть полезна всем читателям.
Примечание:
При написании этой статьи я использовал версию go: go1.16.6.
Несколько примеров, использованных в статье:GitHub.com/я сайты/перейти - от…