JavaRush /Java Blog /Random EN /The device of real numbers

The device of real numbers

Published in the Random EN group
Hello! In today's lecture we will talk about numbers in Java, and specifically about real numbers. The device of real numbers - 1Don't panic! :) There will be no mathematical difficulties in the lecture. We will talk about real numbers exclusively from our “programmer” point of view. So, what are “real numbers”? Real numbers are numbers that have a fractional part (which can be zero). They can be positive or negative. Here are some examples: 15 56.22 0.0 1242342343445246 -232336.11 How does a real number work? Quite simple: it consists of an integer part, a fractional part and a sign. For positive numbers the sign is usually not indicated explicitly, but for negative numbers it is indicated. Previously, we examined in detail what operations on numbers can be performed in Java. Among them were many standard mathematical operations - addition, subtraction, etc. There were also some new ones for you: for example, the remainder of division. But how exactly does working with numbers work inside a computer? In what form are they stored in memory?

Storing real numbers in memory

I think it won’t be a discovery for you that numbers can be big and small :) They can be compared with each other. For example, the number 100 is less than the number 423324. Does this affect the operation of the computer and our program? Actually - yes . Each number is represented in Java by a specific range of values :
Type Memory size (bits) Range of values
byte 8 bit -128 to 127
short 16 bit -32768 to 32767
char 16 bit unsigned integer that represents a UTF-16 character (letters and numbers)
int 32 bits from -2147483648 to 2147483647
long 64 bits from -9223372036854775808 to 9223372036854775807
float 32 bits from 2 -149 to (2-2 -23 )*2 127
double 64 bits from 2 -1074 to (2-2 -52 )*2 1023
Today we’ll talk about the last two types - floatand double. Both perform the same task - representing fractional numbers. They are also very often called “ floating point numbers” . Remember this term for the future :) For example, the number 2.3333 or 134.1212121212. Quite strange. After all, it turns out that there is no difference between these two types, since they perform the same task? But there is a difference. Pay attention to the “size in memory” column in the table above. All numbers (and not just numbers - all information in general) are stored in the computer memory in the form of bits. A bit is the smallest unit of information. It's pretty simple. Any bit is equal to either 0 or 1. And the word “ bit ” itself comes from the English “ binary digit ” - a binary number. I think you've probably heard about the existence of the binary number system in mathematics. Any decimal number we are familiar with can be represented as a set of ones and zeros. For example, the number 584.32 in binary would look like this: 100100100001010001111 . Each one and zero in this number is a separate bit. Now you should be more clear about the difference between data types. For example, if we create a number of type float, we only have 32 bits at our disposal. When creating a number, floatthis is exactly how much space will be allocated for it in the computer's memory. If we want to create the number 123456789.65656565656565, in binary it will look like this: 11101011011110011010001010110101000000 . It consists of 38 ones and zeros, that is, 38 bits are needed to store it in memory. floatThis number simply won’t “fit” into the type ! Therefore, the number 123456789 can be represented as a type double. As many as 64 bits are allocated to store it: this suits us! Of course, the range of values ​​will also be suitable. For convenience, you can think of a number as a small box with cells. If there are enough cells to store each bit, then the data type is chosen correctly :) The device of real numbers - 2Of course, different amounts of allocated memory also affect the number itself. Please note that types floathave doubledifferent ranges of values. What does this mean in practice? A number doublecan express greater precision than a number float. 32-bit floating point numbers (in Java this is exactly the type float) have a precision of approximately 24 bits, that is, about 7 decimal places. And 64-bit numbers (in Java this is the type double) have a precision of approximately 53 bits, that is, approximately 16 decimal places. Here is an example that demonstrates this difference well:
public class Main {

   public static void main(String[] args)  {

       float f = 0.0f;
       for (int i=1; i <= 7; i++) {
           f += 0.1111111111111111;
       }

       System.out.println(f);
   }
}
What should we get here as a result? It would seem that everything is quite simple. We have the number 0.0, and we add 0.1111111111111111 to it 7 times in a row. The result should be 0.7777777777777777. But we created a number float. Its size is limited to 32 bits and, as we said earlier, it is capable of displaying a number up to about the 7th decimal place. Therefore, in the end, the result we get in the console will be different from what we expected:

0.7777778
The number seemed to be “cut off.” You already know how data is stored in memory - in the form of bits, so this should not surprise you. It’s clear why this happened: the result 0.7777777777777777 simply did not fit into the 32 bits allocated to us, so it was truncated to fit into a type variable float:) We can change the type of the variable to doublein our example, and then the final result will not be truncated:
public class Main {

   public static void main(String[] args)  {

       double f = 0.0;
       for (int i=1; i <= 7; i++) {
           f += 0.1111111111111111;
       }

       System.out.println(f);
   }
}

0.7777777777777779
There are already 16 decimal places, the result “fits” into 64 bits. By the way, perhaps you noticed that in both cases the results were not entirely correct? The calculation was made with minor errors. We'll talk about the reasons for this below :) Now let's say a few words about how you can compare numbers with each other.

Comparison of real numbers

We partially already touched on this issue in the last lecture, when we talked about comparison operations. We will not re-analyze operations such as >, <, >=. <=Let's look at a more interesting example instead:
public class Main {

   public static void main(String[] args)  {

       double f = 0.0;
       for (int i=1; i <= 10; i++) {
           f += 0.1;
       }

       System.out.println(f);
   }
}
What number do you think will be displayed on the screen? The logical answer would be the answer: the number 1. We start counting from the number 0.0 and successively add 0.1 to it ten times in a row. Everything seems to be correct, it should be one. Try running this code, and the answer will greatly surprise you :) Console output:

