JDK1.8源码笔记(12) BigInteger&BigDecimal

BigInteger

前言

Immutable arbitrary-precision integers.
不可变任意精度整数。

All operations behave as if BigIntegers were represented in two's-complement notation (like Java's primitive integer types).
所有操作都把BigInteger当成二进制补码表示(就像一个Java的基本整数类型)。
原理就是模拟Integer。

BigInteger provides analogues to all of Java's primitive integer operators, and all relevant methods from java.lang.Math.
各种操作运算,BigInteger应有尽有,包括Math包中的。

* Additionally, BigInteger provides operations for modular arithmetic, GCD
* calculation, primality testing, prime generation, bit manipulation,
* and a few other miscellaneous operations.

除此之外,还额外提供模运算,求最大公约数,素数检测,素数生成,位运算和其他miscellaneous的操作。

* <p>Semantics of arithmetic operations exactly mimic those of Java's integer
* arithmetic operators, as defined in <i>The Java Language Specification</i>.
BigInteger就连运算方法的语义上也精确模拟Integer的运算方法。

For example, division by zero throws an {@code ArithmeticException}, and division of a negative by a positive yields a negative (or zero) remainder.
比如除以零会抛出ArithmeticException,如果一个正数除以一个负数会得到非正余数。

* All of the details in the Spec concerning overflow are ignored, as
* BigIntegers are made as large as necessary to accommodate the results of an
* operation.
溢出?没有!

* <p>Semantics of shift operations extend those of Java's shift operators
* to allow for negative shift distances.
shift操作也允许负数。

A right-shift with a negative shift distance results in a left shift, and vice-versa.
负数的right-shift会转化为left-shift,反之亦然。

* The unsigned
* right shift operator ({@code >>>}) is omitted, as this operation makes
* little sense in combination with the "infinite word size" abstraction
* provided by this class.
无符号右移被省略,因为BigInteger是无限制大小的,原因大概是Sign bit is stored separately.
https://stackoverflow.com/questions/5281852/biginteger-unsigned-left-or-right-shift#

* The binary operators ({@code and},
* {@code or}, {@code xor}) implicitly perform sign extension on the shorter
* of the two operands prior to performing the operation.
二进制操作运算前会对较短操作数进行带符号扩展。

* None of the single-bit
* operations can produce a BigInteger with a different sign from the
* BigInteger being operated on, as they affect only a single bit, and the
* "infinite word size" abstraction provided by this class ensures that there
* are infinitely many "virtual sign bits" preceding each BigInteger.
没有single-bit运算操作可以修改BigInteger的符号。因为"infinite word size"概念确保有无穷的"virtual sign bits"。

继承类&实现接口

继承Number类,实现Comparable接口。
Comparable我们在前面已经介绍过了,这里不再赘述。

在这里简单说一下Number类。
* The abstract class {@code Number} is the superclass of platform
* classes representing numeric values that are convertible to the
* primitive types {@code byte}, {@code double}, {@code float}, {@code
* int}, {@code long}, and {@code short}.
常见的数字类型,例如Double、Integer、Byte、Float、Long、Short等都是继承Number的。

Number是一个抽象类,不能实例化。

public abstract int intValue();
public abstract long longValue();
public abstract float floatValue();
public abstract double doubleValue();

定义了四个抽象方法,分别用于把当前数值转换为int、long、float和double,当然rounding或者truncation是可能发生的。

public byte byteValue() {
    return (byte)intValue();
}
public short shortValue() {
    return (short)intValue();
}

还定义两个转换为byte和short的方法。

通过上面的分析我们可以看到,Number类主要的功能是在于进行数值的转换的。

构造方法

public BigInteger(byte[] val) {
    if (val.length == 0)
        throw new NumberFormatException("Zero length BigInteger");

    if (val[0] < 0) {
        mag = makePositive(val);
        signum = -1;
    } else {
        mag = stripLeadingZeroBytes(val);
        signum = (mag.length == 0 ? 0 : 1);
    }
    if (mag.length >= MAX_MAG_LENGTH) {
        checkRange();
    }
}

