Переключитесь на BigInteger при необходимости

Я читаю текстовый файл, который содержит числа в диапазоне [1, 10^100]. Я затем выполняю последовательность арифметических операций на каждом числе. Я хотел бы использовать BigInteger, только если число вне интервала/большого расстояния. Один подход должен был бы рассчитать, сколько цифр там находится в строке и переключается на BigInteger, если существуют слишком многие. Иначе я просто использовал бы примитивную арифметику, поскольку это быстрее. Существует ли лучший путь?

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

6
задан dogbane 6 April 2010 в 17:48
поделиться

6 ответов

Я подозреваю, что решение использовать примитивные значения для целых и действительных чисел (сделанное по соображениям производительности) сделало этот вариант невозможным. Обратите внимание, что Python и Ruby делают то, о чем вы просите.

В этом случае для обработки небольшого особого случая может потребоваться больше работы, чем оно того стоит (вам нужен какой-то специальный класс для обработки двух случаев), и вам следует просто использовать BigInteger .

6
ответ дан 9 December 2019 в 22:31
поделиться

Возможно ли это? да. Но с этим много проблем.

Рассмотрим, например, что Java хранит ссылок на BigInteger, который фактически размещен в куче, но хранит литералы int . Разницу можно прояснить в C:

int i;
BigInt* bi;

Теперь, чтобы автоматически перейти от литерала к ссылке, необходимо как-то аннотировать литерал. Например, если был установлен наивысший бит int, тогда другие биты можно было бы использовать как поиск в таблице для получения правильной ссылки. Это также означает, что вы будете получать BigInt ** bi всякий раз, когда он переполняется.

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

Все это добавило бы много накладных расходов к базовой целочисленной арифметике, что на практике свело бы на нет любые преимущества, которые у вас были изначально. Другими словами, быстрее принять BigInt, чем пытаться использовать int и обнаруживать условия переполнения, в то же время манипулируя проблемой ссылки / литерала.

Итак, чтобы получить реальное преимущество, нужно было бы использовать на больше места для представления целых чисел.Таким образом, вместо того, чтобы хранить 32 бита в стеке, в объектах или где-либо еще, где мы их используем, мы храним, например, 64 бита и используем дополнительные 32 бита для управления тем, хотим ли мы ссылку или литерал. Это могло бы сработать, но есть очевидная проблема - использование пространства. :-) Мы могли бы увидеть больше этого на 64-битном оборудовании.

Теперь вы можете спросить, почему не просто 40 бит (32 бита + 1 байт) вместо 64? В принципе, на современном оборудовании предпочтительнее хранить данные с шагом 32 бита по соображениям производительности, поэтому мы в любом случае будем заполнять 40 бит до 64 бит.

РЕДАКТИРОВАТЬ

Давайте рассмотрим, как это можно сделать на C #. У меня нет опыта программирования на C #, поэтому я не могу написать код для этого, но надеюсь, что смогу дать обзор.

Идея состоит в том, чтобы создать для него структуру. Это должно выглядеть примерно так:

public struct MixedInt
{
   private int i;
   private System.Numeric.BigInteger bi;

   public MixedInt(string s) 
   {
      bi = BigInteger.Parse(s);
      if (parsed <= int.MaxValue && parsed => int.MinValue)
      {
          i = (int32) parsed;
          bi = 0;
      }   
   }

   // Define all required operations
}

Итак, если число находится в целочисленном диапазоне, мы используем int, в противном случае мы используем BigInteger. Операции должны обеспечивать переход от одной к другой по мере необходимости / возможности. С точки зрения клиента это прозрачно. Это всего лишь один тип MixedInt, и класс позаботится об использовании того, что подходит лучше.

Обратите внимание, однако, что такая оптимизация вполне может быть уже частью BigInteger C #, учитывая ее реализацию в виде структуры.

Если бы в Java было что-то вроде структуры C #, мы могли бы сделать что-то подобное и в Java.

0
ответ дан 9 December 2019 в 22:31
поделиться

Есть ли причина, по которой Java не может сделать это автоматически, т.е. переключиться на BigInteger, если int было слишком маленьким?

Потому что это поведение программирования более высокого уровня, чем в настоящее время Java. Язык даже не знает о классе BigInteger и о том, что он делает (т.е. его нет в JLS). Он знает только Integer (среди прочего) для целей упаковки и распаковки.

Говоря о упаковке / распаковке, int является примитивным типом; BigInteger - это ссылочный тип. У вас не может быть переменной, которая может содержать значения обоих типов.

4
ответ дан 9 December 2019 в 22:31
поделиться

Вы можете прочитать значения в BigInteger s, а затем преобразовать их в long s, если они достаточно малы.

private final BigInteger LONG_MAX = BigInteger.valueOf(Long.MAX_VALUE);
private static List<BigInteger> readAndProcess(BufferedReader rd) throws IOException {
    List<BigInteger> result = new ArrayList<BigInteger>();
    for (String line; (line = rd.readLine()) != null; ) {
        BigInteger bignum = new BigInteger(line);
        if (bignum.compareTo(LONG_MAX) > 0) // doesn't fit in a long
            result.add(bignumCalculation(bignum));
        else result.add(BigInteger.valueOf(primitiveCalculation(bignum.longValue())));
    }
    return result;
}
private BigInteger bignumCalculation(BigInteger value) { 
    // perform the calculation 
}
private long primitiveCalculation(long value) {
    // perform the calculation
}

