    <rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:sy="http://purl.org/rss/1.0/modules/syndication/" xmlns:admin="http://webns.net/mvcb/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:content="http://purl.org/rss/1.0/modules/content/">
     <channel>
        <title>ACCU  :: Further Comments on Code Critique 123</title>
        <link>https://members.accu.org/index.php/articles/2828</link>
        <description>Professionalism in Programming</description>
        <dc:language>en-us</dc:language> 
        <dc:creator>Administrator</dc:creator> 
        <admin:generatorAgent rdf:resource="http://www.xaraya.org" /> 
        <admin:errorReportsTo rdf:resource="mailto:webeditor@accu.org" />
       <sy:updatePeriod>hourly</sy:updatePeriod>
       <sy:updateFrequency>1</sy:updateFrequency>
       <docs>http://backend.userland.com/rss</docs>




<div class="xar-mod-head"><span class="xar-mod-title">Student Code Critiques from CVu journal. + CVu Journal Vol 32, #4 - September 2020</span></div>

<table border="0" cellpadding="1" cellspacing="0">
    <tbody>
    <tr>
        <td valign="top">
            Browse in :
       </td>
       <td valign="top">

                                            <a href="https://members.accu.org/index.php/articles/">All</a>

                     &gt;                         <a href="https://members.accu.org/index.php/articles/c184/">Journal Columns</a>

                     &gt;                         <a href="https://members.accu.org/index.php/articles/c183/">Code Critique</a>
<br />

                                            <a href="https://members.accu.org/index.php/articles/">All</a>

                     &gt;                         <a href="https://members.accu.org/index.php/articles/c76/">Journals</a>

                     &gt;                         <a href="https://members.accu.org/index.php/articles/c77/">CVu</a>

                     &gt;                         <a href="https://members.accu.org/index.php/articles/c414/">324</a>
<br />

                                            <a href="https://members.accu.org/index.php/articles/c183-414/">Any of these categories</a>

                    -                        <a href="https://members.accu.org/index.php/articles/c183+414/">All of these categories</a>
<br />
</td>
   </tr>
   </tbody>
</table>




<div class="xar-error">
   <p>
 <strong>Note:</strong> when you create a new publication type,
the articles module will automatically use the templates
<em>user-display-[publicationtype].xt</em>
and <em>user-summary-[publicationtype].xt</em>.
If those templates do not exist when you try to preview or display a new article,
you'll get this warning :-)  Please place your own templates in themes/<em>yourtheme</em>/modules/articles . The templates will get the extension .xt there. </p>
</div>
<div class="xar-norm xar-standard-box-padding">
   <h1><strong>Title:</strong>&nbsp;Further Comments on Code Critique 123</h1>
<p><strong>Author:</strong>&nbsp;Bob Schmidt</p>
<p>
<strong>Date:</strong> 02 September 2020 17:02:39 +01:00 or Wed, 02 September 2020 17:02:39 +01:00</p>
<p><strong>Summary:</strong>&nbsp;Additional information has been sent in response to an earlier Code Critique Competition.</p>
<p><strong>Body:</strong>&nbsp;<p>Steven Singer &lt;steven@pertinentdetail.org&gt; writes in with some additional reflections on the last issueâ€™s code critique. For reference, the code is in Listing 1 (<span class="filename">pow.c</span>) and Listing 2 (<span class="filename">test.c</span>).</p>

<table class="sidebartable">
	<tr>
		<td>
			<pre class="programlisting">
/**
  * @input A : Integer
  *  * @Output Integer
  */
int isPower(int A) {
  if(A==1) return 1;
  else
  {
    int i;
    if(A%2==0)
    {
      for(i=2; i&lt;=sqrt(A); i=i+2)
      if(pow(i, (int)(log(A)/log(i)))==A)
      return 1;
    }
    else
    {
      for(i=3; i&lt;=sqrt(A); i=i+2)
      if(pow(i, (int)(log(A)/log(i)))==A)
      return 1;
    }
    return 0;
  }
}
			</pre>
		</td>
	</tr>
	<tr>
		<td class="title">Listing 1</td>
	</tr>
</table>

<table class="sidebartable">
	<tr>
		<td>
			<pre class="programlisting">
