Rational Number to Repeating Numeral Conversion
code-golf math number-theory restricted-complexity
As I'm sure you know, the decimal expansion of every rational number is either terminating—consisting of a finite number of digits, or repeating—consisting of infinitely many digits, but ending with a finite pattern that repeats itself indefinitely: for example, the decimal expansion of the rational number 1/6 is 0.1666...
, where the sixes repeat forever.
One way to represent this decimal expansion finitely and unambiguously, is to write the repeating part, called the repetend, enclosed in parentheses:
going back to the previous example, under this scheme the number 1/6 is written as 0.1(6)
.
We call this representation a repeating decimal.
Of course, none of this is specific to base 10.
More generally, we call such a representation, in any base, a repeating numeral.
Note that, for the sake of this challenge, we use the term "repeating numeral" (or simply, "numeral") to refer to any numeral written using this scheme, whether or not it actually has a repeating part.
Challenge
Write a program or a function taking a pair of integers, p and q, and returning a repeating numeral representing the rational number p/q.
You may assume that p ≥ 0, and q > 0, so that p/q is never negative.
The resulting numeral should be in base 10, unless you go for the relevant bonus below.
There is more than one possible numeral representing a given rational number.
For example, the rational number 1/1 can be represented, among (infinitely many) other options, as 1
, 1.0
, 1.(0)
, 0.(9)
, and so on...
For this challenge, however, we'd like the output to be unique.
The following set of rules, which your program must follow, takes care of that:
When the integer part of the numeral is zero, there should be a single 0
before the radix point.
For example, given the input 1/2, the output should be 0.5
, and not .5
.
The repetend, if exists, must begin after the radix point; that is, it shouldn't apply to the integer part.
For example, given the input 10/9, the output should be 1.(1)
, and not (1)
.
The output should be finite and minimal, under the above rules.
This is the most significant rule controlling the output, so it's worth highlighting some of its consequences:
There should be no leading or trailing zeroes.
For example, given the input 3/2, the output should be 1.5
, and not 01.5
or 1.50
.
If the fractional part is zero, it should be omitted.
For example, given the input 1/1, the output should be 1
, and not 1.0
.
If the repetend is zero, it should be omitted.
For example, given the input 1/2, the output should be 0.5
, and not 0.5(0)
.
When the input admits a terminating numeral, there shouldn't be an unnecessary repetend.
For example, given the input 1/1, the output should be 1
, and not 0.(9)
.
The repetend, and the rest of the fractional part, should not be superfluous; there shouldn't be any repetition in the repetend itself, nor in the repetend and the rest of the fractional part.
For example, given the input 1/99, the output should be 0.(01)
, and not 0.01(01)
, 0.(0101)
, or 0.0(10)
.
As usual, you may not use any built-in or library functions aimed specifically at this problem.
Input and Output
You may take the input through the command line, through STDIN, as function arguments, or using an equivalent method.
You may use any convenient format for the input, but make sure to specify it in your post.
You may assume that p and q are no greater than 10,000,000, to the extent that it helps you to avoid overflow.
You may write the output to STDOUT, return it as the function's result or through an output parameter, as a string, or use an equivalent method.
Score
This is code-golf. The shortest answer, in bytes, combined with the any of the bonuses, wins.
Bonuses
If, in addition to the requirements listed above, your program satisfies the requirements for any of the following bonuses, multiply your score by the specified amount.
×0.8 Bonus Support other bases
Your program should take a third parameter, b, which is an integer between 2 and 36, inclusive, and return the corresponding numeral in base b, instead of base 10.
The letters of the alphabet, either in lowercase or uppercase, should be used as digits above 9.
For example, given the input 1/2 and b=3, the output should be 0.(1)
.
Note that base b applies only to the output—if your program takes its input in string form, it should interpret it in base 10.
×0.6 Bonus O(1) space complexity
Your program's space complexity should be O(1), where the unit of space is the amount of space required to hold the input.
In other words, the amount of memory required by your program should, on a large scale, be proportional to the amount of memory required by the input.
You may not use the fact that the input range is bounded when reasoning about your program's space complexity, other than to the extent of establishing the amount of space required by the input.
If you go for this bonus, it is strongly advised that you include at least a brief explanation of your program, so that others can verify that it meets the criterion.
If your program does not print the output directly, but rather returns a string, you may ignore the space occupied by the string as long as your program only appends to the string, and doesn't modify or read from it otherwise.
(Note that something like s=s+"0"
is fine, even though it technically involves reading from the string.)
Test Cases
Each of the test cases below lists the input, p/q
, on the first line, and the corresponding output on the second line.
Some of the tests list a third parameter, b
, which specifies a different base for the output.
These tests are only applicable for the relevant bonus.
Short-Output Tests
Your program should solve each of the following tests in a matter of seconds.
0/1
0
123/123
1
12/3
4
1/2
0.5
1/3
0.(3)
7/6
1.1(6)
3/11
0.(27)
1234/9999
0.(1234)
7/12
0.58(3)
22/7
3.(142857)
123/456
0.269(736842105263157894)
7124771/4545450
1.56(745118)
1/68
0.01(4705882352941176)
678/2345
0.2(891257995735607675906183368869936034115138592750533049040511727078)
7683/238
32.2(815126050420168067226890756302521008403361344537)
123631/99999
1.(23632)
4576/2345
1.9(513859275053304904051172707889125799573560767590618336886993603411)
2239231/4950000
0.45236(98)
673/23430
0.0(2872385830132309005548442168160478019632949210413999146393512590695689)
8984/2318
3.(875754961173425366695427092320966350301984469370146678170836928386540120793787748058671268334771354616048317515099223468507333908541846419327006039689387402933563416738567730802415)
1/2 2
0.1
214/5467 13
0.(067ccb5145334200a07b6253394bc5ca12bc650017b87998acc2c516a7993810702ba1)
214 5467 11
0.0(481119a34a86245a0053a1022114643a64131367585a8095498942556925833303993816590371976aa2a6952238969a61490900a792044229187a18262724060a517a986785002750566607877532070733842a95a27a4476828a12971701a4740884573649355153)
330420/335923 36
0.(zero)
9322181/306936 28
12.ab(cd)
123/6573 29
0.(0flb8c3hblol)
123/6573 21
0.0(85669592cjj9ca173fij6g67hhg7g940b5efk6j2bb7276c1ch25ikf7dd070jf4h96hek41ji102h3ea6kb2ic5hc3089b19kjc8dfhaf9147c48kgg4k5ab463d1fd5k86eigacciai54ihj3k2e7aghdbcfeebfbi811b8ajdh521e4ed334d4bgk9f650e1i99dide8j83if205d77kdk15g3be360gj12jki3h6ae09i28f38hkcb9jb018c753a5bjgd8gc044g0fa9geh7j57f0ce624a882a2fg231h0i6da4379)
Long-Output Tests
The following tests produce significantly longer output.
Instead of listing their full output, only the MD5 checksum of the output if given.
The checksum is calculated without a trailing newline, and using lowercase letters for digits above 9 (in the relevant test cases).
If you want to see the full output for these tests, or to calculate the checksum under different conditions, you can use the test snippet below.
Your program should solve each of the following tests in no more than a few minutes.
343/677472
e1810c85500a97c36f2ba5dcc94a2aa6
234/62332
662adcee10cd77d001282f0e31a84b77
57628/7894211
81222aaa93f192c6d8334fcde6381a74
67332/1267232
3ea190c83a735a8bafc922cbb177e6f4
954/3684332
d4ef940c9986f9c2ac52ee822d94d6a7
783232/3462241
7724d3444b5540b8b4f07d13317e8da3
83/7657124
b295369267f48e642e202e0364898c82
764231/54646224
ecf7f3dd12f2d495b31a178fc03662d7
6743/1231234
9f447fd27447abb881795fc5e2f814e5
764/9343249
8381dc6f127be6d95cadc0b5bdd70104
345/2234448 22
667d437e35c3825696f64f1b27e6a827
63451/2343324 3
5d86d77856cc0a15ac8a51b46c2f22d6
93457/546464 2
1084e884aded53f0da933480c45db1b8
678541/453524 9
5bb54bddaab7bd933258d4d78a83cdba
4572/2341198 15
39af2bc67ab58438068f672ed3a8357d
1/4821466 6
57ba79a48c438b7fd21aa39ee3575c8d
145/5821417 21
009f087ae661cfd4690cc51ba71a827f
5472/2333645 33
d24583dfdad205de61763c6a87637979
Test Snippet
If you want to further test your submission, you can use the following test snippet to find the repeating numeral representation of arbitrary rational numbers, or to find the rational number corresponding to arbitrary repeating numerals.
Note that the result can get very big, very fast, which can be a little much for your browser; in this case, you might want to uncheck the "Result" checkbox, and check the "MD5" checkbox, to only get the MD5 checksum of the output.
Note also that you can resize the input and output boxes.
<style>* { font-family: sans-serif; }table { border-collapse: collapse; }table > tbody > tr > td { padding: 0px; }#status { display: none; }#status[loading] { display: initial; font-style: italic; }#main { display: none; width:100%; border-right:.7em solid transparent; font-size: 95%; }#main[loaded] { display: table; }#main > tbody > tr + tr > td { padding-top: .25em; }#main > tbody > tr > td + td { padding-left: .5em; }#main > tbody > tr > td:first-of-type { width: 4.25em; }.label { white-space: pre; }#flags { padding-left: 2.4em; font-size: smaller; }#flags > table > tbody > tr > td + td { padding-left: 1em; }#error { display: none; font-size: smaller; color: #880000; }#error[error] { display: initial; }#job_cancel, #job_throbber { display: none; vertical-align: bottom; }#job_cancel[working], #job_throbber[working] { display: initial; }#job_throbber > img { height: 1em; }#job_throbber[working] { padding-left: .3em; }#job_cancel[working] { padding-left: 1em; }#job_cancel > input { height: 1.8em; font-size: small; }input[type="text"], input[type="number"], textarea { padding: 0.25em; height: 1.4em; font-family: monospace; }textarea { width: 100%; }#base { width: 2.8em; text-align: right; }.output { background-color: #e4e4e4; border: none; }#length { width: 8em; text-align: right; }#md5 { width: 20em; text-align: center; }#result_container[active="false"] { display: none; }#md5_container[active="false"] { display: none; }</style></head><body><div id="status" loading>Loading...</div><table id="main" onkeydown="handle_key_down(event)"><tr><td id="input_label" class="label">Fraction:</td><td><textarea id="input" oninput="update(true)" spellcheck="false">123/456</textarea></td></tr><tr><td class="label">Base:</td><td><table><tr><td><input id="base" type="number" value="10" spellcheck="false" oninput="update(true)"></td><td id="flags"><table><tr><td><label><table><tr><td><input id="flag_result" type="checkbox" checked onchange="handle_flag('result')"></td><td>Result</td></tr></table></label></td><td><label><table><tr><td><input id="flag_md5" type="checkbox" onchange="handle_flag('md5')"></td><td>MD5</td></tr></table></label></td><td><label><table><tr><td><input id="flag_uppercase" type="checkbox" onchange="handle_flag('uppercase')"></td><td>Uppercase</td></tr></table></label></td><td><label><table><tr><td><input id="flag_trailing_newline" type="checkbox" onchange="handle_flag('trailing_newline')"></td><td>Trailing Newline</td></tr></table></label></td><td><label><table><tr><td><input id="flag_progressive_output" type="checkbox" onchange="handle_flag('progressive_output')"></td><td>Progressive Output</td></tr></table></label></td></tr></table></td></tr></table></td></tr><tr><td></td><td><span id="error"></span></td></tr><tr><td class="label">Length:</td><td><table id="length_table"><tr><td><input id="length" class="output" type="text" spellcheck="false" readonly></td><td id="job_throbber"><img src="https://i.stack.imgur.com/sHKZY.gif"></td><td id="job_cancel"><input type="button" value="Cancel" onclick="cancel_job()"><td></tr></table></td></tr><tr id="md5_container"><td class="label">MD5:</td><td><input id="md5" class="output" type="text" spellcheck="false" readonly></td></tr><tr id="result_container"><td id="result_label" class="label">Numeral:</td><td class="full_width"><textarea id="result" class="output" spellcheck="false" readonly></textarea></td></tr></table><script async type="text/javascript" src="https://gist.githack.com/anonymous/49705525fd01ba66c1ad/raw/c35226ad89444e3af07d0e505f7163df8574b860/repnum.js"></script>