Converting rational to/from decimal
Closed this issue · 6 comments
So, I'd like to {in,out}put numbers in my application by decimals (aka 1.234
) instead of fractions.
I have this incredibly naive function for converting a decimal to a fraction:
math::Rational decimal_to_rational(const std::string decimal) {
if (decimal.find_first_not_of("0123456789") == std::string::npos) {
return math::Rational(decimal);
} else {
bool found_decimal_point = false;
unsigned int ten_exp = 0;
std::string integer;
for (char current_char : decimal) {
if (current_char >= '0' && current_char <= '9') {
if (found_decimal_point) {
ten_exp++;
}
integer.append(1, current_char);
} else if (current_char == '.') {
if (found_decimal_point) {
throw std::invalid_argument("Multiple decimal points");
}
found_decimal_point = true;
} else {
throw std::invalid_argument("Not a number");
}
}
integer.append("/1");
integer.append(ten_exp, '0');
std::cout << integer;
return math::Rational(integer);
}
}
But I have nothing to convert back other than doing regular floating point arithmetic, which can cause overflows with complicated enough rationals.
Is there a better way to convert a decimal to a rational and back again?
The conversion from double precision floating point to rational isn't too bad; see the most recent commit that provides this additional constructor, e.g.,
math::Rational q1 = 1.1234;
math::Rational q2 = std::stod("1.1234");
(Note that the resulting value is the exact rational representation of the underlying 64-bit double, not the exact value of the decimal string literal. That is, the denominator is a power of 2, not 10. This seems more generally useful than converting directly from a decimal string.)
The conversion in the other direction is a bit tricker to get right, requiring handling of over/underflow, etc. See here for a good discussion; let me think about this and get back to you shortly.
Preferably, the conversion would be done entirely with a string, so there would be no risk of {over,under}flow. However, irrational fractions need to be dealt with, maybe you could allow a precision to be set (aka 50 digits after the decimal, then stop).
I may misunderstand exactly what you're looking for; I'm not sure what you mean by "irrational fractions"?
See the most recent commit with an added function to_string(precision)
to convert a rational to a "decimal" string (i.e., with a decimal point if needed) truncated to at most a given maximum number of digits to the right of the decimal point. (A specified maximum is required, since a rational may not have a terminating decimal representation.)
Still working on the conversion "back" from rational to double...
Sorry, by "irrational fractions" I meant fractions that didn't have a terminating decimal representation
(I just didn't know how to say it).
Also, I don't need it as a double - I really did want to convert the rational to a decimal string.
Ty! <3
P.S. if there's any way to detect a repeating decimal, that would be great too! :P
I cleaned up the conversion to and from decimal strings, to correctly round the string representation to a given (max) number of digits, and to support either the "a/b" or "integer.fraction" syntax in the constructor (so that the latter allows you to directly input strings yielding a denominator that is a power of 10).
Detection of a repeating decimal is difficult if you want to know the actual length of the period of the repetition, where by "difficult" is meant at least as hard as factoring the totient function of the denominator. If you find a way to do this, let me know :). (If you just want to know if it's repeating, you can check for any prime factors of the denominator other than 2 or 5, which is also not efficient in general.)
Ty for your support!