#include &lt;stdio.h&gt;
int main(void)
{
  int A = 1000;
  printf(&quot;isPower(1000) =&gt; %i\n&quot;, isPower(A));
  return 0;
}
			</pre>
		</td>
	</tr>
	<tr>
		<td class="title">Listing 2</td>
	</tr>
</table>

<p>This is the original problem:</p>

<p class="blockquote">Please find a bug in my code based on finding whether a positive integer can be expressed as pow(a, b) where a and b are non negative integers and b&gt;1. My code outputs for 1000 (10<sup>3</sup>), 0 where it should be 1. Please find the bugâ€¦</p>

<h2>Steven writesâ€¦</h2>

<p>There is a significant optimisation that was missed in the answers to code critique #123, and I think it falls into the category of things <em>CVu</em> readers like to read about.</p>

<p>Marcel MarrÃ© mentioned something in passing at the end of his critique that could be interpreted as the same optimisation but it was bound up in comments about avoiding floating point. I think the optimisation is useful in its own right, and simple enough to explain if we stick to using floats.</p>

<p>But, before I get there, I learnt a couple of lessons whilst writing code to show the optimisation of interest, and I think those lessons are also worth passing on.</p>

<p>In these examples, Iâ€™m not going to worry about the return value for inputs less than one. That was covered adequately in the existing code critiques and I want to emphasise the core of the functions.</p>

<p>To test my code, I built a test framework to run the various code samples so I could benchmark them and check the results. I ran test cases for all the results that should produce a positive result and all the adjacent integers that should produce a negative result for a total of a little over 140,000 test cases.</p>

<p>Iâ€™m going to show some code samples. Iâ€™ll use the typedef <code>domain</code> for the integer type used for the input and calculations to distinguish it from the integers used for other purposes (such as return values) and to allow me to try other types (unsigned and/or long integers).</p>

<p>The first thing I learnt was that the compiler I was using would quite happily hoist the <code>sqrt(A)</code> used as the loop limit out of the loop, but it wouldnâ€™t hoist the <code>log(A)</code>. That surprised me. I expected it to spot both or neither. I created a basic function with these optimisations that I could use as a timing baseline (see Listing 3).</p>

<table class="sidebartable">
	<tr>
		<td>
			<pre class="programlisting">
int simple(domain A)
{
  if (A == 1)
    return 1;
  domain root_A = round(sqrt(A));
  double log_A = log(A);
  for(domain i = A % 2 == 0 ? 2 : 3;
      i &lt;= root_A; i += 2)
    if (pow(i, round(log_A / log(i))) == A)
      return 1;
  return 0;
}
			</pre>
		</td>
	</tr>
	<tr>
		<td class="title">Listing 3</td>
	</tr>
</table>

<p>Note that the equality comparison between the result of the <code>pow()</code> and <code>A</code> is safe as the mantissa of the double has many more bits than the integer being used to hold <code>A</code>, and weâ€™re giving <code>pow()</code> representable integer inputs. If the mantissa werenâ€™t big enough, then weâ€™d need to either use a longer floating point type or a whole different strategy. Adding a tolerance is not good enough, if the answer is out by one, then we need to return zero even if our input is 2<sup>128</sup>âˆ’1.</p>

<p>On the random machine I was using (a 2.16 GHz Celeron N2840, so moderately modern but bottom of the range, with GCC 5.4.0 -O3), this took 243 seconds to run all my test cases. Code samples from the other answers using the same strategy had similar performance, varying slightly depending on whether the <code>log(A)</code> had been hoisted.</p>

<p>The interesting exception was the code from Marcel MarrÃ©, which was significantly faster. It took 76 seconds to run the tests. I could get this down to 56 seconds by rewriting the code to avoid long integers as follows:</p>

<pre class="programlisting">
  int marre_no_long(domain A)
  {
    if (A == 1) {
      return 1;
    } else {

      domain root_A = round(sqrt(A));
      domain a;

      for(a = A % 2 == 0 ? 2 : 3; a &lt;= root_A;
          a++) {
        if (A % a == 0) {
          domain limit = A / a;
          domain p = a * a;
          while(p &lt;= limit)
            p *= a;
          if (p == A)
            return 1;
        }
      }
      return 0;
    }
  }</pre>
  
