Base-N编码字节数组 [英] Base-N encoding of a byte array

查看:180
本文介绍了Base-N编码字节数组的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

几天前,我遇到了此CodeReview 为Base-36编码字节数组。然而,随后的答案没有触及解码到一个字节数组,或者可能重复使用答案来执行不同的基数(基数)的编码。



答案对于链接的问题使用BigInteger。所以就实现而言,基数和数字可以被参数化。



然而,BigInteger的问题是,我们将输入视为假定的整数。但是,我们的输入字节数组只是不透明的一系列值。




  • 如果字节数组以一系列零字节结束,例如{0xFF,0x7F,0x00,0x00},在答案中使用算法时,这些字节将丢失(只会编码{0xFF,0x7F}。

  • 零字节具有符号位设置,因此进行的零字节被消耗,因为它被视为BigInt的符号定界符,所以{0xFF,0xFF,0x00,0x00}将仅编码为{0xFF,0xFF,0x00}。



.NET程序员如何使用BigInteger创建一个有效的和不知不觉的编码器,具有解码支持,加上处理endian的能力结果零字节丢失的能力


解决方案

>编辑 [2016/04/19]:如果您喜欢异常,您可能希望更改一些Decode实现代码,以将 InvalidDataException



编辑 [2014/09/14]:我已将HACK添加到Encode()中处理输入中最后一个字节被签名的情况(如果要转换为sbyte)。只有我现在想到的只有一个理智的解决方案就是将数组调整一次。对于这种情况,额外的单元测试通过了,但是我没有重新运行perf代码来解释这种情况。如果你可以帮助它,总是有你的输入Encode()包括一个虚拟的0字节,以避免额外的分配。



用法



我创建了一个RadixEncoding类(在代码部分中找到),它用三个参数初始化:


  1. 基数字符串(长度决定当前的实际基数),

  2. 输入字节数组的假定字节排序(endian)

  3. 以及用户是否希望编码/解码逻辑确认结束零字节。

创建Base-36编码,具有小端输入,并且相对于结尾零字节:

  const string k_base36_digits =0123456789abcdefghijklmnopqrstuvwxyz; 
var base36_no_zeros = new RadixEncoding(k_base36_digits,EndianFormat.Little,false);

然后实际执行编码/解码:

  const string k_input =A test 1234; 
byte [] input_bytes = System.Text.Encoding.UTF8.GetBytes(k_input);
string encoded_string = base36_no_zeros.Encode(input_bytes);
byte [] decoded_bytes = base36_no_zeros.Decode(encoded_string);



性能



定时与诊断。 ,运行在i7 860 @ 2.80GHz。定时EXE自身运行,而不是调试器。



使用与上述相同的 k_base36_digits 字符串初始化编码,EndianFormat.Little和结束零字节确认(即使UTF8字节没有任何额外的结尾零字节)



要编码UTF8字节的A测试12341,000,000次需要2.6567905秒

要解码相同的字符串相同的时间需要3.3916248secs
b
$ b

要对UTF8进行编码A测试1234的字节稍大一点! 100,000次需要1.1577325秒

要解码相同的字符串,相同的时间需要1.244326秒
b
$ b

代码



如果您没有 CodeContracts生成器,您将不得不使用if / throw代码重新实现合同。

  using System; 
使用System.Collections.Generic;
使用System.Numerics;
使用Contract = System.Diagnostics.Contracts.Contract;

public enum EndianFormat
{
///< summary>最低有效位顺序(lsb)< / summary>
///< remarks>从右到左< / remarks>
///< see cref =BitConverter.IsLittleEndian/>
Little,
///< summary>最高有效位顺序(msb)< / summary>
///< remarks>从左到右< / remarks>
Big,
};

///< summary>对字符串进行编码/解码字符串< / summary>
///< remarks>
///编码的字符串总是以big-endian排序
///
///< p>编码和解码获取< b> includeProceedingZeros< / b>参数作为一个解决方案
///对于我们的BigInteger实现的边缘情况。
/// MSDN说BigInteger字节数组是LSB-> MSB排序。因此,在
/// end处的零字节缓冲区将在结果编码的基数字符串中忽略这些零。
///如果这样的精度损失绝对不会发生,就会真实地传递给< b> includeProceedingZeros< / b>
///对于一些额外的处理,它将处理零位(编码)
///或字节(解码)的填充。< / p>
///< p>注意:这样做用于解码< b>可以< / b>添加一个额外的字节比原来的
///给予Encode。< / p>
///< / remarks>
//根据http://codereview.stackexchange.com/questions/14084/base-36-encoding-of-a-byte-array/
的答案public class RadixEncoding
{
const int kByteBitCount = 8;

只读字符串kDigits;
readonly double kBitsPerDigit;
只读BigInteger kRadixBig;
readonly EndianFormat kEndian;
readonly bool kIncludeProceedingZeros;

///< summary>此编码的数字基础< / summary>
public int Radix {get {return kDigits.Length; }}
///< summary>对由Decode< / summary>进行编码并输出的字节的端序排序
public EndianFormat Endian {get {return kEndian; }}
///< summary>如果我们要结束零字节被编码,则为True< / summary>
public bool IncludeProceedingZeros {get {return kIncludeProceedingZeros; }}

public override string ToString()
{
返回string.Format(Base- {0} {1},Radix.ToString(),kDigits);
}

///< summary>使用给定字符创建基数编码器作为基数< / summary>中的数字。
///< param name =digits>用于基数编码字符串的数字< / param>
///< param name =bytesEndian>由Decode< / param>编码并输出的字节的端序排序
///< param name =includeProceedingZeros>如果我们要结束零字节进行编码< / param>
public RadixEncoding(string digits,
EndianFormat bytesEndian = EndianFormat.Little,bool includeProceedingZeros = false)
{
Contract.Requires< ArgumentNullException>(digits!= null);
int radix = digits.Length;

kDigits =数字;
kBitsPerDigit = System.Math.Log(radix,2);
kRadixBig = new BigInteger(radix);
kEndian = bytesEndian;
kIncludeProceedingZeros = includeProceedingZeros;
}

//编码指定字节数所需的字符数
int EncodingCharsCount(int bytesLength)
{
return(int)Math .Ceiling((bytesLength * kByteBitCount)/ kBitsPerDigit);
}
//解码指定字符数所需的字节数
int DecodingBytesCount(int charsCount)
{
return(int)Math.Ceiling(( charsCount * kBitsPerDigit)/ kByteBitCount);
}

///< summary>将字节数组编码为基数编码的字符串< / summary>
///< param name =bytes>要编码的字节数组< / param>
///< returns>编码为基数编码字符串的字节< / returns>
///< remarks> If< paramref name =bytes/>为零长度,返回一个空字符串< / remarks>
public string Encode(byte [] bytes)
{
Contract.Requires< ArgumentNullException>(bytes!= null);
Contract.Ensures(Contract.Result< string>()!= null);

//不要真的这样做,我们的代码将构建这个结果(空字符串),
//但是为什么在做工作之前不能捕获这个条件?
if(bytes.Length == 0)return string.Empty;

//如果数组以零结束,设置为此的容量将帮助我们知道多少
//'padding'我们需要添加
int result_length = EncodingCharsCount(bytes.Length);
// List<>具有(n就地)反向方法。 StringBuilder不这就是为什么。
var result = new List< char>(result_length);

// HACK:BigInteger将最后一个字节用作符号字节。如果该字节的MSB被设置,
//我们需要用额外的0来填充输入(即使它为正)
if((bytes [bytes.Length-1]& 0x80)= = 0x80)
Array.Resize(ref bytes,bytes.Length + 1);

var dividend = new BigInteger(bytes);
// IsZero的计算比评估dividend> 0
//它调用BigInteger.CompareTo(BigInteger)
while(!dividend.IsZero)
{
BigInteger余数;
dividend = BigInteger.DivRem(dividend,kRadixBig,out remaining);
int digit_index = System.Math.Abs​​((int)remaining);
result.Add(kDigits [digit_index]);
}

if(kIncludeProceedingZeros)
for(int x = result.Count; x< result.Capacity; x ++)
result.Add(kDigits [0 ]); //使用代表'零'的字符填充

//定位大端排序中的字符
if(kEndian == EndianFormat.Little)
result.Reverse );
//如果我们没有最终添加填充,ToArray将最终返回一个TrimExcess'd数组,
//所以没有浪费
返回新的字符串(result.ToArray()) ;
}

void DecodeImplPadResult(ref byte [] result,int padCount)
{
if(padCount> 0)
{
int new_length = result.Length + DecodingBytesCount(padCount);
Array.Resize(ref result,new_length); //新的字节将为零,只是我们想要的方式
}
}
#region Decode(Little Endian)
byte [] DecodeImpl(string chars,int startIndex = 0)
{
var bi = new BigInteger(); (int x = startIndex; x< chars.Length; x ++)
{
int i = kDigits.IndexOf(chars [x]);

if(i< 0)返回null; // invalid character
bi * = kRadixBig;
bi + = i;
}

return bi.ToByteArray();
}
byte [] DecodeImplWithPadding(string chars)
{
int pad_count = 0; (int x = 0; x< chars.Length; x ++,pad_count ++)
if(chars [x]!= kDigits [0])break;

var result = DecodeImpl(chars,pad_count);
DecodeImplPadResult(ref result,pad_count);

返回结果;
}
#endregion
#region解码(Big Endian)
byte [] DecodeImplReversed(string chars,int startIndex = 0)
{
var bi = new BigInteger(); (int x =(chars.Length-1)-startIndex; x> = 0; x--)
{
int i = kDigits.IndexOf(chars [x]) ;
if(i< 0)返回null; // invalid character
bi * = kRadixBig;
bi + = i;
}

return bi.ToByteArray();
}
byte [] DecodeImplReversedWithPadding(string chars)
{
int pad_count = 0; (int x = chars.Length - 1; x> = 0; x--,pad_count ++)
if(chars [x]!= kDigits [0])break;

var result = DecodeImplReversed(chars,pad_count);
DecodeImplPadResult(ref result,pad_count);

返回结果;
}
#endregion
///< summary>将基数编码的字符串解码为字节数组< / summary>
///< param name =radixChars> radix string< / param>
///< returns>解码的字节,如果遇到无效字符,则返回null< / returns>
///< remarks>
///如果< paramref name =radixChars/>是一个空字符串,返回零长度数组
///
///使用< paramref name =IncludeProceedingZeros/>有可能返回一个没有输入的
///附加零字节的缓冲区。所以一个4字节的缓冲区被编码,这可能会结束
///返回一个5字节缓冲区,额外的字节为空。
///< / remarks>
public byte [] Decode(string radixChars)
{
Contract.Requires< ArgumentNullException>(radixChars!= null);

if(kEndian == EndianFormat.Big)
return kIncludeProceedingZeros? DecodeImplReversedWithPadding(radixChars):DecodeImplReversed(radixChars);
else
return kIncludeProceedingZeros? DecodeImplWithPadding(radixChars):DecodeImpl(radixChars);
}
};



