Округление произвольной длины с плавающей запятой до n цифр

Я уже прочитал большинство вопросов SO, связанных с «округлением до n цифр». Я считаю, что этот вопрос является отдельным, потому что он больше связан с округлением поплавков произвольной длины.

0.56714329040978387299996866221035554975381578718651250813513107922304579308668456669321944696175229455773495728285284421635951688

Приведенное выше число является примером типов чисел, с которыми я работаю.

Я понимаю, как округлить число в С++, это довольно просто. Однако я не понимаю, как можно округлять произвольно длинные числа до n цифр.

Например, у меня может быть число длиной 192 цифры, которое мне нужно округлить до 84 цифр, или число, состоящее из 1831 цифры, которое мне нужно округлить до 293 цифр. Как это можно сделать с помощью одной функции (или аналогичной)?

Псевдокод для ясности, но на самом деле я использую мульти-точность Boost cpp_dec_float для чисел с произвольной точностью вместо стандартных:

float round(float num, int digits){
    //returns num rounded to n digits
}

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

Например, если у меня есть число из 1000 цифр, и я хочу округлить его до n цифр, мне нужно сделать что-то вроде floor(num * 10^1000)/10^1000). Это не работает, потому что 10^1000 очень велико. Решением этого было бы многократное умножение и деление с меньшими показателями.


person esote    schedule 12.10.2016    source источник
comment
Просто чтобы убедиться, что я правильно понял вопрос. Что из двух: округлить до цифры (относительная точность) или «округлить до единицы» (абсолютная точность). Например. round(0.253e+5,2digits)==0.25e+5 (родственник) или round(0.253e+5, 100units)==0.253e+5)?   -  person Adrian Colomitchi    schedule 12.10.2016
comment
@AdrianColomitchi Я больше говорю о том, что 0.0236, округленное до 3 цифр, возвращает 0.024, если вы об этом?   -  person esote    schedule 12.10.2016
comment
На самом деле я использую мультиточность Boost для поплавков произвольной точности - вам нужно быть более конкретным.   -  person Veedrac    schedule 12.10.2016
comment
Вот оно: round(x, precision_unit)=trunc(x/precision_unit+0.5)*precision_unit; - код в моем ответе   -  person Adrian Colomitchi    schedule 12.10.2016
comment
Ваш 0.5671432... не произвольной длины с плавающей запятой. Это текстовое представление значения, которое будет преобразовано компилятором в значение с плавающей запятой. Значения с плавающей запятой во всех известных мне системах не поддерживают точность 192 разряда, а тем более 1000 (обычно double может содержать около 16 разрядов). Как только вы выходите за пределы точности значения с плавающей запятой, любые отображаемые цифры становятся бессмысленными. Поэтому все, что вы хотите сделать со всеми этими цифрами, вам придется делать с текстовым представлением, а не с типами с плавающей запятой.   -  person Pete Becker    schedule 12.10.2016
comment
@PeteBecker Определение произвольного: основано на случайном выборе или личной прихоти. Я использую его в этом смысле и частично в вычислительном и математическом смысле. Я не знаю специфики работы мультиточной библиотеки Boost.   -  person esote    schedule 12.10.2016
comment
@Anonymous - мои извинения; Я упустил из виду ваше упоминание о многоточности Boost.   -  person Pete Becker    schedule 13.10.2016


Ответы (1)


[Отредактировано - лучшее решение]

Выполнение округления может основываться на:

round(x, precision_unit)=trunc(x/precision_unit+0.5)*precision_unit;

Что-то типа:

  using namespace boost::multiprecision;

  const uint lower_prec_digits=28;

  typedef number<cpp_dec_float<100>> higher_prec;
  typedef number<cpp_dec_float<lower_prec_digits>> lower_prec;

  const higher_prec eps_div=
      std::numeric_limits<
          number<cpp_dec_float<lower_prec_digits+1>>
      >::epsilon()
  ;

  higher_prec pi(
    "3.1415926535"
      "8979323846"
      "2643383279"
      "5028841971"
      "6939937510"
      "5820974944"
      "5923078164"
      "0628620899"
      "8628034825"
      "3421170679"
  );

  lower_prec round_pie=lower_prec(trunc(pi/eps_div+0.5)*eps_div);

  std::cout.precision(100);
  std::cout << round_pie << std::endl << pi << std::endl;

Результат:

3.1415926535897932384626433833
3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117068
person Adrian Colomitchi    schedule 12.10.2016
comment
Не используйте std::endl, если вам не нужны дополнительные функции, которые он делает. '\n' заканчивает строку. - person Pete Becker; 12.10.2016