<p>At first this result makes no sense. Marcelâ€™s code may be avoiding floating point operations in the loop but those are fast on modern computers (not as fast as integer, but fast nonetheless). Instead he has a loop which must be expensive.</p>

<p>The answer lies in the pre-test he does. Before going into the long loop, he checks whether the target is a multiple of base thatâ€™s been chosen (the line <code>if (A % a == 0)</code>). Clearly if the target. which is greater than one, is not a multiple of the base, then it canâ€™t be a positive integer power of the base.</p>

<p>This modulo operation is fast. Itâ€™s a single integer operation instead of (at least) two floating point operations. Nearly all choices of base fail this test (for example, when the loop index is seven, six out of seven target values will fail the test). That was the second lesson.</p>

<p>That suggests we can optimise the simple code to perform this integer test. It will cost us an extra integer operation in a few cases, but it will save us two floating point operations in most cases (<code>log</code> and <code>pow</code> and associated rounding and comparisons). This gives code like this:</p>

<pre class="programlisting">
  int check_mod(domain A)
  {
    if (A == 1)
      return 1;
    domain root_A = round(sqrt(A));
    double log_A = log(A);
    for(domain i = A % 2 == 0 ? 2 : 3;
        i &lt;= root_A; i += 2)
      if (A % i == 0 &amp;&amp;
          pow(i, round(log_A / log(i))) == A)
        return 1;
    return 0;
  }</pre>

<p>This reduces the run time to 29 seconds, so eight times faster than the moderately optimised float version and not a significant change in strategy. Not bad for such a small change.</p>

<p>But, we can do a lot better than this, and that involves going back to the problem weâ€™re trying to solve and optimising our algorithm not our code.</p>

<p>The problem weâ€™re trying to solve is given a number, <em>A</em>, find out whether it can be written as one integer to the power of another, <em>x</em><sup>y</sup> with <em>y</em> â‰¥ 2. Itâ€™s not possible to solve this directly for both variables. The technique chosen in the solutions so far is to make an exhaustive search of <em>x</em> and then, for each <em>x</em>, look to see if thereâ€™s a solution for <em>y</em>. This is fairly easy to do by taking the log to base <em>x</em> of both sides of the equation <em>x</em><sup>y</sup> = <em>A</em> to give <em>y</em> = log<sub>x</sub><em> A</em> followed by a test to see whether <em>y</em> is an integer.</p>

<p>The trick is to spot that we can do this the other way, we can make a guess at <em>y</em> and find <em>x</em>. We can take the <em>y</em>th root of both sides to give <em>x</em> = <sup>y</sup>âˆš<em>A</em> and then check whether <em>x</em> is an integer.</p>

<p>As with the original approach, instead of directly testing whether we get an integer result, we calculate <em></em><sup>y</sup> and check we get the original result.</p>

<p>We need to find a limit for the search. In both cases, the limit for the value weâ€™re varying is found by looking at the minimum value of the other variable. So, when weâ€™re varying <em>x</em>, we need to allow for <em>y</em> being two which means that we need to search up to <em>x</em> = âˆš<em>A</em>. When weâ€™re varying <em>y</em>, we need to allow for <em>x</em> being 2 which means we need to search up to <em>y</em> = log<sub>2</sub> <em>A</em>. That gives us the following code:</p>

<pre class="programlisting">
  int root_not_base(domain A)
  {
    if (A == 1)
      return 1;
    int limit = log(A)/log(2) + 0.0001;
    for(int root = 2; root &lt;= limit; root++)
      if (pow(round(pow(A, 1.0/root)), root) == A)
        return 1;
    return 0;
  }
</pre>

<p>This runs through the test cases in just 0.82 seconds which is about three hundred times faster than the original. This is not a bad solution and itâ€™s quite short and therefore readable.</p>

<p>The next obvious question is can we do better. Well, of course we can but weâ€™re going to have to sacrifice a little readability.</p>

<p>For large inputs, we end up searching a lot of roots which will have a result between two and three. Thatâ€™s because the largest power of two below <code>INT_MAX</code> is 2<sup>30</sup> but the largest power of three is 3<sup>19</sup>.</p>

