Java secure coding guide: number operation

brief introduction

In Java, there are byte, short, int, long, float, double and char that can be called number. What should we pay attention to when using these Nubmers? Let's have a look.

Range of number

Each number type has its range. Let's look at the range of number types in Java:

Considering the most commonly used int operation, although the range of int is large enough, if we do some int operations, we may still exceed the range of int.

What will be sent if the int range is exceeded? Take the following example:

    public void testIntegerOverflow(){
        System.out.println(Integer.MAX_VALUE+1000);
    }

Operation result: - 2147482649.

Obviously, integer MAX_ Value + 1000 will exceed the maximum range of integer, but we did not get an exception reminder, but got an incorrect result.

The correct operation is to throw an exception if we encounter an overflow problem: arithmeticexception.

How to prevent this integeroverflow problem? Generally speaking, we have the following ways.

for instance:

    static final int safeAdd(int left,int right) {
        if (right > 0 ? left > Integer.MAX_VALUE - right
                : left < Integer.MIN_VALUE - right) {
            throw new ArithmeticException("Integer overflow");
        }
        return left + right;
    }

In the above example, we need to add two integers. Before adding, we need to judge the range to ensure the security of calculation.

Math's addexact and multiplyexact methods have provided overflow judgment. Let's take a look at the implementation of addexact:

    public static int addExact(int x,int y) {
        int r = x + y;
        // HD 2-12 Overflow iff both arguments have the opposite sign of the result
        if (((x ^ r) & (y ^ r)) < 0) {
            throw new ArithmeticException("integer overflow");
        }
        return r;
    }

See how to use:

    public int addUseMath(int a,int b){
        return Math.addExact(a,b);
    }

Since it is beyond the range of integer, we can use a larger range of long to store data.

    public static long intRangeCheck(long value) {
        if ((value < Integer.MIN_VALUE) || (value > Integer.MAX_VALUE)) {
            throw new ArithmeticException("Integer overflow");
        }
        return value;
    }

    public int addUseUpcasting(int a,int b){
        return (int)intRangeCheck((long)a+(long)b);
    }

In the above example, we convert a + B into the addition of two long to ensure that the range is not overflowed.

Then, a range comparison is performed to determine whether the result after addition is still within the integer range.

We can use BigInteger Valueof (a) convert int to BigInteger, and then perform the following operations:

    public int useBigInteger(int a,int b){
        return BigInteger.valueOf(a).add(BigInteger.valueOf(b)).intValue();
    }

Distinguish between bit operation and arithmetic operation

We usually perform bit or arithmetic operations on integer. Although two operations can be performed, it is best not to perform the two operations at the same time, which will cause confusion.

For example, the following example:

x += (x << 1) + 1;

What does the above example want to do? In fact, it wants to assign the value of 3x + 1 to X.

But it's hard to understand, so we need to avoid this implementation.

Take another example:

    public void testBitwiSEOperation(){
        int i = -10;
        System.out.println(i>>>2);
        System.out.println(i>>2);
        System.out.println(i/4);
    }

What we wanted to do was divide I by 4, and it turned out that only the last one was the result we wanted.

Let's explain that the first I > > > 2 is a logical shift to the right, which will fill the leftmost one with 0, so the result is a positive value of 1073741821.

The second I > > 2 is to move the arithmetic right. The leftmost one will still be filled with 1, but it will be rounded down, so the result is - 3

Using I / 4 directly, we round it up, so the result is - 2

Be careful not to use 0 as a divisor

When we use a variable as a divisor, we must first judge whether it is 0

C + + compatible unsigned integer types

In Java, only 16 bit char represents an unsigned integer, while int actually represents a signed integer.

In C or C + +, unsigned integers can be directly represented. If we have a 32-bit unsigned integer, how can we use java to deal with it?

    public int readIntWrong(DataInputStream is) throws IOException {
        return is.readInt();
    }

Looking at the above example, we read an int value from the stream. If it is a 32-bit unsigned integer, the read int becomes a signed negative integer, which is consistent with our expectations.

Consider that long is 64 bits. Can we use long to represent 32-bit unsigned integers?

    public long readIntRight(DataInputStream is) throws IOException{
        return is.readInt() & 0xFFFFFFFFL; // Mask with 32 one-bits
    }

Looking at the above example, we return long. If we convert a 32-bit int to a 64 bit long, it will be automatically completed according to the symbol bit.

So at this time, we need to mask with 0xffffffffl to reset the high 32 bits to 0

Nan and infinity

In integer operation, the divisor cannot be 0, otherwise the exception will be run directly. However, the concepts of Nan and infinity are introduced in floating-point operations. Let's take a look at the definitions in double and float.

public static final double POSITIVE_INFINITY = 1.0 / 0.0;
public static final double NEGATIVE_INFINITY = -1.0 / 0.0;
public static final double NaN = 0.0d / 0.0;
public static final float POSITIVE_INFINITY = 1.0f / 0.0f;
public static final float NEGATIVE_INFINITY = -1.0f / 0.0f;
public static final float NaN = 0.0f / 0.0f;