这个是BigInteger的第一个构造函数,可以看到大概的功能:
如果传入的Byte数组val是空的话(哪怕你表示0,也得有才能表示吧),会抛出异常。
如果是正数的话,会调用stripLeadingZeroBytes方法,这个方法的功能是首先消除可能存在的前导零,然后计算byte转换为int需要开多少的空间,在经过该数值的转换之后填入int数组内。
private static int[] stripLeadingZeroBytes(byte a[]) {
    int byteLength = a.length;
    int keep;

    // Find first nonzero byte
    for (keep = 0; keep < byteLength && a[keep] == 0; keep++)
        ;

    // Allocate new array and copy relevant part of input array
    int intLength = ((byteLength - keep) + 3) >>> 2;
    int[] result = new int[intLength];
    int b = byteLength - 1;
    for (int i = intLength-1; i >= 0; i--) {
        result[i] = a[b--] & 0xff;
        int bytesRemaining = b - keep + 1;
        int bytesToTransfer = Math.min(3, bytesRemaining);
        for (int j=8; j <= (bytesToTransfer << 3); j += 8)
            result[i] |= ((a[b--] & 0xff) << j);
    }
    return result;
}

这个方法实现还是非常优美的,我们来具体看一下:
首先通过一个for循环找到第一个非0的byte空间。
for (keep = 0; keep < byteLength && a[keep] == 0; keep++)
        ;

通过移位的方式计算出byte转换为int需要int的大小空间(一个int四个byte)。
int intLength = ((byteLength - keep) + 3) >>> 2;
最后从数组尾部向前遍历填入数据.
for (int i = intLength-1; i >= 0; i--) {
    result[i] = a[b--] & 0xff;
    int bytesRemaining = b - keep + 1;
    int bytesToTransfer = Math.min(3, bytesRemaining);
    for (int j=8; j <= (bytesToTransfer << 3); j += 8)
        result[i] |= ((a[b--] & 0xff) << j);
}
result[i] = a[b--] & 0xff;

这么做是为了去除byte符号的影响,因为如果你执行result[i] = a[b--],高位会进行有符号扩展,这不是我们想看到的,所以我们需要先和被认为是int类型的同样进行扩展的0xff相与。subtlety!
https://stackoverflow.com/questions/11380062/what-does-value-0xff-do-in-java
没看明白的话就自己再想想,这个不好解释清楚的。
如果是负数的话,则会调用makePositive方法,makePositive也有很多值得说一下的地方:
private static int[] makePositive(byte a[]) {
    int keep, k;
    int byteLength = a.length;

    // Find first non-sign (0xff) byte of input
    for (keep=0; keep < byteLength && a[keep] == -1; keep++)
        ;


    /* Allocate output array.  If all non-sign bytes are 0x00, we must
     * allocate space for one extra output byte. */
    for (k=keep; k < byteLength && a[k] == 0; k++)
        ;

    int extraByte = (k == byteLength) ? 1 : 0;
    int intLength = ((byteLength - keep + extraByte) + 3) >>> 2;
    int result[] = new int[intLength];

    /* Copy one's complement of input into output, leaving extra
     * byte (if it exists) == 0x00 */
    int b = byteLength - 1;
    for (int i = intLength-1; i >= 0; i--) {
        result[i] = a[b--] & 0xff;
        int numBytesToTransfer = Math.min(3, b-keep+1);
        if (numBytesToTransfer < 0)
            numBytesToTransfer = 0;
        for (int j=8; j <= 8*numBytesToTransfer; j += 8)
            result[i] |= ((a[b--] & 0xff) << j);

        // Mask indicates which bits must be complemented
        int mask = -1 >>> (8*(3-numBytesToTransfer));
        result[i] = ~result[i] & mask;
    }

    // Add one to one's complement to generate two's complement
    for (int i=result.length-1; i >= 0; i--) {
        result[i] = (int)((result[i] & LONG_MASK) + 1);
        if (result[i] != 0)
            break;
    }

    return result;
}

分析过程省略,难以用语言解释。最终这个方法的功能是将补码转换为相对应的原码。
result[i] = (int)((result[i] & LONG_MASK) + 1); 不与LONG_MASK是会溢出的。

其他的构造方法本质上也都大同小异,最终都是通过一些手段转化为int数组。

成员变量&成员方法