<p>It turns out that, since contemporary computers hold numbers in base two, and can perform bit operations quickly, thereâ€™s a neat trick to check if a number is a power of two. If we use that to catch the powers of two, then we can terminate our search at log<sub>3</sub><em>A</em>. We have to reject two explicitly as thatâ€™s 2<sup>1</sup> and so doesnâ€™t satisfy our requirements but, on the other hand, the trick gives us the answer we want for one. This code uses a slightly different way of exiting at the limit:</p>

<pre class="programlisting">
  int root_opt(domain A)
  {
    if (A == 2)
      return 0;
    /* Power of 2 or zero */
    if ((A &amp; (A - 1)) == 0)
      return 1;
    for(int root = 2; ;root++) {
      double base = pow(A, 1.0/root);
      if (pow(round(base), root) == A)
        return 1;
      if (base &lt; 3)
        break;
    }
    return 0;
  }
</pre>

<p>This has got our run time down to about 0.53 seconds.</p>

<p>Can we do better? Well yes. Weâ€™ve not learnt our lesson and once again weâ€™ve fallen into the trap of micro-optimising too early. We need to back to the problem and attack it with some more maths. We can note that if we can find an integer <em>x</em> for some power <em>y</em>, then we can also find a solution for any factor of <em>y</em>. Reversing that, if any of the factors of <em>y</em> doesnâ€™t have an integral solution for <em>x</em> then thereâ€™s no solution for <em>y</em>. As an example, one million is 10<sup>6</sup>, but we donâ€™t need to find that out by taking the sixth root. Since six is two times three, there are also the solutions 100<sup>3</sup> and 1000<sup>2</sup>. Since we care only whether an answer exists, we can just look at prime values of <em>y</em>:</p>

<pre class="programlisting">
#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))

int root_primes(domain A)
{
  if (A == 2)
    return 0;
  /* Power of 2 or zero */
  if ((A &amp; (A - 1)) == 0)
    return 1;

  static const double primes[] = {
    2, 3, 5, 7, 11, 13, 17, 19
  }; /* Enough for uint32 */

  for(size_t i = 0; i &lt; ARRAY_SIZE(primes);
      i++) {
    double root = primes[i];
    double base = pow(A, 1.0/root);
    if (pow(round(base), root) == A)
      return 1;
    if (base &lt; 3)
      break;
  }

  return 0;
}
</pre>

<p>Instead of using floating point operations to try at most twenty-nine roots, weâ€™re only trying at most eight.</p>

<p>This has now got our run time down to about 0.23 seconds, a thousand times faster than the original micro-optimised code (for the mix of values in the test cases I was using).</p>

<p>This is as far as I have got in the optimisation. Perhaps other people can take it further.</p>

<p>We can look at the overall limit on the performance for the different approaches, mostly because no discussion of algorithms is complete without using Big O notation. The original approach was O(âˆš<em>N</em>). This approach is O(Ï€(log <em>N</em>)) where Ï€(<em>x</em>) is the number of primes less than or equal to <em>x</em>. This can be approximated to give an overall order of O(log <em>N</em>/log log <em>N</em>) but that limit becomes relevant only at extremely large <em>N</em>. Using <code>long int</code>s and increasing the maximum input value to sixty eight trillion needs only one more prime.</p>

<p>For comparison, a binary search of a table, as mentioned by Paul Floyd, is blindingly fast even just using <em>bsearch()</em>, ten milliseconds in my tests, which is probably below accuracy of my very dumb benchmarking. But as Paul correctly noted, it comes with the cost of a lot of memory. The size of the table scales roughly as the square root of the maximum value in the table. To extend it to sixty-eight trillion, increases the size of the table to over eight million entries.</p>

<p class="bio"><span class="author"><b>Roger Orr</b></span> Roger has been programming for over 20 years, most recently in C++ and Java for various investment banks in Canary Wharf and the City. He joined ACCU in 1999 and the BSI C++ panel in 2002.</p>
</p>
<p><strong>Notes:</strong>&nbsp;</p>
<p><em>More fields may be available via dynamicdata ..</em></p>
</div>
</channel>
</rss>