0.9999999999999999
But why did an error occur in such a simple example? O_o Here even a fifth grader could easily answer correctly, but the Java program produced an inaccurate result. “Inaccurate” is a better word here than “incorrect.” We still got a number very close to one, and not just some random value :) It differs from the correct one literally by a millimeter. But why? Perhaps this is just a one-time mistake. Maybe the computer crashed? Let's try to write another example.
public class Main {

   public static void main(String[] args)  {

       //add 0.1 to zero eleven times in a row
       double f1 = 0.0;
       for (int i = 1; i <= 11; i++) {
           f1 += .1;
       }

       // Multiply 0.1 by 11
       double f2 = 0.1 * 11;

       //should be the same - 1.1 in both cases
       System.out.println("f1 = " + f1);
       System.out.println("f2 = " + f2);

       // Let's check!
       if (f1 == f2)
           System.out.println("f1 and f2 are equal!");
       else
           System.out.println("f1 and f2 are not equal!");
   }
}
Console output:

f1 = 1.0999999999999999
f2 = 1.1
f1 и f2 не равны!
So, this is clearly not a matter of computer glitches :) What's going on? Errors like these are related to the way numbers are represented in binary form in the computer's memory. The fact is that in the binary system it is impossible to accurately represent the number 0.1 . By the way, the decimal system also has a similar problem: it is impossible to represent fractions correctly (and instead of ⅓ we get 0.33333333333333..., which is also not quite the correct result). It would seem like a trifle: with such calculations, the difference can be one hundred thousandth part (0.00001) or even less. But what if the entire outcome of your Very Serious Program depends on this comparison?
if (f1 == f2)
   System.out.println("Rocket flies into space");
else
   System.out.println("The launch is canceled, everyone goes home");
We clearly expected the two numbers to be equal, but due to the internal memory design, we canceled the rocket launch. The device of real numbers - 3If so, we need to decide how to compare two floating-point numbers so that the result of the comparison is more... ummm... predictable. So, we have already learned rule No. 1 when comparing real numbers: never use ==floating point numbers when comparing real numbers. Ok, I think that's enough bad examples :) Let's look at a good example!
public class Main {

   public static void main(String[] args)  {

       final double threshold = 0.0001;

       //add 0.1 to zero eleven times in a row
       double f1 = .0;
       for (int i = 1; i <= 11; i++) {
           f1 += .1;
       }

       // Multiply 0.1 by 11
       double f2 = .1 * 11;

       System.out.println("f1 = " + f1);
       System.out.println("f2 = " + f2);

       if (Math.abs(f1 - f2) < threshold)
           System.out.println("f1 and f2 are equal");
       else
           System.out.println("f1 and f2 are not equal");
   }
}
Here we are essentially doing the same thing, but changing the way we compare the numbers. We have a special “threshold” number - 0.0001, one ten-thousandth. It may be different. It depends on how precise a comparison you need in a particular case. You can make it larger or smaller. Using the method, Math.abs()we obtain the modulus of a number. The modulus is the value of a number regardless of sign. For example, the numbers -5 and 5 will have the same modulus and be equal to 5. We subtract the second number from the first, and if the resulting result, regardless of the sign, is less than the threshold that we set, then our numbers are equal. In any case, they are equal to the degree of accuracy that we established using our “threshold number” , that is, at a minimum they are equal down to one ten-thousandth. This method of comparison will save you from the unexpected behavior that we saw in the case of ==. Another good way to compare real numbers is to use a special class BigDecimal. This class was specifically created to store very large numbers with a fractional part. Unlike doubleand float, when using BigDecimaladdition, subtraction and other mathematical operations are performed not using operators ( +-, etc.), but using methods. This is what it will look like in our case:
import java.math.BigDecimal;

public class Main {

   public static void main(String[] args)  {

       /*Create two BigDecimal objects - zero and 0.1.
       We do the same thing as before - add 0.1 to zero 11 times in a row
       In the BigDecimal class, addition is done using the add () method */
       BigDecimal f1 = new BigDecimal(0.0);
       BigDecimal pointOne = new BigDecimal(0.1);
       for (int i = 1; i <= 11; i++) {
           f1 = f1.add(pointOne);
       }

       /*Nothing has changed here either: create two BigDecimal objects
       and multiply 0.1 by 11
       In the BigDecimal class, multiplication is done using the multiply() method*/
       BigDecimal f2 = new BigDecimal(0.1);
       BigDecimal eleven = new BigDecimal(11);
       f2 = f2.multiply(eleven);

       System.out.println("f1 = " + f1);
       System.out.println("f2 = " + f2);

       /*Another feature of BigDecimal is that number objects need to be compared with each other
       using the special compareTo() method*/
       if (f1.compareTo(f2) == 0)
           System.out.println("f1 and f2 are equal");
       else
           System.out.println("f1 and f2 are not equal");
   }
}
What kind of console output will we get?

f1 = 1.1000000000000000610622663543836097232997417449951171875
f2 = 1.1000000000000000610622663543836097232997417449951171875
f1 и f2 равны
We got exactly the result we expected. And pay attention to how accurate our numbers turned out, and how many decimal places fit into them! Much more than in floatand even in double! Remember the class BigDecimalfor the future, you will definitely need it :) Phew! The lecture was quite long, but you did it: well done! :) See you in the next lesson, future programmer!
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION