Подходящий масштаб для преобразования через BigDecimal в число с плавающей запятой - PullRequest
2 голосов
/ 08 октября 2019

Я написал класс рациональных чисел произвольной точности, который должен обеспечить способ преобразования в число с плавающей точкой. Это можно сделать напрямую через BigDecimal:

return new BigDecimal(num).divide(new BigDecimal(den), 17, RoundingMode.HALF_EVEN).doubleValue();

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

Какое будет правильное число, определенное как,наименьшее число, такое, что увеличение его значения не сделает ответ более точным?

1 Ответ

2 голосов
/ 11 октября 2019

Введение

Нет достаточной конечной точности.

Задача, поставленная в вопросе, эквивалентна:

  • Какая точность p гарантируетчто преобразование любого рационального числа x в p десятичных цифр и затем в число с плавающей точкой дает число с плавающей точкой, ближайшее x (или, в случае связилюбой из двух ближайших x )?

Чтобы увидеть, что это эквивалентно, обратите внимание, что деление BigDecimal, показанное в вопросе, возвращает num / div ввыбранное количество знаков после запятой. Затем возникает вопрос, может ли увеличение этого количества десятичных разрядов повысить точность результата. Ясно, что если число с плавающей запятой ближе x , чем результат, точность может быть улучшена. Таким образом, мы спрашиваем, сколько десятичных знаков необходимо, чтобы гарантировать получение ближайшего числа с плавающей запятой (или одного из двух связанных).

Так как BigDecimal предлагает выбор методов округления, я рассмотрюдостаточно ли одного из них. Я предполагаю, что для преобразования в число с плавающей запятой используется округление до ближайшего числа связей (даже при использовании BigDecimal при преобразовании в Double или Float). Я даю доказательство, используя формат бинарного кода IEEE-754, который Java использует для Double, но это доказательство применимо к любому двоичному формату с плавающей запятой путем изменения значения 2 52 , используемого ниже, на 2 w -1 , где w - количество бит в значении.

Доказательство

Один из параметров для BigDecimal деление - метод округления. Java BigDecimal имеет несколько методов округления . Нам нужно только рассмотреть три, ROUND_UP, ROUND_HALF_UP и ROUND_HALF_EVEN. Аргументы для остальных аналогичны приведенным ниже с использованием различных симметрий.

В дальнейшем предположим, что мы преобразуем в десятичную форму, используя любую большую точность p . То есть p - это число десятичных цифр в результате преобразования.

Пусть m - рациональное число 2 52 +1 + ½-10 р . Два соседних двоичных числа 64 m равны 2 52 + 1 и 2 52 + 2. m ближе к первому, так что это результат, который нам требуется от преобразования m сначала в десятичную, а затем в плавающую точку.

В десятичной, m - это 4503599627370497.4999…, где есть p −1 с последующими 9 с. При округлении до p значащих цифр с ROUND_UP, ROUND_HALF_UP или ROUND_HALF_EVEN, результат будет 4503599627370497.5 = 2 52 + 1 + ½. (Признайте, что в позиции, где происходит округление, отбрасывается 16 конечных 9, что фактически составляет долю .9999999999999999 относительно позиции округления. В ROUND_UP любая ненулевая отклоненная сумма вызывает округление. В ROUND_HALF_UP и ROUND_HALF_EVEN, aвыброшенная сумма больше ½ в этой позиции вызывает округление в большую сторону.)

2 52 + 1 + ½ одинаково близко к соседним двоичным числам64 2 52 + 1 и2 52 + 2, поэтому метод округления до ближайших связей к четным дает 2 52 + 2.

Таким образом, результат равен 2 52 + 2, что не является двоичным значением64, ближайшим к m .

Следовательно, конечной точности p не достаточно для правильного округления всех рациональных чисел.

...