基本单元测试



 使用系统; 
使用Microsoft.VisualStudio.TestTools.UnitTesting;

static bool ArraysCompareN&T(T [] input,T [] output)
其中T:IEquatable< T>
{
if(output.Length< input.Length)return false; (int x = 0; x< input.Length; x ++)
if(!output [x] .Equals(input [x]))return false;

返回true;
}
static bool RadixEncodingTest(RadixEncoding encoding,byte [] bytes)
{
string encoded = encoding.Encode(bytes);
byte [] decoded = encoding.Decode(encoded);

返回ArraysCompareN(字节,解码);
}
[TestMethod]
public void TestRadixEncoding()
{
const string k_base36_digits =0123456789abcdefghijklmnopqrstuvwxyz;
var base36 = new RadixEncoding(k_base36_digits,EndianFormat.Little,true);
var base36_no_zeros = new RadixEncoding(k_base36_digits,EndianFormat.Little,true);

byte [] ends_with_zero_neg = {0xFF,0xFF,0x00,0x00};
byte [] ends_with_zero_pos = {0xFF,0x7F,0x00,0x00};
byte [] text = System.Text.Encoding.ASCII.GetBytes(A test 1234);

Assert.IsTrue(RadixEncodingTest(base36,ends_with_zero_neg));
Assert.IsTrue(RadixEncodingTest(base36,ends_with_zero_pos));
Assert.IsTrue(RadixEncodingTest(base36_no_zeros,text));
}


