Unmarshal JSON, чтобы отпустить. Почему требуется float64? - PullRequest
0 голосов
/ 26 октября 2019

Я заметил какое-то странное поведение с ходом unmarshals json float. Некоторые номера, но не все, отказываются правильно разбирать. Исправить это так же просто, как использовать float64 вместо float32 в переменной назначения, но я не могу найти вескую причину, почему это так.

Вот код, который демонстрируетпроблема:

package main

import (
    "encoding/json"
    "fmt"
    . "github.com/shopspring/decimal"
)

func main() {
    bytes, _ := json.Marshal(369.1368) // not every number is broken, but this one is
    fmt.Println("bytes", string(bytes))

    var f32 float32
    json.Unmarshal(bytes, &f32)
    fmt.Printf("f32 %f\n", f32) // adds an extra 0.00001 to the number

    var d Decimal
    json.Unmarshal(bytes, &d)
    fmt.Printf("d %s\n", d) // 3rd party packages work

    // naw, you can just float64
    var f64 float64
    json.Unmarshal(bytes, &f64)
    fmt.Printf("f64 %f\n", f64) // float64 works
}

float64 не требуется для точного представления номера моего примера, поэтому зачем здесь?

Перейти на игровую площадку: https://play.golang.org/p/tHkonQtZoCt

1 Ответ

4 голосов
/ 26 октября 2019

Ваше утверждение неверно: 369.1368 не может быть точно представлено либо float32 или float64.

Ближайшее значение float32(приблизительно) 369.136810302734375, округляя до 369.13681, откуда берется ваша дополнительная цифра. Ближайшее значение float64 (приблизительно) 369.13679999999999382, которое лучше подходит для ваших целей.

(Конечно, если вы округлите одно из этих значений до четырех цифр после десятичной точки, вы получитеожидаемое число.)

Представление Decimal является точным: ошибки округления нет.

JSON передает и получает значения с плавающей запятой, выраженные в десятичном виде, но фактические реализации , на разных языках, тогда кодируют эти числа по-разному. В зависимости от того, с какой сущностью вы разговариваете с помощью JSON, кодирование и декодирование с помощью Decimal может сохранить число в точности так, как вы хотите, но помните, что программы, написанные, например, на C ++ или Python, могут декодировать ваш номер вдругая точность с плавающей точкой и вводит различные ошибки округления.

Этот пример Go Playground использует недавно добавленный формат %x и показывает, как числа хранятся внутри:

как float32 = 369.13681030273437500 (float32), что в действительности составляет 12095875p-15 или 0x1.712306p + 08

и:

как float64 =369.13679999999999382 (float64), который на самом деле 6493923261440380p-44 или 0x1.712305532617cp + 08

То есть число 369. независимо от внутренне представлено в двоичном виде. Это между 2 8 = 256 и 2 9 = 512. В двоичном виде это 1 256, нет 128, 1 64, 1 32, 1 16, нет 8, нет 4,нет 2, а 1 1: 1.01110001 что-то x 2 8 . Формат %b выражает это в одном направлении, а формат %x - в другом, с %x, начинающимся с 1.72 (1. 0111 0010).

См. Не нарушена ли математика с плавающей запятой? (как jub0bs связаны в комментарии) для более.

...