(Вы можете сделать возвращаемое значение List и сделать его смешанным набором объектов BigInteger и Long , но это не будет выглядят очень красиво и не сильно улучшат производительность.)

Производительность может быть лучше , если большое количество чисел в файле достаточно мало, чтобы поместиться в a long (в зависимости от сложности расчета). По-прежнему существует риск переполнения в зависимости от того, что вы делаете в primitiveCalculation , и теперь вы повторили код, (по крайней мере) удвоив потенциал ошибки, поэтому вам нужно будет решить, действительно ли прирост производительности стоило того.

Если ваш код чем-то похож на мой пример, вы, вероятно, получите больше от распараллеливания кода, чтобы вычисления и ввод-вывод не выполнялись в одном потоке - вам придется сделать некоторые довольно тяжелые вычисления для такой архитектуры, чтобы зависеть от ЦП.

1
ответ дан 9 December 2019 в 22:31
поделиться

Влияние использования BigDecimals, когда будет достаточно чего-то меньшего, на удивление, эээ, большое: выполнение следующего кода

public static class MyLong {
    private long l;
    public MyLong(long l) { this.l = l; }
    public void add(MyLong l2) { l += l2.l; }
}

public static void main(String[] args) throws Exception {
    // generate lots of random numbers
    long ls[] = new long[100000];
    BigDecimal bds[] = new BigDecimal[100000];
    MyLong mls[] = new MyLong[100000];
    Random r = new Random();
    for (int i=0; i<ls.length; i++) {
        long n = r.nextLong();
        ls[i] = n;
        bds[i] = new BigDecimal(n);
        mls[i] = new MyLong(n);
    }
    // time with longs & Bigints
    long t0 = System.currentTimeMillis();
    for (int j=0; j<1000; j++) for (int i=0; i<ls.length-1; i++) {
        ls[i] += ls[i+1];
    }
    long t1 = Math.max(t0 + 1, System.currentTimeMillis());
    for (int j=0; j<1000; j++) for (int i=0; i<ls.length-1; i++) {
        bds[i].add(bds[i+1]);
    }
    long t2 = System.currentTimeMillis();
    for (int j=0; j<1000; j++) for (int i=0; i<ls.length-1; i++) {
        mls[i].add(mls[i+1]);
    }
    long t3 = System.currentTimeMillis();
    // compare times
    t3 -= t2;
    t2 -= t1;
    t1 -= t0;
    DecimalFormat df = new DecimalFormat("0.00");
    System.err.println("long: " + t1 + "ms, bigd: " + t2 + "ms, x"
            + df.format(t2*1.0/t1) + " more, mylong: " + t3 + "ms, x"
            + df.format(t3*1.0/t1) + " more");
}

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

long: 375ms, bigd: 6296ms, x16.79 more, mylong: 516ms, x1.38 more

Класс MyLong предназначен только для того, чтобы посмотреть на эффекты бокса, чтобы сравнить их с тем, что вы получили бы с обычным BigOrLong класс.

1
ответ дан 9 December 2019 в 22:31
поделиться

Есть ли причина, по которой Java не могла сделать это автоматически, т.е. переключиться на BigInteger, если int было слишком маленьким?

Это одно из преимуществ динамической типизации , но Java статически типизирована и предотвращает это.

В языке с динамическим типом, когда два Integer , которые суммируются, приводят к переполнению, система может вернуть, скажем, Long . Поскольку язык с динамической типизацией полагается на утиную печать, это нормально. Этого не может случиться со статически типизированным языком; это нарушило бы систему типов.

РЕДАКТИРОВАТЬ

Учитывая, что мой ответ и комментарий не были ясными, здесь я пытаюсь более подробно объяснить, почему я считаю, что статическая типизация является основной проблемой:

1) сам факт того, что мы говорим о примитивном типе - это проблема статической типизации; нас не заботит язык с динамическим типом.

2) с примитивными типами результат переполнения не может быть преобразован в другой тип, кроме int , потому что это было бы неверно со статической типизацией

   int i = Integer.MAX_VALUE + 1; // -2147483648

3) со ссылочными типами, это то же самое за исключением того, что у нас есть автобокс.Тем не менее, добавление не могло вернуть, скажем, BigInteger , потому что оно не соответствовало бы системе статического типа (A BigInteger не может быть преобразовано в Integer ).

  Integer j = new Integer( Integer.MAX_VALUE ) + 1; // -2147483648

4) можно было бы создать подкласс, скажем, Number и реализовать в типе UnboundedNumeric , который оптимизирует представление внутренне (независимость представления).

 UnboundedNum k = new UnboundedNum( Integer.MAX_VALUE ).add( 1 ); // 2147483648

Тем не менее, это не совсем ответ на исходный вопрос.

5) с динамической типизацией, что-то вроде

var d = new Integer( Integer.MAX_VALUE ) + 1; // 2147483648

вернет Long , что нормально.

-2
ответ дан 9 December 2019 в 22:31
поделиться
Другие вопросы по тегам:

Похожие вопросы: