В какой-то момент вызывает ли проблема добавление большого целого числа к длинному целому значению значительно ниже его предела в Java? - PullRequest
1 голос
/ 08 февраля 2020

Например, добавив 4.0E16 или * 4 * Math.pow (10, 16) * к:

5,390,195,186,705,543

результат должен быть:

45,390,195,186,705,543

, но следующий код возвращает

45,390,195,186,705,544

, добавив 1 к наименее значимое di git (что не должно происходить).

Из того, что я исследовал в Интернете, верхний предел long:

9,223,372,036,854,775,807

но пример, который я привел, еще не превысил этот предел.

Соответствующий код:

int [] digits = new digits[] {6,1,4,5,3,9,0,1,9,5,1,8,6,7,0,5,5,4,3};
long sum = new Long(0);
int k = digits.length-1;

for(int j = digits.length-1; j>=0; j--)
{

    sum = (long) (sum +  (long)digits[j]*Math.pow(10, k-j));
}

Ответы [ 3 ]

2 голосов
/ 08 февраля 2020

Функция Math.pow работает только для значений double. Здесь вы используете целые числа, но они тихо преобразуются в двойные, потому что это единственный доступный метод, а язык java spe c допускает тихие расширяющие преобразования.

Проблема в том, что «расширение» Лонг в двойке не совсем без потерь. 52 из 64 битов, доступных двойному типу, относятся к самому числу, остальное - к «величине» (и я здесь слишком упрощенно упрощаю), в то время как в целом все 63 доступных бита предназначены для Сам номер, без битов по величине. Это означает, что double может представлять большие (и меньшие (дробные)) числа, но не может представлять каждое число в пределах диапазона, который он может представлять.

Вот почему вы наблюдаете эти «ошибки». Это просто, как удваивается работа. Подробнее о том, как удваивается работа, см. 0.30000000000000004.com .

1 голос
/ 08 февраля 2020

Следующие несколько строк демонстрируют, что пошло не так:

double a=  Math.pow(10, 16)*  4.0;
long   b = (long) a;
long   c = 5390195186705543L + b;

long   d = (long) (5390195186705543L + 4L * Math.pow(10, 16));
long   e = 5390195186705543L + 4L * (long) Math.pow(10, 16);

System.out.println("a="+a+" b="+b+" c="+c+" d = "+d+" e="+e);

Выходы:

a=4.0E16 b=40000000000000000 c=45390195186705543 
d = 45390195186705544 e=45390195186705543

a , b и c имеют все ожидаемое значение, поэтому вы видите, что добавление длинных целых чисел работает как положено.

В случае d мы имеем неверный результат, потому что Math.pow(10, 16) возвращает значение типа double, поэтому все выражение оценивается как значение типа double, а затем, наконец, преобразуется в длинное целое число. Поэтому мы добавляем здесь двойные значения, а не целые числа. Double имеет ограниченную точность, что приводит к немного неправильному результату.

Пример e дает правильный результат, потому что мы сначала конвертируем double в long перед добавлением его к другим длинным целым значениям , В этом случае мы не теряем точность, потому что 40000000000000000 может быть точно сохранен в удвоении. Но будьте осторожны, это не так хорошо работает со всеми возможными числами.

Помните, что типы данных с плавающей запятой не являются точными на 100%. Поэтому хорошее правило состоит в том, что человек никогда не должен сравнивать два числа с плавающей запятой или двойные значения на равенство. Например, более сложные выражения, чем 1.0/3.0 + 1.0/3.0 + 1.0/3.0 == 1.0, могут потерпеть неудачу.

Если вам нужны предсказуемые результаты с определенным количеством цифр, лучше использовать BigDecimal или BigInteger.

0 голосов
/ 08 февраля 2020

Math.pow () создает значение типа double . Формат double определяется стандартом IEEE 754, см. Java спецификация языка .

В соответствии с этим стандартом до 52 бит используются для хранения значащих бит , Это означает, что можно представлять числа с не более 15 десятичных цифр без потерь. Также некоторые числа могут содержать до 17 десятичных цифр, но между ними есть «разрывы», в которых числа представлены неправильно.

В вашем случае точными являются только первые 15 десятичных цифр , Другие цифры могут быть или не быть правильными .

Вот пример, который показывает, что:

public class DoublePrecisionDemo {

    public static void main(String[] args) {
        System.out.printf("45390195186705540   --> %20.0f\n", 45390195186705540d);
        System.out.printf("45390195186705541   --> %20.0f\n", 45390195186705541d);
        System.out.printf("45390195186705542   --> %20.0f\n", 45390195186705542d);
        System.out.printf("45390195186705543   --> %20.0f\n", 45390195186705543d);
        System.out.printf("45390195186705544   --> %20.0f\n", 45390195186705544d);
        System.out.printf("45390195186705545   --> %20.0f\n", 45390195186705545d);
        System.out.printf("45390195186705546   --> %20.0f\n", 45390195186705546d);
        System.out.printf("45390195186705547   --> %20.0f\n", 45390195186705547d);
        System.out.printf("45390195186705548   --> %20.0f\n", 45390195186705548d);
        System.out.printf("45390195186705549   --> %20.0f\n", 45390195186705549d);
    }

}

Это приведет к следующему результату:

45390195186705540   -->    45390195186705536
45390195186705541   -->    45390195186705544
45390195186705542   -->    45390195186705544
45390195186705543   -->    45390195186705544
45390195186705544   -->    45390195186705544
45390195186705545   -->    45390195186705544
45390195186705546   -->    45390195186705544
45390195186705547   -->    45390195186705544
45390195186705548   -->    45390195186705552
45390195186705549   -->    45390195186705552

Это показывает, что для первого числа верны только первые 15 десятичных цифр. Для многих чисел также правильный 16-й ди git. И в одном случае, 45390195186705544, правильные даже 17 цифр.

Если вам нужна более высокая точность, рассмотрите возможность использования BigDecimal вместо double .

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