1 divided by 0 is infinity, and 0 divided by 0 is Nan.

Next, let's take a look at the comparison between Nan and infinity:

public void compareInfinity(){
    System.out.println(Double.POSITIVE_INFINITY == Double.POSITIVE_INFINITY);
    }

The running result is true.

    public void compareNaN(){
        System.out.println(Double.NaN == Double.NaN);
    }

The running result is false.

You can see that Nan is false compared with Nan.

So how do we compare Nan?

Don't worry. Double provides an IsNaN method. We can use it as follows:

System.out.println(Double.isNaN(Double.NaN));

Next, let's look at a double parsing that is often used in the code:

    public void incorrectParse(String userInput){
        double val = 0;
        try {
            val = Double.valueOf(userInput);
        } catch (NumberFormatException e) {
        }
        //do something for val
    }

Is there a problem with this code? At first glance, there seems to be no problem, but if our userinput is Nan, infinity, or - infinity, double Valueof is the result that can be parsed.

    public void testNaN(){
        System.out.println(Double.valueOf("NaN"));
        System.out.println(Double.valueOf("Infinity"));
        System.out.println(Double.valueOf("-Infinity"));
    }

Run output:

NaN
Infinity
-Infinity

Therefore, we need to judge Nan and infinity additionally:

public void correctParse(String userInput){
        double val = 0;
        try {
            val = Double.valueOf(userInput);
        } catch (NumberFormatException e) {
        }
        if (Double.isInfinite(val)){
            // Handle infinity error
        }

        if (Double.isNaN(val)) {
            // Handle NaN error
        }
        //do something for val
    }

Do not use float or double as counters for loops

Consider the following code:

for (float x = 0.1f; x <= 1.0f; x += 0.1f) {
  System.out.println(x);
}

What's wrong with the above code?

We all know that floating-point numbers in Java are inaccurate, but not necessarily anyone knows why they are inaccurate.

Here I'll explain to you that all and numbers in the computer are stored in binary. Let's take 0.6 as an example.

When 0.6 is converted to binary format, it is rounded by 2, 0.6x2 = 1.2, rounding remains 0.2, continue the above steps, 0.2x2 = 0.4, 0.4x2 = 0.8,0.8x2 = 1.6, rounding remains 0.6, and a cycle is generated.

So the binary format of 0.6 is. 1001 1001 1001 1001 1001 1001 Infinite cycle.

Therefore, some decimals cannot be accurately expressed in binary, which eventually leads to the inaccuracy of using float or double as a counter.

Construction of BigDecimal

In order to solve the problem of missing precision in float or double calculation, we usually use BigDecimal.

When using BigDecimal, please be careful not to build BigDecimal from float, otherwise unexpected problems may occur.

    public void getFromFloat(){
        System.out.println(new BigDecimal(0.1));
    }

The result of the above code is: 0.10000000000005551231257827021181583404541015625.

This is because binary cannot perfectly display all decimals.

Therefore, we need to build BigDecimal from string:

    public void getFromString(){
        System.out.println(new BigDecimal("0.1"));
    }

Type conversion problem

In Java, various types of numbers can be converted to each other:

For example:

Or reverse:

When changing from a wide range of types to a small range of types, we should consider whether the scope of conversion types is exceeded:

    public void intToByte(int i){
        if ((i < Byte.MIN_VALUE) || (i > Byte.MAX_VALUE)) {
            throw new ArithmeticException("Value is out of range");
        }
        byte b = (byte) i;
    }

For example, in the above example, we convert int to byte. Before conversion, we need to judge whether int exceeds the range of byte.

At the same time, we also need to consider the accuracy switching. See the following example:

    public void intToFloat(){
        System.out.println(subtraction(1111111111,1111111111));
    }

    public int subtraction(int i,float j){
        return i - (int)j;
    }

What is the result?

The answer is not 0, but - 57.

Why?

Because we have done two conversions here. The first conversion is from 1111 to float. Although float has 32 bits, only 23 bits store real values, 1 bit is symbol bit, and the remaining 8 bits refer to digits.

Therefore, the conversion from 1111 to float sends a loss of accuracy.

We can modify the subtraction method. First, judge the range of float. If it exceeds the representation range of 23bit, it means that the sending accuracy is lost, and we need to throw an exception:

    public int subtraction(int i,float j){
        System.out.println(j);
        if ((j > 0x007fffff) || (j < -0x800000)) {
            throw new ArithmeticException("Insufficient precision");
        }
        return i - (int)j;
    }

Of course, there is another way. We can use a double with higher accuracy for conversion. The double has 52 bits to store real data, so it's enough.

    public int subtractionWithDouble(int i,double j){
        System.out.println(j);
        return i - (int)j;
    }

Code for this article:

learn-java-base-9-to-20/tree/master/security

The content of this article comes from the network collection of netizens. It is used as a learning reference. The copyright belongs to the original author.
THE END
分享
二维码
< <上一篇
下一篇>>