Всякий раз, когда пользователь вводит дробь, она может содержать десятичные дроби (long double). Ниже у меня есть код (C++), показывающий, как это реализовать:
class Fraction
{
public:
long double numerator, denominator;
Fraction(long double _numerator, long double _denominator) : numerator(_numerator), denominator(_denominator) {}
}
bool isInt(long double a)
{
long double intPart;
std::modf(a, &intPart);
return (a == intPart);
}
void simplifyRealFraction(Fraction& frac)
{
constexpr long double MULTIPLE_FACTOR = 2L;
constexpr int EXPONENT_LIMIT = 100;
int n_exp, d_exp;
std::frexp(frac.numerator, &n_exp);
std::frexp(frac.denominator, &d_exp);
long double new_n = frac.numerator / std::exp2(std::max(n_exp,d_exp));
long double new_d = frac.denominator / std::exp2(std::max(n_exp,d_exp));
while(!isInt(new_n) || !isInt(new_d))
{
new_n *= MULTIPLE_FACTOR;
new_d *= MULTIPLE_FACTOR;
}
std::frexp(new_n, &n_exp);
std::frexp(new_d, &d_exp);
if (n_exp > EXPONENT_LIMIT || d_exp > EXPONENT_LIMIT)
{
return;
}
long long int int_gcd = std::gcd(static_cast<long long int>(new_n), static_cast<long long int>(new_d));
new_n /= int_gcd;
new_d /= int_gcd;
frac.numerator = new_n;
frac.denominator = new_d;
}
Однако, когда я пытаюсь запустить свою функцию с дробью 0,15/0,20, она меняет ее на гораздо большее, но эквивалентное значение: 5,40432e+15/7,20576e+15.
После некоторой отладки я обнаружил, что std::gcd вернул 1. Я предполагаю, что это ошибка округления числа с плавающей запятой от 0,15 до чего-то другого (например, 14,999999 или аналогичного).
Есть ли лучший способ сделать этот процесс? Как избежать ошибок округления?





Вы видите, как работают числа с плавающей запятой. Они не хранят числа в десятичном формате, например .15. Они хранят их в двоичном виде. Таким образом, он может точно содержать .5 (2^-1), .25 (2^-2), .125 (2^-3) и другие степени двойки (или их комбинации). Что-то вроде 0,15 сохраняется как 0,125 + 0,015625 (2^-6)+... Таким образом, любое число, которое не является точной суммой степеней 2, не будет точно удерживаться. Это будет оценено.
Если вам действительно нужно их точно удерживать, вам нужно использовать математическую библиотеку с фиксированной точкой. Или, если вы знаете, что все числа имеют ровно 2 десятичных знака, воспринимайте их как целые сотки, а не как целые числа.
Итак, как же калькулятор выполняет эту задачу? Если бы я ввел 1 + 0,1, это всегда дало бы результат 1,1 или 11/10.
ОНИ не используют числа с плавающей запятой. Они используют библиотеки с фиксированной запятой или большие целочисленные библиотеки.
У вас (почти) всегда будут числовые ошибки с числами с плавающей запятой. Единственный способ избежать этого – не использовать их. Поэтому сделайте целочисленные значения дроби/знаменителя (например, из библиотеки bigint)