final int signum;
* The signum of this BigInteger: -1 for negative, 0 for zero, or
* 1 for positive.  Note that the BigInteger zero <i>must</i> have
* a signum of 0.  This is necessary to ensures that there is exactly one
* representation for each BigInteger value.
这个变量使用来存储数值的符号的,注意到是final类型。
-1为负数,0为0,1为正数。
数值为0,signum也必为0.

final int[] mag;
这个就是用来保存具体数值的真身了。
* The magnitude of this BigInteger, in <i>big-endian</i> order: the
* zeroth element of this array is the most-significant int of the
* magnitude.
BigInteger规定使用big-endian方式存储数据,即mag低位是具体数值的高位。
The magnitude must be "minimal" in that the most-significant int ({@code mag[0]}) must be non-zero.
为了保证数值表示的唯一性,最低位是非零的。
Note that this implies that the BigInteger zero has a zero-length mag array.
所以当数值为0的话,mag的长度自然也就是0了。

public BigInteger add(BigInteger val) {
    if (val.signum == 0)
        return this;
    if (signum == 0)
        return val;
    if (val.signum == signum)
        return new BigInteger(add(mag, val.mag), signum);

    int cmp = compareMagnitude(val);
    if (cmp == 0)
        return ZERO;
    int[] resultMag = (cmp > 0 ? subtract(mag, val.mag)
            : subtract(val.mag, mag));
    resultMag = trustedStripLeadingZeroInts(resultMag);

    return new BigInteger(resultMag, cmp == signum ? 1 : -1);
}

这是BigInteger的add方法,用于把两个BigInteger相加。
这个方法逻辑很清晰,首先用几个if条件构成shortcut,随后将异号加法转换为减法。

private static int[] subtract(int[] big, int[] little) {
    int bigIndex = big.length;
    int result[] = new int[bigIndex];
    int littleIndex = little.length;
    long difference = 0;

    // Subtract common parts of both numbers
    while (littleIndex > 0) {
        difference = (big[--bigIndex] & LONG_MASK) -
                (little[--littleIndex] & LONG_MASK) +
                (difference >> 32);
        result[bigIndex] = (int)difference;
    }

    // Subtract remainder of longer number while borrow propagates
    boolean borrow = (difference >> 32 != 0);
    while (bigIndex > 0 && borrow)
        borrow = ((result[--bigIndex] = big[bigIndex] - 1) == -1);

    // Copy remainder of longer number
    while (bigIndex > 0)
        result[--bigIndex] = big[bigIndex];

    return result;
}

要注意到根据的BigInteger表示数字的方法,是采用原码表示的,二进制另存,所以这是一个大小关系确定的原码减法。

BigDecimal

前言

Above all, I want demonstrate the precision and scale.
For a number as -12345.6789, the precision is 9, and the scale is 4.
precision和小数点没有任何关系。

Immutable, arbitrary-precision signed decimal numbers.
不可变、任意精度有符号含小数数字。

A {@code BigDecimal} consists of an arbitrary precision integer <i>unscaled value</i> and a 32-bit integer <i>scale</i>.
一个BigDecimal由未标精度的任意长度整数和32整数标尺构成。

If zero or positive, the scale is the number of digits to the right of the decimal point.
如果BigDecimal非负,scale表示的是小数点右侧数字的个数。

If negative, the unscaled value of the number is multiplied by ten to the power of the negation of the scale. The value of the number represented by the BigDecimal is therefore (unscaledValue × 10-scale).
如果是负数的话,则使用unscaledValue × pow(10, -scale)的方式表示。

The {@code BigDecimal} class gives its user complete control over rounding behavior.
BigDecimal把舍入操作的全部控制权给用户掌握。

If no rounding mode is specified and the exact result cannot be represented, an exception is thrown;
如果具体的舍入规则没有被明确,则具体的结果不仅不会被呈现,还会抛出异常。

* otherwise, calculations can be carried out to a chosen precision
* and rounding mode by supplying an appropriate {@link MathContext}
* object to the operation.
否则当一个合适的MathContext对象会被运算通过一个选定的精度和舍入方式。

对于MathContext的介绍如下:
* Immutable objects which encapsulate the context settings which
* describe certain rules for numerical operators, such as those
* implemented by the {@link BigDecimal} class.
这个类是专门用来描述数字操作的Context。这个类是被final修饰的。