A couple of days ago I came across this CodeReview for Base-36 encoding a byte array. However, the answers that followed didn't touch on decoding back into a byte array, or possibly reusing the answer to perform encodings of different bases (radix).

The answer for the linked question uses BigInteger. So as far as implementation goes, the base and its digits could be parametrized.

The problem with BigInteger though, is that we're treating our input as an assumed integer. However, our input, a byte array, is just an opaque series of values.

  • If the byte array ends in a series of zero bytes, eg {0xFF,0x7F,0x00,0x00}, those bytes will be lost when using the algorithm in the answer (would only encode {0xFF,0x7F}.
  • If the last non-zero byte has the sign bit set then the proceeding zero byte is consumed as it's treated as the BigInt's sign delimiter. So {0xFF,0xFF,0x00,0x00} would encode only as {0xFF,0xFF,0x00}.

How could a .NET programmer use BigInteger to create a reasonably efficient and radix-agnostic encoder, with decoding support, plus the ability to handle endian-ness, and with the ability to 'work around' the ending zero bytes being lost?

解决方案

edit [2016/04/19]: If you're fond of exceptions, you may wish to change some of the Decode implementation code to throw InvalidDataException instead of just returning null.

edit [2014/09/14]: I've added a 'HACK' to Encode() to handle cases where the last byte in the input is signed (if you were to convert to sbyte). Only sane solution I could think of right now is to just Resize() the array by one. Additional unit tests for this case passed, but I didn't rerun perf code to account for such cases. If you can help it, always have your input to Encode() include a dummy 0 byte at the end to avoid additional allocations.

Usage

I've created a RadixEncoding class (found in the "Code" section) which initializes with three parameters:

  1. The radix digits as a string (length determines the actual radix of course),
  2. The assumed byte ordering (endian) of input byte arrays,
  3. And whether or not the user wants the encode/decode logic to acknowledge ending zero bytes.

To create a Base-36 encoding, with little-endian input, and with respect given to ending zero bytes:

const string k_base36_digits = "0123456789abcdefghijklmnopqrstuvwxyz";
var base36_no_zeros = new RadixEncoding(k_base36_digits, EndianFormat.Little, false);

And then to actually perform encoding/decoding:

const string k_input = "A test 1234";
byte[] input_bytes = System.Text.Encoding.UTF8.GetBytes(k_input);
string encoded_string = base36_no_zeros.Encode(input_bytes);
byte[] decoded_bytes = base36_no_zeros.Decode(encoded_string);

Performance

Timed with Diagnostics.Stopwatch, ran on an i7 860 @2.80GHz. Timing EXE ran by itself, not under a debugger.

Encoding was initialized with the same k_base36_digits string from above, EndianFormat.Little, and with ending zero bytes acknowledged (even though the UTF8 bytes don't have any extra ending zero bytes)

To encode the UTF8 bytes of "A test 1234" 1,000,000 times takes 2.6567905secs
To decode the same string the same amount of times takes 3.3916248secs

To encode the UTF8 bytes of "A test 1234. Made slightly larger!" 100,000 times takes 1.1577325secs
To decode the same string the same amount of times takes 1.244326secs

Code

If you don't have a CodeContracts generator, you will have to reimplement the contracts with if/throw code.

using System;
using System.Collections.Generic;
using System.Numerics;
using Contract = System.Diagnostics.Contracts.Contract;

public enum EndianFormat
{
    /// <summary>Least Significant Bit order (lsb)</summary>
    /// <remarks>Right-to-Left</remarks>
    /// <see cref="BitConverter.IsLittleEndian"/>
    Little,
    /// <summary>Most Significant Bit order (msb)</summary>
    /// <remarks>Left-to-Right</remarks>
    Big,
};

/// <summary>Encodes/decodes bytes to/from a string</summary>
/// <remarks>
/// Encoded string is always in big-endian ordering
/// 
/// <p>Encode and Decode take a <b>includeProceedingZeros</b> parameter which acts as a work-around
/// for an edge case with our BigInteger implementation.
/// MSDN says BigInteger byte arrays are in LSB->MSB ordering. So a byte buffer with zeros at the 
/// end will have those zeros ignored in the resulting encoded radix string.
/// If such a loss in precision absolutely cannot occur pass true to <b>includeProceedingZeros</b>
/// and for a tiny bit of extra processing it will handle the padding of zero digits (encoding)
/// or bytes (decoding).</p>
/// <p>Note: doing this for decoding <b>may</b> add an extra byte more than what was originally 
/// given to Encode.</p>
/// </remarks>
// Based on the answers from http://codereview.stackexchange.com/questions/14084/base-36-encoding-of-a-byte-array/
public class RadixEncoding
{
    const int kByteBitCount = 8;

    readonly string kDigits;
    readonly double kBitsPerDigit;
    readonly BigInteger kRadixBig;
    readonly EndianFormat kEndian;
    readonly bool kIncludeProceedingZeros;

    /// <summary>Numerial base of this encoding</summary>
    public int Radix { get { return kDigits.Length; } }
    /// <summary>Endian ordering of bytes input to Encode and output by Decode</summary>
    public EndianFormat Endian { get { return kEndian; } }
    /// <summary>True if we want ending zero bytes to be encoded</summary>
    public bool IncludeProceedingZeros { get { return kIncludeProceedingZeros; } }

    public override string ToString()
    {
        return string.Format("Base-{0} {1}", Radix.ToString(), kDigits);
    }

    /// <summary>Create a radix encoder using the given characters as the digits in the radix</summary>
    /// <param name="digits">Digits to use for the radix-encoded string</param>
    /// <param name="bytesEndian">Endian ordering of bytes input to Encode and output by Decode</param>
    /// <param name="includeProceedingZeros">True if we want ending zero bytes to be encoded</param>
    public RadixEncoding(string digits,
        EndianFormat bytesEndian = EndianFormat.Little, bool includeProceedingZeros = false)
    {
        Contract.Requires<ArgumentNullException>(digits != null);
        int radix = digits.Length;

        kDigits = digits;
        kBitsPerDigit = System.Math.Log(radix, 2);
        kRadixBig = new BigInteger(radix);
        kEndian = bytesEndian;
        kIncludeProceedingZeros = includeProceedingZeros;
    }

    // Number of characters needed for encoding the specified number of bytes
    int EncodingCharsCount(int bytesLength)
    {
        return (int)Math.Ceiling((bytesLength * kByteBitCount) / kBitsPerDigit);
    }
    // Number of bytes needed to decoding the specified number of characters
    int DecodingBytesCount(int charsCount)
    {
        return (int)Math.Ceiling((charsCount * kBitsPerDigit) / kByteBitCount);
    }

    /// <summary>Encode a byte array into a radix-encoded string</summary>
    /// <param name="bytes">byte array to encode</param>
    /// <returns>The bytes in encoded into a radix-encoded string</returns>
    /// <remarks>If <paramref name="bytes"/> is zero length, returns an empty string</remarks>
    public string Encode(byte[] bytes)
    {
        Contract.Requires<ArgumentNullException>(bytes != null);
        Contract.Ensures(Contract.Result<string>() != null);

        // Don't really have to do this, our code will build this result (empty string),
        // but why not catch the condition before doing work?
        if (bytes.Length == 0) return string.Empty;

        // if the array ends with zeros, having the capacity set to this will help us know how much
        // 'padding' we will need to add
        int result_length = EncodingCharsCount(bytes.Length);
        // List<> has a(n in-place) Reverse method. StringBuilder doesn't. That's why.
        var result = new List<char>(result_length);

        // HACK: BigInteger uses the last byte as the 'sign' byte. If that byte's MSB is set, 
        // we need to pad the input with an extra 0 (ie, make it positive)
        if ( (bytes[bytes.Length-1] & 0x80) == 0x80 )
            Array.Resize(ref bytes, bytes.Length+1);

        var dividend = new BigInteger(bytes);
        // IsZero's computation is less complex than evaluating "dividend > 0"
        // which invokes BigInteger.CompareTo(BigInteger)
        while (!dividend.IsZero)
        {
            BigInteger remainder;
            dividend = BigInteger.DivRem(dividend, kRadixBig, out remainder);
            int digit_index = System.Math.Abs((int)remainder);
            result.Add(kDigits[digit_index]);
        }

        if (kIncludeProceedingZeros)
            for (int x = result.Count; x < result.Capacity; x++)
                result.Add(kDigits[0]); // pad with the character that represents 'zero'

        // orientate the characters in big-endian ordering
        if (kEndian == EndianFormat.Little)
            result.Reverse();
        // If we didn't end up adding padding, ToArray will end up returning a TrimExcess'd array, 
        // so nothing wasted
        return new string(result.ToArray());
    }

    void DecodeImplPadResult(ref byte[] result, int padCount)
    {
        if (padCount > 0)
        {
            int new_length = result.Length + DecodingBytesCount(padCount);
            Array.Resize(ref result, new_length); // new bytes will be zero, just the way we want it
        }
    }
    #region Decode (Little Endian)
    byte[] DecodeImpl(string chars, int startIndex = 0)
    {
        var bi = new BigInteger();
        for (int x = startIndex; x < chars.Length; x++)
        {
            int i = kDigits.IndexOf(chars[x]);
            if (i < 0) return null; // invalid character
            bi *= kRadixBig;
            bi += i;
        }

        return bi.ToByteArray();
    }
    byte[] DecodeImplWithPadding(string chars)
    {
        int pad_count = 0;
        for (int x = 0; x < chars.Length; x++, pad_count++)
            if (chars[x] != kDigits[0]) break;

        var result = DecodeImpl(chars, pad_count);
        DecodeImplPadResult(ref result, pad_count);

        return result;
    }
    #endregion
    #region Decode (Big Endian)
    byte[] DecodeImplReversed(string chars, int startIndex = 0)
    {
        var bi = new BigInteger();
        for (int x = (chars.Length-1)-startIndex; x >= 0; x--)
        {
            int i = kDigits.IndexOf(chars[x]);
            if (i < 0) return null; // invalid character
            bi *= kRadixBig;
            bi += i;
        }

        return bi.ToByteArray();
    }
    byte[] DecodeImplReversedWithPadding(string chars)
    {
        int pad_count = 0;
        for (int x = chars.Length - 1; x >= 0; x--, pad_count++)
            if (chars[x] != kDigits[0]) break;

        var result = DecodeImplReversed(chars, pad_count);
        DecodeImplPadResult(ref result, pad_count);

        return result;
    }
    #endregion
    /// <summary>Decode a radix-encoded string into a byte array</summary>
    /// <param name="radixChars">radix string</param>
    /// <returns>The decoded bytes, or null if an invalid character is encountered</returns>
    /// <remarks>
    /// If <paramref name="radixChars"/> is an empty string, returns a zero length array
    /// 
    /// Using <paramref name="IncludeProceedingZeros"/> has the potential to return a buffer with an
    /// additional zero byte that wasn't in the input. So a 4 byte buffer was encoded, this could end up
    /// returning a 5 byte buffer, with the extra byte being null.
    /// </remarks>
    public byte[] Decode(string radixChars)
    {
        Contract.Requires<ArgumentNullException>(radixChars != null);

        if (kEndian == EndianFormat.Big)
            return kIncludeProceedingZeros ? DecodeImplReversedWithPadding(radixChars) : DecodeImplReversed(radixChars);
        else
            return kIncludeProceedingZeros ? DecodeImplWithPadding(radixChars) : DecodeImpl(radixChars);
    }
};

Basic Unit Tests

using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;

static bool ArraysCompareN<T>(T[] input, T[] output)
    where T : IEquatable<T>
{
    if (output.Length < input.Length) return false;
    for (int x = 0; x < input.Length; x++)
        if(!output[x].Equals(input[x])) return false;

    return true;
}
static bool RadixEncodingTest(RadixEncoding encoding, byte[] bytes)
{
    string encoded = encoding.Encode(bytes);
    byte[] decoded = encoding.Decode(encoded);

    return ArraysCompareN(bytes, decoded);
}
[TestMethod]
public void TestRadixEncoding()
{
    const string k_base36_digits = "0123456789abcdefghijklmnopqrstuvwxyz";
    var base36 = new RadixEncoding(k_base36_digits, EndianFormat.Little, true);
    var base36_no_zeros = new RadixEncoding(k_base36_digits, EndianFormat.Little, true);

    byte[] ends_with_zero_neg = { 0xFF, 0xFF, 0x00, 0x00 };
    byte[] ends_with_zero_pos = { 0xFF, 0x7F, 0x00, 0x00 };
    byte[] text = System.Text.Encoding.ASCII.GetBytes("A test 1234");

    Assert.IsTrue(RadixEncodingTest(base36, ends_with_zero_neg));
    Assert.IsTrue(RadixEncodingTest(base36, ends_with_zero_pos));
    Assert.IsTrue(RadixEncodingTest(base36_no_zeros, text));
}

这篇关于Base-N编码字节数组的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