美文网首页
IOTA基石头 - 三进制系统之 Trit 和 Tryte

IOTA基石头 - 三进制系统之 Trit 和 Tryte

作者: 萝卜头4lbt | 来源:发表于2019-05-29 20:16 被阅读0次

    一、概要

        三进制数值,作为iota 的基础核心,不管是其模型序列化方式、pow 运算 以及 各种hash 算法实现中,都需要用到的数据结构,因此,本章节主要深入iota 的三进制数值 分析。
        三进制数值系统有两种:平衡三进制系统, trit取值为 -1, 0, 1; 非对称三进制系统,trit取值为 0, 1, 2,而IOTA 使用的是平衡三进制。trit 是 trinary Digit, 三元数字位, 类似于 bit, trit取值 -1, 0,1;tryte 是 trinary Byte, 类比于 byte, 一个tryte 由3个trit组成,为了使得tryte更好地让用户识别,IOTA创建了 tryte 字母表: 9ABC…XYZ; 包含26个大写字母和数字9,总共27个字符。
        题外话,为什么使用三进制,因为三进制架构的电路功耗是较低的,IOTA的创始人认为未来将会是三进制电路的世界,不得不说IOTA确实是走在了前列,包括采用三进制以及抗量子的算法。

    二、详细介绍&解析

    2.1 trit/tryte 互转

        在分析前,我们先来明确一下一些三进制特性,首先是trit(非平衡)转换成10进制的方法为(假设trits 为 [xyz],高位在左,低位在右):

    x * 3^0 + y * 3^1+ z*3^2。

        另外,平衡3进制系统的进制比较特殊,它是遇2 进-1,并且在自身运算时,时按照低位在左,高位在右。怎么说,例如
    0,0,0 ,
    +1 => -1, 0, 0
    +1 => -1, 1, 0
    +1 =>  0, 1, 0

    2.1.1 trit 转 tryte
    2-1

        图2-1 为iota系统中,使用平衡三进制 trit /tryte 字母转换表,一个tryte 用三个 trit 表示,我们接深入trit 转 Tryte的具体实现:

    public class Converter {
    
      public static final String TRYTE_ALPHABET = "9ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    
        public static String trytes(final List<Integer> trits) {
            return trytes(trits, 0, trits.size());
        }
    
        public static String trytes(final List<Integer> trits, final int offset, final int size) {
    
            StringBuilder trytes = new StringBuilder();
            // 遍历 trits 列表中的所有位数,高位在前,低位在后
            for (int i = 0; i < (size + 3 - 1) / 3; i++) {
                //计算10进制数值,等价于x(i)*3^0 + x(i+1)*3^1+ x(i+2)*3^2
                int j = trits.get(offset + i * 3) + trits.get(offset + i * 3 + 1) * 3 + trits.get(offset + i * 3 + 2) * 9;
                if (j < 0) {
                    // 代码走到这里,说明该三进制 数转成10进制数时小于0
                    j += Constants.TRYTE_ALPHABET.length(); //等价于j+= 27
                }
                //根据计算索引值,从字符表中获取指定字符
                trytes.append(Constants.TRYTE_ALPHABET.charAt(j));
            }
            return trytes.toString(); 
        }
    }
    

    上述代码比较简单,核心是遍历所有的三进制按照高位在前,低位在后,每三个 三进制位 先转 10进制数,然后索引字符表TRYTE_ALPHABET中具体的字符。

    2.1.2 tryte 转 trit

        有了上述tryte 转 trits实现的了解后,trits 转 tryte 就简单多了,在Converter 实现中,直接通过 tryte的字母表 TRYTE_ALPHABET 以及TRYTE->TRITS 的映射表TRYTE_TO_TRITS_MAPPINGS直接完成。
        首先, TRYTE_ALPHABET 是一个String 字符串,该字符串按序存放了tryte 中的27个字符;而TRYTE_TO_TRITS_MAPPINGS则是一个二维数组,第一维则长度为27,一一对应了TRYTE_ALPHABET 中的字符索引位置,第二维则存放了大小为3的 trits 数组,即 tryte 字符对应的 trits 值,该映射表TRYTE_TO_TRITS_MAPPINGSConverter 类初始化时,作为静态变量 被初始化。
        通过TRYTE_ALPHABET以及TRYTE_TO_TRITS_MAPPINGS,可以简单获取tryte 字符中,指定字符的 trits值。其工作原理为,给定一个tryte 字符 ,先定位 给定字符在 TRYTE_ALPHABET 中的 索引位置 index,然后通过index ,直接通过TRYTE_TO_TRITS_MAPPINGS[index] 获取相应的 trits 数组 值(大小为3)。
        因此,我们先来深入TRYTE_TO_TRITS_MAPPINGS的初始实现:

    public class Converter {
      
        static final byte[][] TRYTE_TO_TRITS_MAPPINGS = new byte[27][];
        public static final int NUMBER_OF_TRITS_IN_A_TRYTE = 3;
    
        public static final String TRYTE_ALPHABET = "9ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    
        static {
    
            final byte[] trits = new byte[NUMBER_OF_TRITS_IN_A_BYTE];
            //通过遍历27次,求出全部的映射值
            for (int i = 0; i < 27; i++) {
                //将当前的 i 所对应的trits 值 拷贝到TRYTE_TO_TRITS_MAPPINGS中
                TRYTE_TO_TRITS_MAPPINGS[i] = Arrays.copyOf(trits, NUMBER_OF_TRITS_IN_A_TRYTE);
                //trits 自增1
                increment(trits, NUMBER_OF_TRITS_IN_A_TRYTE);
            }
        }
    
        private static void increment(final byte[] trits, final int size) {
            for (int i = 0; i < size; i++) {
                //Converter.MAX_TRIT_VALUE = 1,
                //Converter.MIN_TRIT_VALUE = -1,
                // 先执行trits[i]++,在进行判断
                if (++trits[i] > Converter.MAX_TRIT_VALUE) {
                    //代码走到这里,说明需要处理满二进一,因此,需要i 需要 trits[i+1] 执行 ++ 操纵
                    trits[i] = Converter.MIN_TRIT_VALUE;
                } else {
                    // 非 满二进一的情况下,当前的trits[i]++后,立马跳出
                    break;
                }
            }
        }
    }
    

    上述代码为TRYTE_TO_TRITS_MAPPINGS初始化实现,合兴就是 从[000]开始,执行 自增1 运算,执行27 次上述已经描述的其运算机制。

    有了这个转换表以后,具体的 tryte 转 trit 实现就简单了:

        
        /**
         *  该方法将trytes 转换为byte的结果覆盖到dest 的destOffset 到 end 
         */
        public static void trits(final String trytes, byte[] dest, int destOffset) {
            // 防御式判断,确保结果trytes 的转换结果足以存放于dest当中,NUMBER_OF_TRITS_IN_A_TRYTE = 3
            if((dest.length - destOffset) < trytes.length() * NUMBER_OF_TRITS_IN_A_TRYTE) {
                throw new IllegalArgumentException("Destination array is not large enough.");
            }
            // 遍历trytes中所有 字符
            for (int i = 0; i < trytes.length(); i++) {
                //组个字符转换为trits,并写入到  dest        
                System.arraycopy(TRYTE_TO_TRITS_MAPPINGS[TRYTE_ALPHABET.indexOf(trytes.charAt(i))], 0, dest,
                        destOffset + i * NUMBER_OF_TRITS_IN_A_TRYTE, NUMBER_OF_TRITS_IN_A_TRYTE);
            }
        }
    

    上述代码流程中,主要是遍历trytes ,并作如下转换,先将所遍历到的字符,去tryte 字符表 TRYTE_ALPHABET中,查找 字符所在索引 index,然后在依据index 从 转换表TRYTE_TO_TRITS_MAPPINGS 定位到具体的 三个 三进制 数字,最后写入dest 相应的位置上,直到trytes 遍历完毕。


    2.2 trit/byte 互转

        trits/byte 实现与 trits/tryte 互转 类似,核心的不同点在于 一个 byte 需要5个trits。
        正常来说,一个byte 有256 种组合,5个trits 有 3^5 = 243 种组合,首先,trits 的范围为[-121,121], 而byte 的范围为[-128,127],例如,byte 中的127 转成trits 为 [1,0,-1,-1,-1], 但 byte 中 -116 同样也会转成 [1,0,-1,-1,-1],也就是说,iota 中的 trits /byte 互转实现中,byte 的范围只能在[-121,121],不然,会产生冲突

    2.2.1 trit 转 byte
        public static final int RADIX = 3;
    
        public static final int NUMBER_OF_TRITS_IN_A_BYTE = 5;
    
        public static void bytes(final byte[] trits, byte[] dest) {
            bytes(trits, 0, dest, 0, trits.length);
        }
    
        public static void bytes(final byte[] trits, final int srcPos, byte[] dest, int destPos, final int tritsLength) {
            // 5个TRITS 组成一个 byte,不足5个 也当一个
            final int expectedLength = (tritsLength + NUMBER_OF_TRITS_IN_A_BYTE - 1) / NUMBER_OF_TRITS_IN_A_BYTE;
    
            //防御式判断,确保存储目标的长度足以存放转换结果
            if((dest.length - destPos) < expectedLength) {
                throw new IllegalArgumentException("Input array not large enough.");
            }
    
            for (int i = 0; i < expectedLength; i++) {
                int value = 0;
                //按照高位在右,低位在左,每五位构成一个byte,最后几位个数不足5的,按照高位补0 计算。
                for (int j = (tritsLength - i * NUMBER_OF_TRITS_IN_A_BYTE) < 5 ? (tritsLength - i * NUMBER_OF_TRITS_IN_A_BYTE) : NUMBER_OF_TRITS_IN_A_BYTE; j-- > 0; ) {
                    value = value * RADIX + trits[srcPos + i * NUMBER_OF_TRITS_IN_A_BYTE + j];
                }
                dest[destPos + i] = (byte)value;
            }
        }
    

    上述代码段为 trit 转 byte 的具体实现,核心是按照高位在右,低位在左,每五位构成一个byte,最后几位个数不足5的,按照高位补0 计算,按照 上述转10进制的方式计算其10进制 值(byte)value,然后写入到字节数组dest中。这里需要稍微注意的小技巧是,在第二个for 循环中,求 相应的byte 值时,从里到外,等价于:
    (((trits[i+4] * 3 + trits[i+3] ) * 3+trits[i+2] ) * 3 + trits[i+1]) * 3 + trits[i]。

    2.2.2 byte 转 trit

    byte 转 trits 与 byte 转 tryte 方法基本一致,都是通过 映射表来实现:

    public class Converter {
      
        static final byte[][] BYTE_TO_TRITS_MAPPINGS = new byte[243][];
        public static final int NUMBER_OF_TRITS_IN_A_BYTE = 5;
    
        static {
    
            final byte[] trits = new byte[NUMBER_OF_TRITS_IN_A_BYTE];
            //通过遍历243次,求出全部的映射值
            for (int i = 0; i < 243; i++) {
                BYTE_TO_TRITS_MAPPINGS[i] = Arrays.copyOf(trits, NUMBER_OF_TRITS_IN_A_BYTE);
                increment(trits, NUMBER_OF_TRITS_IN_A_BYTE);
            }
            ...
        }
    
        private static void increment(final byte[] trits, final int size) {
            for (int i = 0; i < size; i++) {
                //Converter.MAX_TRIT_VALUE = 1,
                //Converter.MIN_TRIT_VALUE = -1,
                // 先执行trits[i]++,在进行判断
                if (++trits[i] > Converter.MAX_TRIT_VALUE) {
                    //代码走到这里,说明需要处理满二进一,因此,需要i 需要 trits[i+1] 执行 ++ 操纵
                    trits[i] = Converter.MIN_TRIT_VALUE;
                } else {
                    // 非 满二进一的情况下,当前的trits[i]++后,立马跳出
                    break;
                }
            }
        }
    }
    

    上述为byte->trits 映射表BYTE_TO_TRITS_MAPPINGS的构造,与TRYTE_TO_TRITS_MAPPINGS一致,只不过BYTE_TO_TRITS_MAPPINGS的长度为243 二为字节数组,这里就不再赘述。

    下面为具体的转换实现:

        public static void getTrits(final byte[] bytes, final int[] trits) {
    
            int offset = 0;
            // 遍历bytes中所有的字节
            for (int i = 0; i < bytes.length && offset < trits.length; i++) {
                System.arraycopy(BYTE_TO_TRITS_MAPPINGS[bytes[i] < 0 ? (bytes[i] + BYTE_TO_TRITS_MAPPINGS.length) : bytes[i]], 0, trits, offset, trits.length - offset < NUMBER_OF_TRITS_IN_A_BYTE ? (trits.length - offset) : NUMBER_OF_TRITS_IN_A_BYTE);
                offset += NUMBER_OF_TRITS_IN_A_BYTE;
            }
            while (offset < trits.length) {
                // 代码走到这里,说明bytes 遍历完并填充完整trits后,trits 还有剩余容量,则直接填充0.
                trits[offset++] = 0;
            }
        }
    
    

    上述实现byte 转 trit,核心是遍历bytes中所有元素,直接通过映射表BYTE_TO_TRITS_MAPPINGS 求出其trit 字节数组值,从左到右填充到目标 数组 trits中。


    2.3 hex/trit 互转

        首先,这里trit 为平衡 三进制,在平衡三进制中-1 可以T表示。

    2.3.1hex转trit

        我们先从一个用例来分析:


    图2-2

    上述为 142 转 非平衡三进制的计算方式:

    • 142 的非平衡三进制为 12021;
    • 然后对12021 反序 的12021;
    • 对于反序中的任意trit为2,加1,然后该trit变为T,并给下一位加1,得到最后的平衡三进制为1T1TT1。

    当然如果未-142,则先求其平衡三进制 1T1TT1, 将 1变为T, T 变为 1,得到-142(hex) = T1T11T(balanced)。

        我们接着深入其源码实现:

        static void tritsFromBigInt(final BigInteger value, final byte[] destination, final int offset, final int size) {
            // 防御式判断
            if (destination.length - offset < size) {
                throw new IllegalArgumentException("Destination array has invalid size");
            }
            // 求符号位,-1 为负数,1为正数,0说明该value为0
            final int signum = value.signum();
            if (signum == 0) {
                //说明value 为0,因此destination 全部填0,然后直接返回
                Arrays.fill(destination, offset, size, (byte) 0);
                return;
            }
    
            // 不管正负,先按正数求
            BigInteger absoluteValue = value.abs();
            for (int i = 0; i < size; i++) {// 逐个填充
                // 上一轮次的 商 除以3,divRemainder 存储 商 和余数
                BigInteger[] divRemainder = absoluteValue.divideAndRemainder(RADIX); 
    
                //获取商
                absoluteValue = divRemainder[0]; 
                //获取余数
                byte remainder = divRemainder[1].byteValue();
    
                if (remainder > Converter.MAX_TRIT_VALUE) {
                    // 代码走到这里,说明当前所求余数为2,根据上述计算规则,将余数置为 -1,然后遇2进1
                    remainder = Converter.MIN_TRIT_VALUE;
                     // 因此,将当前商 加 1处理
                    absoluteValue = absoluteValue.add(BigInteger.ONE);
                }
                // 当然,如果为负数,则求余数相反数,在将结果写入destination相应的位置中。
                destination[offset + i] = signum < 0 ? (byte) -remainder : remainder;
            }
        }
    

    上述实现与图2-2 的人工流程稍有 不同,代码实现中,则直接在不断 求商取余 的过程中,直接求其平衡三进制。但求取规则一致。到这里,想必读者会明白为什么在平衡三进制的 数组中,低位在左,高位在右。

    2.3.1trit转hex

        我们直接进入其源码实现:

    
        private static final BigInteger[] RADIX_POWERS = IntStream.range(0, MAX_POWERS_LONG + 1).mapToObj(RADIX::pow).toArray(BigInteger[]::new);
    
        static final int MAX_POWERS_LONG = 40;
    
        static BigInteger bigIntFromTrits(final byte[] trits, final int offset, final int size) {
            for (int i = offset; i < offset + size; i++) {
                if (trits[i] < -1 || trits[i] > 1) {
                    throw new IllegalArgumentException("not a trit: " + trits[i]);
                }
            }
            BigInteger value = BigInteger.ZERO;
            for (int n = offset + size - 1; n >= offset; ) {
                int count = 0;
                long num = 0L;
                // 每40(MAX_POWERS_LONG) 个 trit为一段
                while (n >= offset && count < MAX_POWERS_LONG) {
                    num = 3 * num + trits[n--];
                    count++;
                }
                value = value.multiply(RADIX_POWERS[count]).add(BigInteger.valueOf(num));
            }
            return value;
        }
    

    上述实现中,需要注意的两点是,首先,这里的转换结果需要用BigInteger表示。因为41位trit 就可以超过long 的最大值。整个转换流程与 trit 转byte 的差不多,都是按照高位在右,低位在左,从里到位,求其 hex 值。但这里必须分段处理,即每40个trit 值先用一个临时的long 变量存储,目的是方式溢出,另外,RADIX_POWERS 则是一个BigInteger 数组,其存放的内容为[3^0, 3^1, ...,3^40],目的是加速 value.multiply(RADIX_POWERS[count]) 的运算。

    到这里,全文分析完毕。

    相关文章

      网友评论

          本文标题:IOTA基石头 - 三进制系统之 Trit 和 Tryte

          本文链接:https://www.haomeiwen.com/subject/xivdtctx.html