Попытка воссоздать поведение printf с удвоением и заданной точностью (округление) и задать вопрос об обработке больших чисел - PullRequest
0 голосов
/ 04 июля 2019

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

double  ft_round(double value, int precision)
{
    long long int power;
    long long int result;

    power = ft_power(10, precision);
    result = (long long int) (value * power);
    return ((double)result / power);
}

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

-154584942443242549.213565124235

Я получаю -922337203685.4775391 в качестве вывода, тогда как сам printf дает мне -154584942443242560.0000000 (точность для обоих выходов равна 7).

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

Мой вопрос в основном состоит из двух частей:

  1. Что именно происходит в этом случае как с моим кодом, так и с самим printf, что вызывает этот вывод? (Я довольно новичок в программировании, извините, если это глупый вопрос)
  2. Ребята, есть ли у вас какие-либо советы о том, как сделать мой код способным обрабатывать эти большие числа?

P.S. Я знаю, что есть библиотеки и тому подобное, чтобы выполнить округление, но я ищу ответ типа «изобретать за рулем» здесь, только к вашему сведению!

Ответы [ 2 ]

2 голосов
/ 04 июля 2019

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

Единственный способ округлить число с плавающей запятой до десятичного - это выполнить всю арифметикув десятичном.По сути, вы начинаете с мантиссы, конвертируя ее в десятичное, как целое число, а затем масштабируете ее степенями 2 (экспонента), используя десятичную арифметику.Величина (десятичной) точности, которую нужно соблюдать на каждом шаге, примерно (чуть-чуть больше) конечной десятичной точности, которую вы хотите.Однако, если вы хотите получить точный результат, он будет порядка диапазона экспонент base-2 (то есть очень большого).

Обычно вместо использования base 10 реализации используют основание с некоторой большой степенью 10, поскольку это эквивалентно работе с, но гораздо быстрее.1000000000 - хорошая база, потому что она умещается в 32 бита и позволяет вам рассматривать ваше десятичное представление как массив 32-битных целых чисел (сравнимо с тем, как BCD позволяет обрабатывать десятичные представления как массивы 4-битных полубайтов).

Моя реализация в musl плотная, но демонстрирует этот подход почти оптимально и может быть информативной.

0 голосов
/ 09 июля 2019
  1. Что именно происходит в этом случае как с моим кодом, так и с самим printf, что вызывает этот вывод?

Переполнение.Либо ft_power(10, precision) превышает LLONG_MAX и / или value * power > LLONG_MAX.

Ребята, есть ли у вас какие-либо советы о том, как сделать мой код способным обрабатывать эти большие числа?

Отложите различные типы int для округления / усечения.Используйте процедуры FP, такие как round(), nearby() и т. Д.

double ft_round(double value, int precision) {
    // Use a re-coded `ft_power()` that computes/returns `double`
    double pwr = ft_power(10, precision);
    return round(value * pwr)/pwr;
}

Как уже упоминалось в этом ответе , числа с плавающей запятой имеют двоичные характеристики, а также конечную точность,Использование только double расширит диапазон допустимого поведения.С экстремальным значением precision значение, вычисленное с помощью этого кода, будет близким, но потенциально только вблизи желаемого результата.

Использование более широкой математики расширит допустимый диапазон.

double ft_round(double value, int precision) {
    double pwr = ft_power(10, precision);
    return (double) (roundl((long double) value * pwr)/pwr);
}

Я не совсем понял, компенсирует ли printf ошибки усечения и округления, вызванные этим, но это другая история

См. Спецификатор ширины Printf для поддержания точности значения с плавающей точкой для печати FP с достаточной точностью.

...