Как Double.toString () работает, если число дроби не может быть точно представлено в двоичном виде? - PullRequest
4 голосов
/ 05 июля 2019

Я не могу понять, как Double.toString () работает в Java / JVM.Я понимаю, что в общем случае дробные числа не могут быть точно представлены в типах с плавающей запятой, таких как Double и Float.Например, двоичное представление 206,64 будет 206,6399999999999863575794734060764312744140625.Тогда почему (206.64) .toString () возвращает «206.64» вместо «206.6399999999999863575794734060764312744140625»?

Тестовый код в Kotlin.

@Test
fun testBigDecimalToString() {
    val value = 206.64
    val expected = "206.64"

    val bigDecimal = BigDecimal(value)

    assertEquals(expected, value.toString()) // success
    assertEquals(expected, bigDecimal.toString()) // failed. Actual: 206.6399999999999863575794734060764312744140625
}

Ответы [ 2 ]

7 голосов
/ 05 июля 2019

Количество цифр, которое вы видите при печати float или double, является следствием правил Java для преобразования по умолчанию float и double в десятичное число.

Форматирование Java по умолчаниюдля чисел с плавающей запятой используется наименьшее количество значащих десятичных цифр, необходимое для различения числа от ближайших представляемых чисел. 1

В вашем примере 206.64 в исходном тексте преобразуется в double значение 206.6399999999999863575794734060764312744140625, поскольку из всех значений, представленных в типе double, это значение наиболее близко к 206.64.Следующие более низкие и следующие более высокие значения - 206,639999999999957935870043002068996429443359375 и 206,640000000000014779288903810083866119384765625.

* * * * * * * * * * * * * * * * * * * * * * * * * * * * это 101699099 ''.206.639999999999957935870043002068996429443359375 и 206.640000000000014779288903810083866119384765625.Обратите внимание, что начиная с конца 9 в 206.63999…, это первое значение отличается от 206.64 на .1364…, тогда как третье значение, 206.64000…, отличается на .1477….Так что, когда Java печатает «206.64», это означает, что значение из double печатаемого ближайшего представимо значения, и это значение 206,6399999999999863575794734060764312744140625, а не дальше 206,640000000000014779288903810083866119384765625 значения.

1019 * Сноска 1021 * 1 Правило Java SE 10 можно найти в документации по java.lang.float, в разделе toString(float d).Документация double аналогична.Отрывок с наиболее важной частью, выделенной жирным шрифтом, выглядит следующим образом:

Возвращает строковое представление float argument.Все упомянутые ниже символы являются символами ASCII.

  • Если аргумент равен NaN, результатом является строка "NaN".

  • В противном случаеРезультатом является строка, которая представляет знак и величину (абсолютное значение) аргумента.Если знак отрицательный, первым символом результата будет '-' ('\u002D');если знак положительный, знак не появляется в результате.Что касается величины м :

    • Если м - бесконечность, то она представлена ​​символами «Бесконечность»;таким образом, положительная бесконечность дает результат «Бесконечность», а отрицательная бесконечность - «-Infinity».

    • Если m равно нулю, то оно представлено символами"0.0";таким образом, отрицательный ноль дает результат «-0.0», а положительный ноль дает результат «0.0».

    • Если m больше или равно 10 -3 , но менее 10 7 , тогда он представляется как целая часть m , в десятичной форме без начальных нулей, за которыми следует '.'('\u002E'), за которым следуют одна или несколько десятичных цифр, представляющих дробную часть m .

    • Если m меньше 10 -3 или больше или равно 10 7 , тогда оно представлено в так называемой «компьютеризированной научной нотации».Пусть n будет уникальным целым числом, таким что 10 n m <10 <sup> n + 1;тогда пусть a будет математически точным отношением m и 10 n , так что 1 ≤ a <10Затем величина представляется как целочисленная часть <em>a , как одна десятичная цифра, за которой следует '.' ('\u002E'), за которой следуют десятичные цифры, представляющие дробную часть a , за которым следует буква 'E' ('\u0045'), за которой следует представление n в виде десятичного целого числа, полученного методом Integer.toString(int).

Сколько цифрдолжны быть напечатаны для дробной части м или a ? Должна быть хотя бы одна цифра для представления дробной части, и помимо этого столько, но только столько, сколько цифр необходимо, чтобы однозначно отличить значение аргумента от смежных значений типа float. То есть предположим, что x является точным математическим значением, представленным десятичным представлением, полученным этим методом для конечного ненулевого аргумента f .Тогда f должно быть значением float, ближайшим к x ;или, если два значения float одинаково близки к x , тогда f должно быть одним из них, а младший значащий бит из значения f долженбыть 0.

0 голосов
/ 05 июля 2019

Я несколько новичок, поэтому я надеюсь, что кто-то с большим опытом может ответить более подробно, но вот что я предполагаю, причина ...

Форматирование

Хотя это для .NET Framework, а не только для Java, я представляю, что они работают аналогично: метод toString использует необязательный ввод форматера , и, скорее всего, Java использует что-то похожее, форматируя double в близкое приближение в методе toString. Учитывая, что Oracle специально заявляет, что toString должен быть кратким и простым для чтения , вероятно, такой метод реализован для Double.toString ().

Только необходимые цифры для различения ...

Это примерно столько документации , сколько я смог найти по специфике метода Double.toString () - обратите внимание на последний абзац:

Сколько цифр должно быть напечатано для дробной части m или a? Должна быть хотя бы одна цифра для представления дробной части, и, кроме того, столько, сколько нужно, , но только столько цифр, сколько необходимо, чтобы однозначно отличить значение аргумента от смежных значений типа double . То есть предположим, что x - это точное математическое значение, представленное десятичным представлением, полученным этим методом для конечного ненулевого аргумента d. Тогда d должно быть двойным значением, ближайшим к x; или если два двойных значения одинаково близки к x, то d должно быть одним из них, а младший значащий бит значенияи d должен быть 0.

Мне любопытно, что это значит под "смежными значениями типа double" (другие переменные?), Но, похоже, он также согласуется с приведенным выше - toString, а другие методы, вероятно, используют только как можно меньше цифр, чтобы однозначно идентифицировать удваивается, округляя, когда число достаточно близко, как в случае 23.675999999999, «достаточно близко» к 23.676. Или я мог бы неправильно понять документацию.

...