Calculating large powers of 2 in decimal
Clash Royale CLAN TAG#URR8PPP
.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;
up vote
11
down vote
favorite
So, this is my problem to solve:
I want to calculate $2^n$ where $0 < n< 10000$
I am representing each element of array as a space where 4digit number should be "living" and if extra digit appears, I am replacing it to the next element of this array.
The principle I am using looks like this:
The code I am using is the following:
static string NotEfficient(int power)
if (power < 0)
throw new Exception("Power shouldn't be negative");
if (power == 0)
return "1";
if (power == 1)
return "2";
int A = new int[3750];
int current4Digit = 0;
//at first 2 is written in first element of array
A[current4Digit] = 2;
int currentPower = 1;
while (currentPower < power)
//multiply every 4digit by 2
for (int i = 0; i <= current4Digit; i++)
A[i] *= 2;
currentPower++;
//checking every 4digit if it
//contains 5 digit and if yes remove and
//put it in next 4digit
for (int i = 0; i <= current4Digit; i++)
if (A[i] / 10000 > 0)
int more = A[i] / 10000;
A[i] = A[i] % 10000;
A[i + 1] += more;
//if new digit should be opened
if (i + 1 > current4Digit)
current4Digit++;
//getting data from array to generate answer
string answer = "";
for (int i = current4Digit; i >= 0; i--)
answer += A[i].ToString().PadLeft(4,'0') + ",";
return return answer.TrimStart('0');
c# algorithm
add a comment |Â
up vote
11
down vote
favorite
So, this is my problem to solve:
I want to calculate $2^n$ where $0 < n< 10000$
I am representing each element of array as a space where 4digit number should be "living" and if extra digit appears, I am replacing it to the next element of this array.
The principle I am using looks like this:
The code I am using is the following:
static string NotEfficient(int power)
if (power < 0)
throw new Exception("Power shouldn't be negative");
if (power == 0)
return "1";
if (power == 1)
return "2";
int A = new int[3750];
int current4Digit = 0;
//at first 2 is written in first element of array
A[current4Digit] = 2;
int currentPower = 1;
while (currentPower < power)
//multiply every 4digit by 2
for (int i = 0; i <= current4Digit; i++)
A[i] *= 2;
currentPower++;
//checking every 4digit if it
//contains 5 digit and if yes remove and
//put it in next 4digit
for (int i = 0; i <= current4Digit; i++)
if (A[i] / 10000 > 0)
int more = A[i] / 10000;
A[i] = A[i] % 10000;
A[i + 1] += more;
//if new digit should be opened
if (i + 1 > current4Digit)
current4Digit++;
//getting data from array to generate answer
string answer = "";
for (int i = current4Digit; i >= 0; i--)
answer += A[i].ToString().PadLeft(4,'0') + ",";
return return answer.TrimStart('0');
c# algorithm
2
reminds me of Calculate 2â¿ where 0<n<10000 - what are you looking for in a code review, what is to be the measure forbetter
?
â greybeard
Jun 5 at 22:24
add a comment |Â
up vote
11
down vote
favorite
up vote
11
down vote
favorite
So, this is my problem to solve:
I want to calculate $2^n$ where $0 < n< 10000$
I am representing each element of array as a space where 4digit number should be "living" and if extra digit appears, I am replacing it to the next element of this array.
The principle I am using looks like this:
The code I am using is the following:
static string NotEfficient(int power)
if (power < 0)
throw new Exception("Power shouldn't be negative");
if (power == 0)
return "1";
if (power == 1)
return "2";
int A = new int[3750];
int current4Digit = 0;
//at first 2 is written in first element of array
A[current4Digit] = 2;
int currentPower = 1;
while (currentPower < power)
//multiply every 4digit by 2
for (int i = 0; i <= current4Digit; i++)
A[i] *= 2;
currentPower++;
//checking every 4digit if it
//contains 5 digit and if yes remove and
//put it in next 4digit
for (int i = 0; i <= current4Digit; i++)
if (A[i] / 10000 > 0)
int more = A[i] / 10000;
A[i] = A[i] % 10000;
A[i + 1] += more;
//if new digit should be opened
if (i + 1 > current4Digit)
current4Digit++;
//getting data from array to generate answer
string answer = "";
for (int i = current4Digit; i >= 0; i--)
answer += A[i].ToString().PadLeft(4,'0') + ",";
return return answer.TrimStart('0');
c# algorithm
So, this is my problem to solve:
I want to calculate $2^n$ where $0 < n< 10000$
I am representing each element of array as a space where 4digit number should be "living" and if extra digit appears, I am replacing it to the next element of this array.
The principle I am using looks like this:
The code I am using is the following:
static string NotEfficient(int power)
if (power < 0)
throw new Exception("Power shouldn't be negative");
if (power == 0)
return "1";
if (power == 1)
return "2";
int A = new int[3750];
int current4Digit = 0;
//at first 2 is written in first element of array
A[current4Digit] = 2;
int currentPower = 1;
while (currentPower < power)
//multiply every 4digit by 2
for (int i = 0; i <= current4Digit; i++)
A[i] *= 2;
currentPower++;
//checking every 4digit if it
//contains 5 digit and if yes remove and
//put it in next 4digit
for (int i = 0; i <= current4Digit; i++)
if (A[i] / 10000 > 0)
int more = A[i] / 10000;
A[i] = A[i] % 10000;
A[i + 1] += more;
//if new digit should be opened
if (i + 1 > current4Digit)
current4Digit++;
//getting data from array to generate answer
string answer = "";
for (int i = current4Digit; i >= 0; i--)
answer += A[i].ToString().PadLeft(4,'0') + ",";
return return answer.TrimStart('0');
c# algorithm
edited Jun 5 at 23:45
rolflâ¦
90.2k13186390
90.2k13186390
asked Jun 5 at 22:01
Beginner
896
896
2
reminds me of Calculate 2â¿ where 0<n<10000 - what are you looking for in a code review, what is to be the measure forbetter
?
â greybeard
Jun 5 at 22:24
add a comment |Â
2
reminds me of Calculate 2â¿ where 0<n<10000 - what are you looking for in a code review, what is to be the measure forbetter
?
â greybeard
Jun 5 at 22:24
2
2
reminds me of Calculate 2â¿ where 0<n<10000 - what are you looking for in a code review, what is to be the measure for
better
?â greybeard
Jun 5 at 22:24
reminds me of Calculate 2â¿ where 0<n<10000 - what are you looking for in a code review, what is to be the measure for
better
?â greybeard
Jun 5 at 22:24
add a comment |Â
1 Answer
1
active
oldest
votes
up vote
25
down vote
accepted
How I can make this algorithm better?
Here's how I would make your algorithm better.
First: your principle is sound. You are displaying a large binary number in base 10 by taking advantage of the fact that int
already has a method that displays a 32 bit number in base 10. Essentially your method is:
- convert the number to base 10000, by making an int array "digits" from 0 to 9999.
- Make a device which can double one of those numbers
- Print the result after the given number of doublings.
This technique works, but your implementation could use some improvements. The improvements I would make are:
- Go big! Why base 10000? You could do base 100000000 in an int no problem or base 1000000000000000000 in a long. Let's do that.
- You allocate a huge number of ints. That's probably either way too many or way too few for the problem at hand. Don't limit yourself. Make a solution that grows as you need it, not one that has an arbitrary limit. Moreover: you make 3750 ints, but you could do the entire problem with only 167 longs.
- Abstract away your operations into a type that represents the value
- Refactor each part of the algorithm into a helper method that does one thing well.
- Stop using mutable state. It's OK to burn a little memory. The garbage collector will deal.
- Use a more efficient algorithm to calculate big powers.
How might we put these principles into practice? Let's try.
First off, I'm going to make a struct which represents a number encoded in base 1000000000000000000. Its implementation will be a list of longs, but it could be any collection type. Lists are just convenient.
(Aside: Why a struct? The implementation is small; it's a wrapper around a single reference. The meaning of the struct is logically a value. It is immutable. So this is a good candidate for a value type. We get the benefits of having a clean interface and implementation hiding, but we only pay the price of a single reference.)
struct Base1000000000000000000
{
private List<long> digits;
private Base1000000000000000000(List<long> digits)
this.digits = digits;
Note that so far everything is private. I want this to have a very locked-down interface. We've got to get this party started somewhere, so let's represent one:
public static readonly Base1000000000000000000 One =
new Base1000000000000000000(new List<long> 1L );
You could do Zero
similarly.
Now what does our display algorithm look like? That's a one-liner:
public override string ToString() =>
string.Join("",
((IEnumerable<long>)digits)
.Reverse()
.Select(d => d.ToString().PadLeft(18, '0')));
Note that we're using the non-destructive reverse, not the destructive reverse on list.
The core of the algorithm is in our adder, which takes a number and returns its double:
public Base1000000000000000000 DoubleIt()
// Note, _ separators in constants are new for C# 7.
const long TheBase = 1_000_000_000_000_000_000;
var result = new List<long>();
result.Add(0);
for (int i = 0; i < this.digits.Count; i += 1)
// Make sure we have storage.
if (i == result.Count)
result.Add(0);
result[i] += this.digits[i] + this.digits[i];
// We might have to carry.
if (result[i] >= TheBase)
// Again, make sure we have room.
if (i + 1 == result.Count)
result.Add(0);
result[i+1] = result[i] / TheBase;
result[i] -= TheBase;
// And we're done.
return new Base1000000000000000000(result);
Super. Now the main algorithm is straightforward:
Base1000000000000000000 c = Base1000000000000000000.One;
for (int i = 0; i < n; ++i)
c = c.DoubleIt();
return c.ToString();
Notice how when I extract each operation to its own method, the code becomes simpler and more elegant.
Now, how can you improve this further?
Exercise: (Easy) Implementoperator+
on this type.
Exercise: (Pretty Easy) Implementoperator-
on this type, and represent negative numbers correctly.
Exercise: (Hard) Implementoperator*
on this type.
Exercise: (Very hard) Now that you have multiplication and addition, you can get to 2n much faster. Suppose you want to do 2384. Start withOne
. Double it. Now you have 21. Multiply it by itself -- square it. Now you have 22. Square it again to get 24. Square it again to get 28. And so on, until you have them up to 2256. Multiply 2256 by 2128 to get 2384, and you're done. Your original algorithm does 384 doublings, but you can get the answer with one doubling and nine multiplies. Can you work out the algorithm for the optimal sequence of multiplications and additions?
Exercise (Very hard): Implement%
and/
on this type.
Exercise (Very very hard): Now do it all over again without using any built-in type for the math. (Obviously usestring
for the text.) You are leveraging the ability ofint
orlong
to represent a bitfield that can be displayed in base 10. What if you didn't have that? Because someone had to write that code. The person who wrote that code didn't have it because it hadn't been written yet. Suppose C# didn't have any integer type; how would you implement math from scratch?
10
Very small nitpick: I think this would be more readable if instead of1000000000000000000L
you'd have a variable with this value and write it using digit separators:1000_000_000_000_000_000L
.
â germi
Jun 6 at 8:10
To be fair that's new to C#7, he might target lower version.
â Zikato
Jun 6 at 13:23
1
@germi: That's a good suggestion, thanks. I would put the value in a constant and not a variable.
â Eric Lippert
Jun 6 at 13:57
@EricLippert even better ;)
â germi
Jun 6 at 14:59
add a comment |Â
1 Answer
1
active
oldest
votes
1 Answer
1
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
25
down vote
accepted
How I can make this algorithm better?
Here's how I would make your algorithm better.
First: your principle is sound. You are displaying a large binary number in base 10 by taking advantage of the fact that int
already has a method that displays a 32 bit number in base 10. Essentially your method is:
- convert the number to base 10000, by making an int array "digits" from 0 to 9999.
- Make a device which can double one of those numbers
- Print the result after the given number of doublings.
This technique works, but your implementation could use some improvements. The improvements I would make are:
- Go big! Why base 10000? You could do base 100000000 in an int no problem or base 1000000000000000000 in a long. Let's do that.
- You allocate a huge number of ints. That's probably either way too many or way too few for the problem at hand. Don't limit yourself. Make a solution that grows as you need it, not one that has an arbitrary limit. Moreover: you make 3750 ints, but you could do the entire problem with only 167 longs.
- Abstract away your operations into a type that represents the value
- Refactor each part of the algorithm into a helper method that does one thing well.
- Stop using mutable state. It's OK to burn a little memory. The garbage collector will deal.
- Use a more efficient algorithm to calculate big powers.
How might we put these principles into practice? Let's try.
First off, I'm going to make a struct which represents a number encoded in base 1000000000000000000. Its implementation will be a list of longs, but it could be any collection type. Lists are just convenient.
(Aside: Why a struct? The implementation is small; it's a wrapper around a single reference. The meaning of the struct is logically a value. It is immutable. So this is a good candidate for a value type. We get the benefits of having a clean interface and implementation hiding, but we only pay the price of a single reference.)
struct Base1000000000000000000
{
private List<long> digits;
private Base1000000000000000000(List<long> digits)
this.digits = digits;
Note that so far everything is private. I want this to have a very locked-down interface. We've got to get this party started somewhere, so let's represent one:
public static readonly Base1000000000000000000 One =
new Base1000000000000000000(new List<long> 1L );
You could do Zero
similarly.
Now what does our display algorithm look like? That's a one-liner:
public override string ToString() =>
string.Join("",
((IEnumerable<long>)digits)
.Reverse()
.Select(d => d.ToString().PadLeft(18, '0')));
Note that we're using the non-destructive reverse, not the destructive reverse on list.
The core of the algorithm is in our adder, which takes a number and returns its double:
public Base1000000000000000000 DoubleIt()
// Note, _ separators in constants are new for C# 7.
const long TheBase = 1_000_000_000_000_000_000;
var result = new List<long>();
result.Add(0);
for (int i = 0; i < this.digits.Count; i += 1)
// Make sure we have storage.
if (i == result.Count)
result.Add(0);
result[i] += this.digits[i] + this.digits[i];
// We might have to carry.
if (result[i] >= TheBase)
// Again, make sure we have room.
if (i + 1 == result.Count)
result.Add(0);
result[i+1] = result[i] / TheBase;
result[i] -= TheBase;
// And we're done.
return new Base1000000000000000000(result);
Super. Now the main algorithm is straightforward:
Base1000000000000000000 c = Base1000000000000000000.One;
for (int i = 0; i < n; ++i)
c = c.DoubleIt();
return c.ToString();
Notice how when I extract each operation to its own method, the code becomes simpler and more elegant.
Now, how can you improve this further?
Exercise: (Easy) Implementoperator+
on this type.
Exercise: (Pretty Easy) Implementoperator-
on this type, and represent negative numbers correctly.
Exercise: (Hard) Implementoperator*
on this type.
Exercise: (Very hard) Now that you have multiplication and addition, you can get to 2n much faster. Suppose you want to do 2384. Start withOne
. Double it. Now you have 21. Multiply it by itself -- square it. Now you have 22. Square it again to get 24. Square it again to get 28. And so on, until you have them up to 2256. Multiply 2256 by 2128 to get 2384, and you're done. Your original algorithm does 384 doublings, but you can get the answer with one doubling and nine multiplies. Can you work out the algorithm for the optimal sequence of multiplications and additions?
Exercise (Very hard): Implement%
and/
on this type.
Exercise (Very very hard): Now do it all over again without using any built-in type for the math. (Obviously usestring
for the text.) You are leveraging the ability ofint
orlong
to represent a bitfield that can be displayed in base 10. What if you didn't have that? Because someone had to write that code. The person who wrote that code didn't have it because it hadn't been written yet. Suppose C# didn't have any integer type; how would you implement math from scratch?
10
Very small nitpick: I think this would be more readable if instead of1000000000000000000L
you'd have a variable with this value and write it using digit separators:1000_000_000_000_000_000L
.
â germi
Jun 6 at 8:10
To be fair that's new to C#7, he might target lower version.
â Zikato
Jun 6 at 13:23
1
@germi: That's a good suggestion, thanks. I would put the value in a constant and not a variable.
â Eric Lippert
Jun 6 at 13:57
@EricLippert even better ;)
â germi
Jun 6 at 14:59
add a comment |Â
up vote
25
down vote
accepted
How I can make this algorithm better?
Here's how I would make your algorithm better.
First: your principle is sound. You are displaying a large binary number in base 10 by taking advantage of the fact that int
already has a method that displays a 32 bit number in base 10. Essentially your method is:
- convert the number to base 10000, by making an int array "digits" from 0 to 9999.
- Make a device which can double one of those numbers
- Print the result after the given number of doublings.
This technique works, but your implementation could use some improvements. The improvements I would make are:
- Go big! Why base 10000? You could do base 100000000 in an int no problem or base 1000000000000000000 in a long. Let's do that.
- You allocate a huge number of ints. That's probably either way too many or way too few for the problem at hand. Don't limit yourself. Make a solution that grows as you need it, not one that has an arbitrary limit. Moreover: you make 3750 ints, but you could do the entire problem with only 167 longs.
- Abstract away your operations into a type that represents the value
- Refactor each part of the algorithm into a helper method that does one thing well.
- Stop using mutable state. It's OK to burn a little memory. The garbage collector will deal.
- Use a more efficient algorithm to calculate big powers.
How might we put these principles into practice? Let's try.
First off, I'm going to make a struct which represents a number encoded in base 1000000000000000000. Its implementation will be a list of longs, but it could be any collection type. Lists are just convenient.
(Aside: Why a struct? The implementation is small; it's a wrapper around a single reference. The meaning of the struct is logically a value. It is immutable. So this is a good candidate for a value type. We get the benefits of having a clean interface and implementation hiding, but we only pay the price of a single reference.)
struct Base1000000000000000000
{
private List<long> digits;
private Base1000000000000000000(List<long> digits)
this.digits = digits;
Note that so far everything is private. I want this to have a very locked-down interface. We've got to get this party started somewhere, so let's represent one:
public static readonly Base1000000000000000000 One =
new Base1000000000000000000(new List<long> 1L );
You could do Zero
similarly.
Now what does our display algorithm look like? That's a one-liner:
public override string ToString() =>
string.Join("",
((IEnumerable<long>)digits)
.Reverse()
.Select(d => d.ToString().PadLeft(18, '0')));
Note that we're using the non-destructive reverse, not the destructive reverse on list.
The core of the algorithm is in our adder, which takes a number and returns its double:
public Base1000000000000000000 DoubleIt()
// Note, _ separators in constants are new for C# 7.
const long TheBase = 1_000_000_000_000_000_000;
var result = new List<long>();
result.Add(0);
for (int i = 0; i < this.digits.Count; i += 1)
// Make sure we have storage.
if (i == result.Count)
result.Add(0);
result[i] += this.digits[i] + this.digits[i];
// We might have to carry.
if (result[i] >= TheBase)
// Again, make sure we have room.
if (i + 1 == result.Count)
result.Add(0);
result[i+1] = result[i] / TheBase;
result[i] -= TheBase;
// And we're done.
return new Base1000000000000000000(result);
Super. Now the main algorithm is straightforward:
Base1000000000000000000 c = Base1000000000000000000.One;
for (int i = 0; i < n; ++i)
c = c.DoubleIt();
return c.ToString();
Notice how when I extract each operation to its own method, the code becomes simpler and more elegant.
Now, how can you improve this further?
Exercise: (Easy) Implementoperator+
on this type.
Exercise: (Pretty Easy) Implementoperator-
on this type, and represent negative numbers correctly.
Exercise: (Hard) Implementoperator*
on this type.
Exercise: (Very hard) Now that you have multiplication and addition, you can get to 2n much faster. Suppose you want to do 2384. Start withOne
. Double it. Now you have 21. Multiply it by itself -- square it. Now you have 22. Square it again to get 24. Square it again to get 28. And so on, until you have them up to 2256. Multiply 2256 by 2128 to get 2384, and you're done. Your original algorithm does 384 doublings, but you can get the answer with one doubling and nine multiplies. Can you work out the algorithm for the optimal sequence of multiplications and additions?
Exercise (Very hard): Implement%
and/
on this type.
Exercise (Very very hard): Now do it all over again without using any built-in type for the math. (Obviously usestring
for the text.) You are leveraging the ability ofint
orlong
to represent a bitfield that can be displayed in base 10. What if you didn't have that? Because someone had to write that code. The person who wrote that code didn't have it because it hadn't been written yet. Suppose C# didn't have any integer type; how would you implement math from scratch?
10
Very small nitpick: I think this would be more readable if instead of1000000000000000000L
you'd have a variable with this value and write it using digit separators:1000_000_000_000_000_000L
.
â germi
Jun 6 at 8:10
To be fair that's new to C#7, he might target lower version.
â Zikato
Jun 6 at 13:23
1
@germi: That's a good suggestion, thanks. I would put the value in a constant and not a variable.
â Eric Lippert
Jun 6 at 13:57
@EricLippert even better ;)
â germi
Jun 6 at 14:59
add a comment |Â
up vote
25
down vote
accepted
up vote
25
down vote
accepted
How I can make this algorithm better?
Here's how I would make your algorithm better.
First: your principle is sound. You are displaying a large binary number in base 10 by taking advantage of the fact that int
already has a method that displays a 32 bit number in base 10. Essentially your method is:
- convert the number to base 10000, by making an int array "digits" from 0 to 9999.
- Make a device which can double one of those numbers
- Print the result after the given number of doublings.
This technique works, but your implementation could use some improvements. The improvements I would make are:
- Go big! Why base 10000? You could do base 100000000 in an int no problem or base 1000000000000000000 in a long. Let's do that.
- You allocate a huge number of ints. That's probably either way too many or way too few for the problem at hand. Don't limit yourself. Make a solution that grows as you need it, not one that has an arbitrary limit. Moreover: you make 3750 ints, but you could do the entire problem with only 167 longs.
- Abstract away your operations into a type that represents the value
- Refactor each part of the algorithm into a helper method that does one thing well.
- Stop using mutable state. It's OK to burn a little memory. The garbage collector will deal.
- Use a more efficient algorithm to calculate big powers.
How might we put these principles into practice? Let's try.
First off, I'm going to make a struct which represents a number encoded in base 1000000000000000000. Its implementation will be a list of longs, but it could be any collection type. Lists are just convenient.
(Aside: Why a struct? The implementation is small; it's a wrapper around a single reference. The meaning of the struct is logically a value. It is immutable. So this is a good candidate for a value type. We get the benefits of having a clean interface and implementation hiding, but we only pay the price of a single reference.)
struct Base1000000000000000000
{
private List<long> digits;
private Base1000000000000000000(List<long> digits)
this.digits = digits;
Note that so far everything is private. I want this to have a very locked-down interface. We've got to get this party started somewhere, so let's represent one:
public static readonly Base1000000000000000000 One =
new Base1000000000000000000(new List<long> 1L );
You could do Zero
similarly.
Now what does our display algorithm look like? That's a one-liner:
public override string ToString() =>
string.Join("",
((IEnumerable<long>)digits)
.Reverse()
.Select(d => d.ToString().PadLeft(18, '0')));
Note that we're using the non-destructive reverse, not the destructive reverse on list.
The core of the algorithm is in our adder, which takes a number and returns its double:
public Base1000000000000000000 DoubleIt()
// Note, _ separators in constants are new for C# 7.
const long TheBase = 1_000_000_000_000_000_000;
var result = new List<long>();
result.Add(0);
for (int i = 0; i < this.digits.Count; i += 1)
// Make sure we have storage.
if (i == result.Count)
result.Add(0);
result[i] += this.digits[i] + this.digits[i];
// We might have to carry.
if (result[i] >= TheBase)
// Again, make sure we have room.
if (i + 1 == result.Count)
result.Add(0);
result[i+1] = result[i] / TheBase;
result[i] -= TheBase;
// And we're done.
return new Base1000000000000000000(result);
Super. Now the main algorithm is straightforward:
Base1000000000000000000 c = Base1000000000000000000.One;
for (int i = 0; i < n; ++i)
c = c.DoubleIt();
return c.ToString();
Notice how when I extract each operation to its own method, the code becomes simpler and more elegant.
Now, how can you improve this further?
Exercise: (Easy) Implementoperator+
on this type.
Exercise: (Pretty Easy) Implementoperator-
on this type, and represent negative numbers correctly.
Exercise: (Hard) Implementoperator*
on this type.
Exercise: (Very hard) Now that you have multiplication and addition, you can get to 2n much faster. Suppose you want to do 2384. Start withOne
. Double it. Now you have 21. Multiply it by itself -- square it. Now you have 22. Square it again to get 24. Square it again to get 28. And so on, until you have them up to 2256. Multiply 2256 by 2128 to get 2384, and you're done. Your original algorithm does 384 doublings, but you can get the answer with one doubling and nine multiplies. Can you work out the algorithm for the optimal sequence of multiplications and additions?
Exercise (Very hard): Implement%
and/
on this type.
Exercise (Very very hard): Now do it all over again without using any built-in type for the math. (Obviously usestring
for the text.) You are leveraging the ability ofint
orlong
to represent a bitfield that can be displayed in base 10. What if you didn't have that? Because someone had to write that code. The person who wrote that code didn't have it because it hadn't been written yet. Suppose C# didn't have any integer type; how would you implement math from scratch?
How I can make this algorithm better?
Here's how I would make your algorithm better.
First: your principle is sound. You are displaying a large binary number in base 10 by taking advantage of the fact that int
already has a method that displays a 32 bit number in base 10. Essentially your method is:
- convert the number to base 10000, by making an int array "digits" from 0 to 9999.
- Make a device which can double one of those numbers
- Print the result after the given number of doublings.
This technique works, but your implementation could use some improvements. The improvements I would make are:
- Go big! Why base 10000? You could do base 100000000 in an int no problem or base 1000000000000000000 in a long. Let's do that.
- You allocate a huge number of ints. That's probably either way too many or way too few for the problem at hand. Don't limit yourself. Make a solution that grows as you need it, not one that has an arbitrary limit. Moreover: you make 3750 ints, but you could do the entire problem with only 167 longs.
- Abstract away your operations into a type that represents the value
- Refactor each part of the algorithm into a helper method that does one thing well.
- Stop using mutable state. It's OK to burn a little memory. The garbage collector will deal.
- Use a more efficient algorithm to calculate big powers.
How might we put these principles into practice? Let's try.
First off, I'm going to make a struct which represents a number encoded in base 1000000000000000000. Its implementation will be a list of longs, but it could be any collection type. Lists are just convenient.
(Aside: Why a struct? The implementation is small; it's a wrapper around a single reference. The meaning of the struct is logically a value. It is immutable. So this is a good candidate for a value type. We get the benefits of having a clean interface and implementation hiding, but we only pay the price of a single reference.)
struct Base1000000000000000000
{
private List<long> digits;
private Base1000000000000000000(List<long> digits)
this.digits = digits;
Note that so far everything is private. I want this to have a very locked-down interface. We've got to get this party started somewhere, so let's represent one:
public static readonly Base1000000000000000000 One =
new Base1000000000000000000(new List<long> 1L );
You could do Zero
similarly.
Now what does our display algorithm look like? That's a one-liner:
public override string ToString() =>
string.Join("",
((IEnumerable<long>)digits)
.Reverse()
.Select(d => d.ToString().PadLeft(18, '0')));
Note that we're using the non-destructive reverse, not the destructive reverse on list.
The core of the algorithm is in our adder, which takes a number and returns its double:
public Base1000000000000000000 DoubleIt()
// Note, _ separators in constants are new for C# 7.
const long TheBase = 1_000_000_000_000_000_000;
var result = new List<long>();
result.Add(0);
for (int i = 0; i < this.digits.Count; i += 1)
// Make sure we have storage.
if (i == result.Count)
result.Add(0);
result[i] += this.digits[i] + this.digits[i];
// We might have to carry.
if (result[i] >= TheBase)
// Again, make sure we have room.
if (i + 1 == result.Count)
result.Add(0);
result[i+1] = result[i] / TheBase;
result[i] -= TheBase;
// And we're done.
return new Base1000000000000000000(result);
Super. Now the main algorithm is straightforward:
Base1000000000000000000 c = Base1000000000000000000.One;
for (int i = 0; i < n; ++i)
c = c.DoubleIt();
return c.ToString();
Notice how when I extract each operation to its own method, the code becomes simpler and more elegant.
Now, how can you improve this further?
Exercise: (Easy) Implementoperator+
on this type.
Exercise: (Pretty Easy) Implementoperator-
on this type, and represent negative numbers correctly.
Exercise: (Hard) Implementoperator*
on this type.
Exercise: (Very hard) Now that you have multiplication and addition, you can get to 2n much faster. Suppose you want to do 2384. Start withOne
. Double it. Now you have 21. Multiply it by itself -- square it. Now you have 22. Square it again to get 24. Square it again to get 28. And so on, until you have them up to 2256. Multiply 2256 by 2128 to get 2384, and you're done. Your original algorithm does 384 doublings, but you can get the answer with one doubling and nine multiplies. Can you work out the algorithm for the optimal sequence of multiplications and additions?
Exercise (Very hard): Implement%
and/
on this type.
Exercise (Very very hard): Now do it all over again without using any built-in type for the math. (Obviously usestring
for the text.) You are leveraging the ability ofint
orlong
to represent a bitfield that can be displayed in base 10. What if you didn't have that? Because someone had to write that code. The person who wrote that code didn't have it because it hadn't been written yet. Suppose C# didn't have any integer type; how would you implement math from scratch?
edited Jun 6 at 14:03
answered Jun 5 at 22:34
Eric Lippert
11.8k32444
11.8k32444
10
Very small nitpick: I think this would be more readable if instead of1000000000000000000L
you'd have a variable with this value and write it using digit separators:1000_000_000_000_000_000L
.
â germi
Jun 6 at 8:10
To be fair that's new to C#7, he might target lower version.
â Zikato
Jun 6 at 13:23
1
@germi: That's a good suggestion, thanks. I would put the value in a constant and not a variable.
â Eric Lippert
Jun 6 at 13:57
@EricLippert even better ;)
â germi
Jun 6 at 14:59
add a comment |Â
10
Very small nitpick: I think this would be more readable if instead of1000000000000000000L
you'd have a variable with this value and write it using digit separators:1000_000_000_000_000_000L
.
â germi
Jun 6 at 8:10
To be fair that's new to C#7, he might target lower version.
â Zikato
Jun 6 at 13:23
1
@germi: That's a good suggestion, thanks. I would put the value in a constant and not a variable.
â Eric Lippert
Jun 6 at 13:57
@EricLippert even better ;)
â germi
Jun 6 at 14:59
10
10
Very small nitpick: I think this would be more readable if instead of
1000000000000000000L
you'd have a variable with this value and write it using digit separators: 1000_000_000_000_000_000L
.â germi
Jun 6 at 8:10
Very small nitpick: I think this would be more readable if instead of
1000000000000000000L
you'd have a variable with this value and write it using digit separators: 1000_000_000_000_000_000L
.â germi
Jun 6 at 8:10
To be fair that's new to C#7, he might target lower version.
â Zikato
Jun 6 at 13:23
To be fair that's new to C#7, he might target lower version.
â Zikato
Jun 6 at 13:23
1
1
@germi: That's a good suggestion, thanks. I would put the value in a constant and not a variable.
â Eric Lippert
Jun 6 at 13:57
@germi: That's a good suggestion, thanks. I would put the value in a constant and not a variable.
â Eric Lippert
Jun 6 at 13:57
@EricLippert even better ;)
â germi
Jun 6 at 14:59
@EricLippert even better ;)
â germi
Jun 6 at 14:59
add a comment |Â
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f195923%2fcalculating-large-powers-of-2-in-decimal%23new-answer', 'question_page');
);
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
2
reminds me of Calculate 2â¿ where 0<n<10000 - what are you looking for in a code review, what is to be the measure for
better
?â greybeard
Jun 5 at 22:24