I'm trying to optimize my screen sharing app. I've already used a few ways to make it faster and stable, such as only sending the deltas between two frames, and using Gzip to compress the data.
This is my client code:
private void Form1_Load(object sender, EventArgs e)
{
Thread th = new Thread(startSend);
th.Start();
}
private void startSend()
{
Bitmap curr;
Bitmap diff;
encoderParams.Param[0] = qualityParam;
Bitmap pre = screenshot();
bmpBytes = imageToByteArray(pre);
SendVarData(handler, bmpBytes);
while (true)
{
curr= screenshot();
diff= Difference(pre, curr);
bmpBytes = imageToByteArray(diff);
SendVarData(handler, bmpBytes);
pre = curr;
}
}
Screenshot:
public Bitmap screenshot()
{
Bitmap screenshot = new Bitmap(SystemInformation.VirtualScreen.Width,
SystemInformation.VirtualScreen.Height,
PixelFormat.Format24bppRgb);
Graphics screenGraph = Graphics.FromImage(screenshot);
screenGraph.CopyFromScreen(0,
0,
0,
0,
SystemInformation.VirtualScreen.Size,
CopyPixelOperation.SourceCopy);
return screenshot;
}
The Difference method:
public Bitmap Difference(Bitmap bmp0, Bitmap bmp1)
{
Bitmap bmp2;
int Bpp = 3;
bmp2 = new Bitmap(bmp0.Width, bmp0.Height, bmp0.PixelFormat);
var bmpData0 = bmp0.LockBits(
new Rectangle(0, 0, bmp0.Width, bmp0.Height),
ImageLockMode.ReadOnly, bmp0.PixelFormat);
var bmpData1 = bmp1.LockBits(
new Rectangle(0, 0, bmp1.Width, bmp1.Height),
ImageLockMode.ReadOnly, bmp1.PixelFormat);
var bmpData2 = bmp2.LockBits(
new Rectangle(0, 0, bmp2.Width, bmp2.Height),
ImageLockMode.ReadWrite, bmp2.PixelFormat);
bmp0.UnlockBits(bmpData0);
bmp1.UnlockBits(bmpData1);
bmp2.UnlockBits(bmpData2);
int len = bmpData0.Height * bmpData0.Stride;
// MessageBox.Show(bmpData0.Stride.ToString());
bool changed=false;
byte[] data0 = new byte[len];
byte[] data1 = new byte[len];
byte[] data2 = new byte[len];
Marshal.Copy(bmpData0.Scan0, data0, 0, len);
Marshal.Copy(bmpData1.Scan0, data1, 0, len);
Marshal.Copy(bmpData2.Scan0, data2, 0, len);
for (int i = 0; i < len; i += Bpp)
{
changed = ((data0[i] != data1[i])
|| (data0[i + 1] != data1[i + 1])
|| (data0[i + 2] != data1[i + 2]));
// this.Invoke(new Action(() => this.Text = changed.ToString()));
data2[i] = changed ? data1[i] : (byte)2; // special markers
data2[i + 1] = changed ? data1[i + 1] : (byte)3; // special markers
data2[i + 2] = changed ? data1[i + 2] : (byte)7; // special markers
if (Bpp == 4) data2[i + 3] =
changed ? (byte)255 : (byte)42; // special markers
}
// this.Invoke(new Action(() => this.Text = changed.ToString()));
Marshal.Copy(data2, 0, bmpData2.Scan0, len);
return bmp2;
}
and the SendVarData function:
int total = 0;
byte[] datasize;
private int SendVarData(Socket s, byte[] data)
{
total = 0;
int size = data.Length;
int dataleft = size;
int sent;
datasize = BitConverter.GetBytes(size);
sent = s.Send(datasize);
sent = s.Send(data, total, dataleft, SocketFlags.None);
total += sent;
dataleft -= sent;
// MessageBox.Show("D");
return total;
}
This is the server - I'm just receiving a full image in the beginning, and then just deltas:
public void startListening()
{
prev = byteArrayToImage(ReceiveVarData(client.Client));
theImage.Image = prev;
while (true)
{
data = ReceiveVarData(client.Client);
curr = byteArrayToImage(data) ;
merge = Merge(prev, curr);
theImage.Image = merge;
count++;
prev = merge;
}
}
public static Bitmap Merge(Bitmap bmp0, Bitmap bmp1)
{
int Bpp = 3;
Bitmap bmp2 = new Bitmap(bmp0.Width, bmp0.Height, bmp0.PixelFormat);
var bmpData0 = bmp0.LockBits(
new System.Drawing.Rectangle(0, 0, bmp0.Width, bmp0.Height),
ImageLockMode.ReadOnly, bmp0.PixelFormat);
var bmpData1 = bmp1.LockBits(
new System.Drawing.Rectangle(0, 0, bmp1.Width, bmp1.Height),
ImageLockMode.ReadOnly, bmp1.PixelFormat);
var bmpData2 = bmp2.LockBits(
new System.Drawing.Rectangle(0, 0, bmp2.Width, bmp2.Height),
ImageLockMode.ReadWrite, bmp2.PixelFormat);
bmp0.UnlockBits(bmpData0);
bmp1.UnlockBits(bmpData1);
bmp2.UnlockBits(bmpData2);
int len = bmpData0.Height * bmpData0.Stride;
byte[] data0 = new byte[len];
byte[] data1 = new byte[len];
byte[] data2 = new byte[len];
Marshal.Copy(bmpData0.Scan0, data0, 0, len);
Marshal.Copy(bmpData1.Scan0, data1, 0, len);
Marshal.Copy(bmpData2.Scan0, data2, 0, len);
for (int i = 0; i < len; i += Bpp)
{
bool toberestored = (data1[i] != 2 && data1[i + 1] != 3 &&
data1[i + 2] != 7 && data1[i + 2] != 42);
if (toberestored)
{
data2[i] = data1[i];
data2[i + 1] = data1[i + 1];
data2[i + 2] = data1[i + 2];
if (Bpp == 4) data2[i + 3] = data1[i + 3];
}
else
{
data2[i] = data0[i];
data2[i + 1] = data0[i + 1];
data2[i + 2] = data0[i + 2];
if (Bpp == 4) data2[i + 3] = data0[i + 3];
}
}
Marshal.Copy(data2, 0, bmpData2.Scan0, len);
return bmp2;
}
I think it's coded fine, but I'm still unable to get more than 6~7fps (of 8kb-100kb) when running on 2 different computers with a fast and stable internet connection, and a maximum of 11fps when running both client and server on the same computer. I think it's because of the complexity of the delta and merging algorithms, but i dont know.
I would very appreciate if anyone could suggest how could optimize it further.
You may organize data2 in a different manner to send less data.
The below "compression" algorithm is very basic but will provide an improved compression compared to your implelmentation.
Search for differences, by identifying start and end of consecutive differences. When you find an interval where all pixels are different, store the length of identical data before that interval using 2 bytes, then store the number of consecutive differences, finally write 3 RGB bytes for each different pixel.
In case of 65535 different pixels, block the max interval length to 65535 and after storing the interval values. The next difference interval starting just after the stored interval, the identical count for next interval will be 0.
In case of 65535 identical pixels, just write $FFFF followed by $0000 indicating an empty sequence of different pixels.
Clarification: What I mean by "identifying start and end of consecutive differences" ?
In the above example, letters identify colors, i.e W(white), P(Pink), O(Orange):
The word "identical" refers to a comparison between data 0 and data1 (not comparing data1[i] with data1[i-1]).
Data0 = WWPWWOWOOOWWOOOPP
Data1 = WWPWPWP OOOWWPP OPP
You have 4 identical pixels (WWPW) followed by an interval (length 3) starting on the 4th character and ending on the 6th where all pixels are different. Then five identical pixels, followed by a new interval with 2 differences. At the end, a few common pixels.
The output for data2 will be (the text in parenthesis are not part of the buffer and explains the previous buffer values :
04 00 (4 identical pixels) 03 00 (3 different pixels coded in next 9 bytes)
Pr Pg Pb (3 bytes RGB code for P) Wr Wg Wb (RGB code for W) Pr Pg Pb (RGB code for P)
05 00 (5 identical pixels) 03 00 (2 different pixels coded in next 6 bytes)
Pr Pg Pb (RGB code for P) ...
The code to write differences would look like to the following.
I let you build the code adressing the opposite action, i.e. reading differences to update the previous image.
Data2Index = 0 ; // next index for additions in data2 ;
int idcount = 0 ;
int diffstart = -1 ;
int diffstart = -1 ;
for (int i = 0; i < len; i += Bpp)
{
changed = ((data0[i] != data1[i])
|| (data0[i + 1] != data1[i + 1])
|| (data0[i + 2] != data1[i + 2]));
if (!changed)
{
if (idcount==ushort.MaxValue)
{ // still identical, but there is a limitation on count
// write to data2 the identical count + differencecount equals to 0
AddIdCountDiffCountAndDifferences(idcount,0,0) ;
idcount = 0 ;
}
if (diffstart>0)
{ // after 0 or more identical values, a change was found
// write to data2 the identical count + difference count + different pixels
AddIdCountDiffCountAndDifferences(idcount,diffcount,diffstart) ;
idcount = 0 ;
diffcount= 0 ;
diffstart=-1 ;
}
else identicalcount++ ; // still identical, continue until difference found
}
else if (diffstart<0)
{ // a difference is found after a sequence of identical pixels, store the index of first difference
diffstart=i ; diffcount=1 ;
}
else
{ // different pixel follows another difference (and limitation not reached)
if (diffcount<ushort.MaxVakue) diffcount++ ;
}
else
{ // limitation reached, i.e. diffcount equals 65535
AddIdCountDiffCountAndDifferences(0,diffcount,diffstart) ;
diffstart+=diffcount ;
diffcount=0 ;
}
The procedure used to fill data2 here:
private int Data2Index = 0 ; // to be reset before
private void AddIdCountDiffCountAndDifferences(int idcount,int diffcount,int diffstart)
{
data2[Data2Index++]=(byte)(idcount && 0xFF) ; // low byte of the int
data2[Data2Index++]=(byte)(idcount >> 8 && 0xFF) ; // second byte of the int
data2[Data2Index++]=(byte)(diffcount && 0xFF) ; // low byte of the int
data2[Data2Index++]=(byte)(diffcount >> 8 && 0xFF) ; // second byte of the int
for (int i=0;i<diffcount;i++)
{
data2[Data2Index++]=data1[diffstart+Bpp*i ] ;
data2[Data2Index++]=data1[diffstart+Bpp*i+1] ;
data2[Data2Index++]=data1[diffstart+Bpp*i+2] ;
}
}
Related
I have an array of audio data, which is a lot of Int32 numbers represented by array of bytes (each 4 byte element represents an Int32) and i want to do some manipulation on the data (for example, add 10 to each Int32).
I converted the bytes to Int32, do the manipulation and convert it back to bytes as in this example:
//byte[] buffer;
for (int i=0; i<buffer.Length; i+=4)
{
Int32 temp0 = BitConverter.ToInt32(buffer, i);
temp0 += 10;
byte[] temp1 = BitConverter.GetBytes(temp0);
for (int j=0;j<4;j++)
{
buffer[i + j] = temp1[j];
}
}
But I would like to know if there is a better way to do such manipulation.
You can check the .NET Reference Source for pointers (grin) on how to convert from/to big endian.
class intFromBigEndianByteArray {
public byte[] b;
public int this[int i] {
get {
i <<= 2; // i *= 4; // optional
return (int)b[i] << 24 | (int)b[i + 1] << 16 | (int)b[i + 2] << 8 | b[i + 3];
}
set {
i <<= 2; // i *= 4; // optional
b[i ] = (byte)(value >> 24);
b[i + 1] = (byte)(value >> 16);
b[i + 2] = (byte)(value >> 8);
b[i + 3] = (byte)value;
}
}
}
and sample use:
byte[] buffer = { 127, 255, 255, 255, 255, 255, 255, 255 };//big endian { int.MaxValue, -1 }
//bool check = BitConverter.IsLittleEndian; // true
//int test = BitConverter.ToInt32(buffer, 0); // -129 (incorrect because little endian)
var fakeIntBuffer = new intFromBigEndianByteArray() { b = buffer };
fakeIntBuffer[0] += 2; // { 128, 0, 0, 1 } = big endian int.MinValue - 1
fakeIntBuffer[1] += 2; // { 0, 0, 0, 1 } = big endian 1
Debug.Print(string.Join(", ", buffer)); // "128, 0, 0, 0, 1, 0, 0, 1"
For better performance you can look into parallel processing and SIMD instructions - Using SSE in C#
For even better performance, you can look into Utilizing the GPU with c#
How about the following approach:
struct My
{
public int Int;
}
var bytes = Enumerable.Range(0, 20).Select(n => (byte)(n + 240)).ToArray();
foreach (var b in bytes) Console.Write("{0,-4}", b);
// Pin the managed memory
GCHandle handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
for (int i = 0; i < bytes.Length; i += 4)
{
// Copy the data
My my = (My)Marshal.PtrToStructure<My>(handle.AddrOfPinnedObject() + i);
my.Int += 10;
// Copy back
Marshal.StructureToPtr(my, handle.AddrOfPinnedObject() + i, true);
}
// Unpin
handle.Free();
foreach (var b in bytes) Console.Write("{0,-4}", b);
I made it just for fun.
Not sure that's less ugly.
I don't know, will it be faster? Test it.
I'm trying to parse a wav file. I'm not sure if there can be multiple data chunks in a wav file, but I originally assumed there was only 1 since the wav file format description I was reading only mentioned there being 1.
But I noticed that the subchunk2size was very small (like 26) when the wav file being parsed was something like 36MB and the sample rate was 44100.
So I tried to parse it assuming there were multiple chunks, but after the 1st chunk, there was no subchunk2id to be found.
To go chunk by chunk, I was using the below code
int chunkSize = System.BitConverter.ToInt32(strm, 40);
int widx = 44; //wav data starts at the 44th byte
//strm is a byte array of the wav file
while(widx < strm.Length)
{
widx += chunkSize;
if(widx < 1000)
{
//log "data" or "100 97 116 97" for the subchunkid
//This is only getting printed the 1st time though. All prints after that are garbage
Debug.Log( strm[widx] + " " + strm[widx+1] + " " + strm[widx+2] + " " + strm[widx+3]);
}
if(widx + 8 < strm.Length)
{
widx += 4;
chunkSize = System.BitConverter.ToInt32(strm, widx);
widx += 4;
}else
{
widx += 8;
}
}
A .wav-File has 3 chunks:
Each chunk has a size of 4 Byte
The first chunk is the "RIFF"-chunk. It includes 8 Byte the filesize(4 Byte) and the name of the format(4byte, usually "WAVE").
The next chunk is the "fmt "-chunk (the space in the chunk-name is important). It includes the audio-format(2 Byte), the number of channels (2 Byte), the sample rate (4 Byte), the byte rate (4 Byte), blockalign (2 Byte) and the bits per sample (2 Byte).
The third and last chunk is the data-chunk. Here are the real data and the amplitudes of the samples. It includes 4 Byte for the datasize, which is the number of bytes for the data.
You can find further explanations of the properties of a .wav-file here.
From this knowledge I have already created the following class:
public sealed class WaveFile
{
//privates
private int fileSize;
private string format;
private int fmtChunkSize;
private int audioFormat;
private int numChannels;
private int sampleRate;
private int byteRate;
private int blockAlign;
private int bitsPerSample;
private int dataSize;
private int[][] data;//One array per channel
//publics
public int FileSize => fileSize;
public string Format => format;
public int FmtChunkSize => fmtChunkSize;
public int AudioFormat => audioFormat;
public int NumChannels => numChannels;
public int SampleRate => sampleRate;
public int ByteRate => byteRate;
public int BitsPerSample => bitsPerSample;
public int DataSize => dataSize;
public int[][] Data => data;
public WaveFile(string path)
{
FileStream fs = File.OpenRead(path);
LoadChunk(fs); //read RIFF Chunk
LoadChunk(fs); //read fmt Chunk
LoadChunk(fs); //read data Chunk
fs.Close();
}
private void LoadChunk(FileStream fs)
{
ASCIIEncoding Encoder = new ASCIIEncoding();
byte[] bChunkID = new byte[4];
fs.Read(bChunkID, 0, 4);
string sChunkID = Encoder.GetString(bChunkID);
byte[] ChunkSize = new byte[4];
fs.Read(ChunkSize, 0, 4);
if (sChunkID.Equals("RIFF"))
{
fileSize = BitConverter.ToInt32(ChunkSize, 0);
byte[] Format = new byte[4];
fs.Read(Format, 0, 4);
this.format = Encoder.GetString(Format);
}
if (sChunkID.Equals("fmt "))
{
fmtChunkSize = BitConverter.ToInt32(ChunkSize, 0);
byte[] audioFormat = new byte[2];
fs.Read(audioFormat, 0, 2);
this.audioFormat = BitConverter.ToInt16(audioFormat, 0);
byte[] numChannels = new byte[2];
fs.Read(numChannels, 0, 2);
this.numChannels = BitConverter.ToInt16(numChannels, 0);
byte[] sampleRate = new byte[4];
fs.Read(sampleRate, 0, 4);
this.sampleRate = BitConverter.ToInt32(sampleRate, 0);
byte[] byteRate = new byte[4];
fs.Read(byteRate, 0, 4);
this.byteRate = BitConverter.ToInt32(byteRate, 0);
byte[] blockAlign = new byte[2];
fs.Read(blockAlign, 0, 2);
this.blockAlign = BitConverter.ToInt16(blockAlign, 0);
byte[] bitsPerSample = new byte[2];
fs.Read(bitsPerSample, 0, 2);
this.bitsPerSample = BitConverter.ToInt16(bitsPerSample, 0);
}
if (sChunkID.Equals("data"))
{
dataSize = BitConverter.ToInt32(ChunkSize, 0);
data = new int[this.numChannels][];
byte[] temp = new byte[dataSize];
for (int i = 0; i < this.numChannels; i++)
{
data[i] = new int[this.dataSize / (numChannels * bitsPerSample / 8)];
}
for (int i = 0; i < data[0].Length; i++)
{
for (int j = 0; j < numChannels; j++)
{
if (fs.Read(temp, 0, blockAlign / numChannels) > 0)
{
if (blockAlign / numChannels == 2)
{ data[j][i] = BitConverter.ToInt32(temp, 0); }
else
{ data[j][i] = BitConverter.ToInt16(temp, 0); }
}
}
}
}
}
}
Needed using-directives:
using System;
using System.IO;
using System.Text;
This class reads all chunks byte per byte and sets the properties. You just have to initialize this class and it will return all properties of your selected wave-file.
In the reference you added I dont see any mention of the chunk size being repeated for each data chunk...
Try something like this:
int chunkSize = System.BitConverter.ToInt32(strm, 40);
int widx = 44; //wav data starts at the 44th byte
//strm is a byte array of the wav file
while(widx < strm.Length)
{
if(widx < 1000)
{
//log "data" or "100 97 116 97" for the subchunkid
//This is only getting printed the 1st time though. All prints after that are garbage
Debug.Log( strm[widx] + " " + strm[widx+1] + " " + strm[widx+2] + " " + strm[widx+3]);
}
widx += chunkSize;
}
I am trying to implement a function which takes an wav file, runs a 100th of a second worth of audio through the FFT by AForge. When I change the offset to alter where in the audio I am computing through the FFT, sometimes I will get results in which I can show in my graph but most of the time I get a complex array of NaN's. Why could this be?
Here is my code.
public double[] test()
{
OpenFileDialog file = new OpenFileDialog();
file.ShowDialog();
WaveFileReader reader = new WaveFileReader(file.FileName);
byte[] data = new byte[reader.Length];
reader.Read(data, 0, data.Length);
samepleRate = reader.WaveFormat.SampleRate;
bitDepth = reader.WaveFormat.BitsPerSample;
channels = reader.WaveFormat.Channels;
Console.WriteLine("audio has " + channels + " channels, a sample rate of " + samepleRate + " and bitdepth of " + bitDepth + ".");
float[] floats = new float[data.Length / sizeof(float)];
Buffer.BlockCopy(data, 0, floats, 0, data.Length);
size = 2048;
int inputSamples = samepleRate / 100;
int offset = samepleRate * 15 * channels;
int y = 0;
Complex[] complexData = new Complex[size];
float[] window = CalcWindowFunction(inputSamples);
for (int i = 0; i < inputSamples; i++)
{
complexData[y] = new Complex(floats[i * channels + offset] * window[i], 0);
y++;
}
while (y < size)
{
complexData[y] = new Complex(0, 0);
y++;
}
FourierTransform.FFT(complexData, FourierTransform.Direction.Forward);
double[] arr = new double[complexData.Length];
for (int i = 0; i < complexData.Length; i++)
{
arr[i] = complexData[i].Magnitude;
}
Console.Write("complete, ");
return arr;
}
private float[] CalcWindowFunction(int inputSamples)
{
float[] arr = new float[size];
for(int i =0; i<size;i++){
arr[i] = 1;
}
return arr;
}
A complex array of NaNs is usually the result of one of the inputs to the FFT being a NaN. To debug, you might check all the values in the input array before the FFT to make sure they are within some valid range, given the audio input scaling.
I had a question related to the use of Lockbits method in C#..
I have a 1bpp Image and I'm trying to access all the pixels of the image but some are still left out.
public Bitmap Pixels(Bitmap original)
{
Rectangle rect = new Rectangle(0, 0, original.Width, original.Height);
BitmapData bimgData = original.LockBits(rect, ImageLockMode.ReadWrite, original.PixelFormat);
IntPtr ptr = bimgData.Scan0;
int bytes = bimgData.Stride * bimg.Height;
byte[] Values = new byte[bytes];
Marshal.Copy(ptr, Values, 0, bytes);
int Val;
int stride = bimgData.Stride;
for (int column = 0; column < bimgData.Height; column = column + 1)
for (int row = 0; row < bimgData.Width; row = row +1)
{
c = column;
r = row;
for (int t = 0; t < 8; t++)
{
Val = Values[((c) * stride) + ((r) / 8)] & 2 ^ t;
if (Val == 0)
Values[((c) * stride) + ((r) / 8)] = (byte)(Values[((c) * stride) + ((r) / 8)] + 2 ^ t);
}
}
Marshal.Copy(Values, 0, ptr, bytes);
original.UnlockBits(bimgData);
return original;
}
This code should turn all the pixels white
Val = Values[((c) * stride) + ((r) / 8)] & 2 ^ t;
2 ^ t doesn't do what you hope it does, that's Visual Basic syntax. In the C# language, ^ is the XOR operator. Use the << operator instead and use parentheses to take care of operator precedence. Fix:
Val = Values[((c) * stride) + ((r) / 8)] & (1 << t);
And fix it again when you set the bit.
Do note that turning the entire image to White doesn't require this kind of code at all. Just set all the bytes to 0xff.
I'm trying to write function that converts an arbitrary large array of bytes (larger than 64-bit) into a decimal number represented as string in c# and I simply can't figure out how to do it.
For example the following code ...
Console.WriteLine(ConvertToString(
new byte[]
{
0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88,
0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00
}));
.. should print out
22774453838368691933757882222884355840
I don't want to just use an extra library like biginteger for that, because I want it to be simple and like to understand how it works.
Some guidelines:
You will need to save the digits of the number in a matrix, one space for each digit. The matrix starts empty.
Then you need a matrix to save the multiplication of the matrix. It too starts empty.
Now for each byte you have:
First multiply each digit of the current number matrix by 256, save the 10 modulus in the corresponding temporary digit and add the the number divided by 10 to the next digits multiplication.
Assign the temporary multiplication matrix to the current digit matrix.
Then add the byte to the first digit.
Correct the current matrix, saving only the 10 modulus in each index and passing the value divided by 10 to the next index.
Then you need to concatenate each digit in a string.
Return the string.
Don't forget to expand each matrix as needed, or determine the maximum size needed from the number of bytes been passed.
Edit, example following the third step above:
Values = [0xAA, 0xBB]
Initial Current = []
Initial Temp = []
With 0xAA
Nothing to multiply.
No change on assignment.
We add 0xAA to the first value in current: Current = [170]
We correct current to save only the modulus, passing the value divided by ten to the next value:
1st digit: Current = [0] pass 17.
2nd digit: Current = [0, 7] pass 1.
3rd digit: Current = [0, 7, 1] no value to pass so process ends.
Now with 0xBB
Multipli by 256, save in temp and correct, do for each digit:
1st digit: Temp = [0], 0 to save to the next digit.
2nd digit: Temp = [0, 1792] before correction, Temp = [0, 2], 179 to pass after correction.
3rd digit: Temp = [0, 2, 1 * 256 + 179 = 435] before correction, Temp = [0, 2, 5], 43 to pass after correction.
4th digit: Temp = [0, 2, 5, 43] before, Temp = [0, 2, 5, 3], 3 to pass after
5th digit: Temp = [0, 2, 5, 3, 4] before and after correction, no digit to save, so the multiplication ends.
Assing temp to current: Current = [0, 2, 5, 3, 4]; Temp = []
Add the current value to the first digit: Current = [187, 2, 5, 3, 4]
Correct the values:
1st digit: Current = [7, 2, 5, 3, 4], 18 to pass.
2nd digit: Current = [7, 0, 5, 3, 4], 2 to pass.
3rd digit: Current = [7, 0, 7, 3, 4], nothing to pass so the addition ends.
Now we only need to concatenate for the result 43707.
Based on #Wilheim's answer:
static string BytesToString(byte[] data) {
// Minimum length 1.
if (data.Length == 0) return "0";
// length <= digits.Length.
var digits = new byte[(data.Length * 0x00026882/* (int)(Math.Log(2, 10) * 0x80000) */ + 0xFFFF) >> 16];
int length = 1;
// For each byte:
for (int j = 0; j != data.Length; ++j) {
// digits = digits * 256 + data[j].
int i, carry = data[j];
for (i = 0; i < length || carry != 0; ++i) {
int value = digits[i] * 256 + carry;
carry = Math.DivRem(value, 10, out value);
digits[i] = (byte)value;
}
// digits got longer.
if (i > length) length = i;
}
// Return string.
var result = new StringBuilder(length);
while (0 != length) result.Append((char)('0' + digits[--length]));
return result.ToString();
}
You want to understand the workings so take a look at super cool
C# BigInteger Class # CodeProject.
Also, I have stripped that class to bare essentials for this question. It can be optimized further. :)
Try copy and paste following code it works!!
using System;
public class BigInteger
{
// maximum length of the BigInteger in uint (4 bytes)
// change this to suit the required level of precision.
private const int maxLength = 70;
private uint[] data = null; // stores bytes from the Big Integer
public int dataLength; // number of actual chars used
public BigInteger()
{
data = new uint[maxLength];
dataLength = 1;
}
public BigInteger(long value)
{
data = new uint[maxLength];
long tempVal = value;
dataLength = 0;
while (value != 0 && dataLength < maxLength)
{
data[dataLength] = (uint)(value & 0xFFFFFFFF);
value >>= 32;
dataLength++;
}
if (tempVal > 0) // overflow check for +ve value
{
if (value != 0 || (data[maxLength - 1] & 0x80000000) != 0)
throw (new ArithmeticException("Positive overflow in constructor."));
}
else if (tempVal < 0) // underflow check for -ve value
{
if (value != -1 || (data[dataLength - 1] & 0x80000000) == 0)
throw (new ArithmeticException("Negative underflow in constructor."));
}
if (dataLength == 0)
dataLength = 1;
}
public BigInteger(ulong value)
{
data = new uint[maxLength];
// copy bytes from ulong to BigInteger without any assumption of
// the length of the ulong datatype
dataLength = 0;
while (value != 0 && dataLength < maxLength)
{
data[dataLength] = (uint)(value & 0xFFFFFFFF);
value >>= 32;
dataLength++;
}
if (value != 0 || (data[maxLength - 1] & 0x80000000) != 0)
throw (new ArithmeticException("Positive overflow in constructor."));
if (dataLength == 0)
dataLength = 1;
}
public BigInteger(BigInteger bi)
{
data = new uint[maxLength];
dataLength = bi.dataLength;
for (int i = 0; i < dataLength; i++)
data[i] = bi.data[i];
}
public BigInteger(byte[] inData)
{
dataLength = inData.Length >> 2;
int leftOver = inData.Length & 0x3;
if (leftOver != 0) // length not multiples of 4
dataLength++;
if (dataLength > maxLength)
throw (new ArithmeticException("Byte overflow in constructor."));
data = new uint[maxLength];
for (int i = inData.Length - 1, j = 0; i >= 3; i -= 4, j++)
{
data[j] = (uint)((inData[i - 3] << 24) + (inData[i - 2] << 16) +
(inData[i - 1] << 8) + inData[i]);
}
if (leftOver == 1)
data[dataLength - 1] = (uint)inData[0];
else if (leftOver == 2)
data[dataLength - 1] = (uint)((inData[0] << 8) + inData[1]);
else if (leftOver == 3)
data[dataLength - 1] = (uint)((inData[0] << 16) + (inData[1] << 8) + inData[2]);
while (dataLength > 1 && data[dataLength - 1] == 0)
dataLength--;
//Console.WriteLine("Len = " + dataLength);
}
public override string ToString()
{
return ToString(10);
}
public string ToString(int radix)
{
string charSet = "ABCDEF";
string result = "";
BigInteger a = this;
BigInteger quotient = new BigInteger();
BigInteger remainder = new BigInteger();
BigInteger biRadix = new BigInteger(radix);
if (a.dataLength == 1 && a.data[0] == 0)
result = "0";
else
{
while (a.dataLength > 1 || (a.dataLength == 1 && a.data[0] != 0))
{
singleByteDivide(a, biRadix, quotient, remainder);
if (remainder.data[0] < 10)
result = remainder.data[0] + result;
else
result = charSet[(int)remainder.data[0] - 10] + result;
a = quotient;
}
}
return result;
}
private static void singleByteDivide(BigInteger bi1, BigInteger bi2,
BigInteger outQuotient, BigInteger outRemainder)
{
uint[] result = new uint[maxLength];
int resultPos = 0;
// copy dividend to reminder
for (int i = 0; i < maxLength; i++)
outRemainder.data[i] = bi1.data[i];
outRemainder.dataLength = bi1.dataLength;
while (outRemainder.dataLength > 1 && outRemainder.data[outRemainder.dataLength - 1] == 0)
outRemainder.dataLength--;
ulong divisor = (ulong)bi2.data[0];
int pos = outRemainder.dataLength - 1;
ulong dividend = (ulong)outRemainder.data[pos];
if (dividend >= divisor)
{
ulong quotient = dividend / divisor;
result[resultPos++] = (uint)quotient;
outRemainder.data[pos] = (uint)(dividend % divisor);
}
pos--;
while (pos >= 0)
{
dividend = ((ulong)outRemainder.data[pos + 1] << 32) + (ulong)outRemainder.data[pos];
ulong quotient = dividend / divisor;
result[resultPos++] = (uint)quotient;
outRemainder.data[pos + 1] = 0;
outRemainder.data[pos--] = (uint)(dividend % divisor);
}
outQuotient.dataLength = resultPos;
int j = 0;
for (int i = outQuotient.dataLength - 1; i >= 0; i--, j++)
outQuotient.data[j] = result[i];
for (; j < maxLength; j++)
outQuotient.data[j] = 0;
while (outQuotient.dataLength > 1 && outQuotient.data[outQuotient.dataLength - 1] == 0)
outQuotient.dataLength--;
if (outQuotient.dataLength == 0)
outQuotient.dataLength = 1;
while (outRemainder.dataLength > 1 && outRemainder.data[outRemainder.dataLength - 1] == 0)
outRemainder.dataLength--;
}
public static void Main(string[] args)
{
BigInteger big = new BigInteger( new byte[]
{
0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88,
0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00
});
Console.WriteLine(big);
}
}
Would System.Decimal meet your needs?