In either case, eight <em>rounding modes</em> are provided for the control of rounding.
在任何情况下,有八种舍入模式提供进行控制舍入。

Using the integer fields in this class (such as {@link #ROUND_HALF_UP}) to represent rounding mode is largely obsolete;
使用ROUND_HALF_UP这种类内的整数变量去表示舍入类型已经过时了。

the enumeration values of the {@code RoundingMode} {@code enum}, (such as {@link RoundingMode#HALF_UP}) should be used instead.
枚举类型RoundingMode正大行其道。
看一下枚举类型的RoundingMode,会发现里面一共定义了8种舍入模式,其中最需要注意的是HALF_EVEN,可以看一下HALF_EVEN的介绍:
* Note that this
* is the rounding mode that statistically minimizes cumulative
* error when applied repeatedly over a sequence of calculations.
* It is sometimes known as {@literal "Banker's rounding,"} and is
* chiefly used in the USA.
"Banker's rounding",statistically,所以商业运算中不用你用谁?

* <p>When a {@code MathContext} object is supplied with a precision
* setting of 0 (for example, {@link MathContext#UNLIMITED}),
* arithmetic operations are exact, as are the arithmetic methods
* which take no {@code MathContext} object.  (This is the only
* behavior that was supported in releases prior to 5.)
当一个MathContext对象使用0作为precision(例如MathContext中的UNLIMITED的precision传入的就是0),这个时候就认为运算是不舍入的,是精确的,就如同没有MathContext传入(这JDK5之前唯一支持的行为)。
简而言之就是不存在precision为0的情况,precision为0,就认为不进行舍入操作。
* As a
* corollary of computing the exact result, the rounding mode setting
* of a {@code MathContext} object with a precision setting of 0 is
* not used and thus irrelevant.  In the case of divide, the exact
* quotient could have an infinitely long decimal expansion; for
* example, 1 divided by 3.  If the quotient has a nonterminating
* decimal expansion and the operation is specified to return an exact
* result, an {@code ArithmeticException} is thrown.  Otherwise, the
* exact result of the division is returned, as done for other
* operations.
作为一个计算结果的推论,如果precision设置为零的话,模式的设置就会认为是无效的。
在有些除法的情况下,保持精确的可能会出现无穷长度,比如说1/3。如果是一个无穷长的decimal还要返回exact result,就会抛出异常了。
下面的代码就会抛出异常:
BigDecimal decimal1 = new BigDecimal("1");
BigDecimal decimal2 = new BigDecimal("3");
BigDecimal result = decimal1.divide(decimal2);
那么该怎么用呢:
BigDecimal decimal1 = new BigDecimal("1");
BigDecimal decimal2 = new BigDecimal("3");
BigDecimal result = decimal1.divide(decimal2, 2, BigDecimal.ROUND_HALF_UP);

* <p>Since the same numerical value can have different
* representations (with different scales), the rules of arithmetic
* and rounding must specify both the numerical result and the scale
* used in the result's representation.
由于数字的值有多种表示方法(使用不同的scale),所以规则必须明确。

在BigDecimal中,两个精度不同但真值相同的数字,用equals进行比较,会严格比较scale,而使用compareTo的话,会忽视scale的影响,这和equals和compareTo语义和实际情况的细微差距是对应的,有例子如下:
BigDecimal decimal1 = new BigDecimal("0.1");
BigDecimal decimal2 = new BigDecimal("0.10");
System.out.println(decimal1.equals(decimal2)); // false
System.out.println(decimal1.compareTo(decimal2)); // 0

* First, the
* total number of digits to return is specified by the
* {@code MathContext}'s {@code precision} setting; this determines
* the result's <i>precision</i>.  The digit count starts from the
* leftmost nonzero digit of the exact result.  The rounding mode
* determines how any discarded trailing digits affect the returned
* result.
首先,总的数字数总是由MathContext的precision指定的(当然精度是不能通过这种方式增加的)。rounding mode决定被丢弃的后续数字是怎么影响返回值的。

* For all arithmetic operators , the operation is carried out as
* though an exact intermediate result were first calculated and then
* rounded to the number of digits specified by the precision setting
* (if necessary), using the selected rounding mode.
对于所有的算数运算,都是先求出精确的结果,再进行舍入运算。

When rounding increases the magnitude of the returned result, it is possible for a new digit position to be created by a carry propagating to a leading "9" digit.
如果舍入操作之后导致数字进位,解决方法如下例:
For example, rounding the value 999.9 to three digits rounding up would be numerically equal to one thousand, represented as 100×101. In such cases, the new "1" is the leading digit position of the returned result.

Besides a logical exact result, each arithmetic operation has a preferred scale for representing a result. The preferred scale for each operation is listed in the table below.
运算其实也是有默认精度的存在的,如下所列:
* </b></caption>
* <tr><th>Operation</th><th>Preferred Scale of Result</th></tr>
* <tr><td>Add</td><td>max(addend.scale(), augend.scale())</td>
* <tr><td>Subtract</td><td>max(minuend.scale(), subtrahend.scale())</td>
* <tr><td>Multiply</td><td>multiplier.scale() + multiplicand.scale()</td>
* <tr><td>Divide</td><td>dividend.scale() - divisor.scale()</td>
* </table>
These scales are the ones used by the methods which return exact arithmetic results; except that an exact divide may have to use a larger scale since the exact result may have more digits. For example, 1/32 is 0.03125.
但是除法是有可能有更大的scale的。

Before rounding, the scale of the logical exact intermediate result is the preferred scale for that operation.
在舍入之前,逻辑中间准确结果的scale就是运算的优先scale。

......

构造方法

public BigDecimal(char[] in, int offset, int len, MathContext mc) {
    // protect against huge length.
    if (offset + len > in.length || offset < 0)
        throw new NumberFormatException("Bad offset or len arguments for char[] input.");
    // This is the primary string to BigDecimal constructor; all
    // incoming strings end up here; it uses explicit (inline)
    // parsing for speed and generates at most one intermediate
    // (temporary) object (a char[] array) for non-compact case.

    // Use locals for all fields values until completion
    int prec = 0;                 // record precision value
    int scl = 0;                  // record scale value
    long rs = 0;                  // the compact value in long
    BigInteger rb = null;         // the inflated value in BigInteger
    // use array bounds checking to handle too-long, len == 0,
    // bad offset, etc.
    try {
        // handle the sign
        boolean isneg = false;          // assume positive
        if (in[offset] == '-') {
            isneg = true;               // leading minus means negative
            offset++;
            len--;
        } else if (in[offset] == '+') { // leading + allowed
            offset++;
            len--;
        }

        // should now be at numeric part of the significand
        boolean dot = false;             // true when there is a '.'
        long exp = 0;                    // exponent
        char c;                          // current character
        boolean isCompact = (len <= MAX_COMPACT_DIGITS);
        // integer significand array & idx is the index to it. The array
        // is ONLY used when we can't use a compact representation.
        int idx = 0;
        if (isCompact) {
            // First compact case, we need not to preserve the character
            // and we can just compute the value in place.
            for (; len > 0; offset++, len--) {
                c = in[offset];
                if ((c == '0')) { // have zero
                    if (prec == 0)
                        prec = 1;
                    else if (rs != 0) {
                        rs *= 10;
                        ++prec;
                    } // else digit is a redundant leading zero
                    if (dot)
                        ++scl;
                } else if ((c >= '1' && c <= '9')) { // have digit
                    int digit = c - '0';
                    if (prec != 1 || rs != 0)
                        ++prec; // prec unchanged if preceded by 0s
                    rs = rs * 10 + digit;
                    if (dot)
                        ++scl;
                } else if (c == '.') {   // have dot
                    // have dot
                    if (dot) // two dots
                        throw new NumberFormatException();
                    dot = true;
                } else if (Character.isDigit(c)) { // slow path
                    int digit = Character.digit(c, 10);
                    if (digit == 0) {
                        if (prec == 0)
                            prec = 1;
                        else if (rs != 0) {
                            rs *= 10;
                            ++prec;
                        } // else digit is a redundant leading zero
                    } else {
                        if (prec != 1 || rs != 0)
                            ++prec; // prec unchanged if preceded by 0s
                        rs = rs * 10 + digit;
                    }
                    if (dot)
                        ++scl;
                } else if ((c == 'e') || (c == 'E')) {
                    exp = parseExp(in, offset, len);
                    // Next test is required for backwards compatibility
                    if ((int) exp != exp) // overflow
                        throw new NumberFormatException();
                    break; // [saves a test]
                } else {
                    throw new NumberFormatException();
                }
            }
            if (prec == 0) // no digits found
                throw new NumberFormatException();
            // Adjust scale if exp is not zero.
            if (exp != 0) { // had significant exponent
                scl = adjustScale(scl, exp);
            }
            rs = isneg ? -rs : rs;
            int mcp = mc.precision;
            int drop = prec - mcp; // prec has range [1, MAX_INT], mcp has range [0, MAX_INT];
            // therefore, this subtract cannot overflow
            if (mcp > 0 && drop > 0) {  // do rounding
                while (drop > 0) {
                    scl = checkScaleNonZero((long) scl - drop);
                    rs = divideAndRound(rs, LONG_TEN_POWERS_TABLE[drop], mc.roundingMode.oldMode);
                    prec = longDigitLength(rs);
                    drop = prec - mcp;
                }
            }
        } else {
            char coeff[] = new char[len];
            for (; len > 0; offset++, len--) {
                c = in[offset];
                // have digit
                if ((c >= '0' && c <= '9') || Character.isDigit(c)) {
                    // First compact case, we need not to preserve the character
                    // and we can just compute the value in place.
                    if (c == '0' || Character.digit(c, 10) == 0) {
                        if (prec == 0) {
                            coeff[idx] = c;
                            prec = 1;
                        } else if (idx != 0) {
                            coeff[idx++] = c;
                            ++prec;
                        } // else c must be a redundant leading zero
                    } else {
                        if (prec != 1 || idx != 0)
                            ++prec; // prec unchanged if preceded by 0s
                        coeff[idx++] = c;
                    }
                    if (dot)
                        ++scl;
                    continue;
                }
                // have dot
                if (c == '.') {
                    // have dot
                    if (dot) // two dots
                        throw new NumberFormatException();
                    dot = true;
                    continue;
                }
                // exponent expected
                if ((c != 'e') && (c != 'E'))
                    throw new NumberFormatException();
                exp = parseExp(in, offset, len);
                // Next test is required for backwards compatibility
                if ((int) exp != exp) // overflow
                    throw new NumberFormatException();
                break; // [saves a test]
            }
            // here when no characters left
            if (prec == 0) // no digits found
                throw new NumberFormatException();
            // Adjust scale if exp is not zero.
            if (exp != 0) { // had significant exponent
                scl = adjustScale(scl, exp);
            }
            // Remove leading zeros from precision (digits count)
            rb = new BigInteger(coeff, isneg ? -1 : 1, prec);
            rs = compactValFor(rb);
            int mcp = mc.precision;
            if (mcp > 0 && (prec > mcp)) {
                if (rs == INFLATED) {
                    int drop = prec - mcp;
                    while (drop > 0) {
                        scl = checkScaleNonZero((long) scl - drop);
                        rb = divideAndRoundByTenPow(rb, drop, mc.roundingMode.oldMode);
                        rs = compactValFor(rb);
                        if (rs != INFLATED) {
                            prec = longDigitLength(rs);
                            break;
                        }
                        prec = bigDigitLength(rb);
                        drop = prec - mcp;
                    }
                }
                if (rs != INFLATED) {
                    int drop = prec - mcp;
                    while (drop > 0) {
                        scl = checkScaleNonZero((long) scl - drop);
                        rs = divideAndRound(rs, LONG_TEN_POWERS_TABLE[drop], mc.roundingMode.oldMode);
                        prec = longDigitLength(rs);
                        drop = prec - mcp;
                    }
                    rb = null;
                }
            }
        }
    } catch (ArrayIndexOutOfBoundsException e) {
        throw new NumberFormatException();
    } catch (NegativeArraySizeException e) {
        throw new NumberFormatException();
    }
    this.scale = scl;
    this.precision = prec;
    this.intCompact = rs;
    this.intVal = rb;
}

成员变量&成员方法

private final BigInteger intVal;
用一个BigInteger用来存unscaled数值,包括符号也一起存了。

private final int scale;
用来确定小数点的位置。

这样看就很清晰了,BigDecimal其实还是对BigInteger的一个封装。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值