The real values \u200b\u200bof the parameters in Delphi are given. Structural data types. An integer data type in Delphi is represented


Non-obvious features of real numbers To take on this article I prompted the questions from time to time questions at a round table caused by the misunderstanding of the internal representation of real numbers. Once a description of the internal representation of such numbers was an integral part of any serious programming book, but now the authors have more interesting items for discussion: COM / DCOM, ActiveX, OLE and much more. On real numbers simply lacking space. And people who have started programming with Delphi and have no experience in older media, often turn out to be completely helpless in front of the incomprehensible behavior of a program containing fractional calculations. I hope my article will overcome the light on these questions and make the behavior of fractions more predictable.
Binary fractions
For a start - a little mathematics. In school, we pass two types of fractions - simple and decimal. Decimal fractions, in fact, are a decomposition of a number in degrees of ten. So, recording 13.6704 means a number equal to 1 * 10 1 + 3 * 10 0 + 6 * 10 -1 + 7 * 10 -2 + 0 * 10 -3 + 4 * 10 -4. But the internal representation of all numbers in the computer, including real - not decimal, and binary. Therefore, binary fractions are used. They are largely similar to decimal, but they have a degree of degree. So, the number 101.1101 \u003d 1 * 2 2 + 0 * 2 1 + 1 * 2 0 + 1 * 2 -1 + 1 * 2 -2 + 0 * 2 -3 + 1 * 2 -4. That is, in the decimal representation, this number is 5.8125, which is not difficult to make sure that any calculator. Now let's remember the science format of the decimal number. The first in this record is a sign of a number - plus or minus. Then there is a so-called mantissa - a number from 1 to 10. Then there is an exhibitor - the degree of ten to which the Mantissa must be multiplied to get the desired number. So, already mentioned number 13.6704 will be recorded in this format as 1.36704 * 10 1 (or 1.36704e1 according to the rules adopted in the computer). If the recorded number is less than the unit, the exhibitor will be negative. Similar recording exists in the binary system. Thus, 101.1101 will be recorded in the form of 1.011101 * 10 10 (a binary form of recording was used everywhere, so 10 10 means 2 2). This is the view used in the computer. The binary point in such a record does not remain in one place, and shifts the value indicated in the exhibitor, so such numbers are called floating point numbers (floating point numbers).
Types of Delphi
In Delphi there are four real types: Single, Double, Extended and Real. Their general format is the same: the sign of the Mantissa Exhibitor Sign is always one bit. It is zero for positive numbers and a unit for negative. As for the size of the mantis and the exhibitors, it is in them a distinction between types. Before switching to specific numbers, consider in more detail the type Real, making a small excursion to this. Real is the standard type of Pascal language, which was present there initially. When Pascal was created, the processors had no built-in support for real numbers, so all operations on this type were reduced to operations with integers. Accordingly, the size of the fields in the REAL type was selected as to optimize these operations. Intel 8086/88 microprocessor and its improved options - 80286 and 80386 - also did not have hardware support for real numbers. But systems on the basis of these processors had the possibility of connecting the so-called coprocessor. This microcircuit worked with memory through the main processor tires and provided hardware support for real numbers. In the middle hand systems, the nest of the coprocessor was usually empty, since it lowered the system (of course, insert the coprocessor there was no problem). For each central processor, their coprocessors were produced, labeled Intel 8087, 80287 and 80387, respectively. There were even coprocessors produced by other firms. They worked faster than Intel "oski, but appeared on the market later. The type of real numbers supported by coprocessors does not coincide with REAL. To provide in its systems support for coprocessor types, Borland enters into Turbo Pascal Single, Double and Extended Types. Extended - It is a native type for the coprocessor, and the types of Single and Double are obtained from it a very simple truncation. When loading the size of the Single or Double type, the latter converts them to the internal register. On the contrary, when unloading the numbers of these types from the register to memory, the coprocessor fees them to The desired size. The internal operations are always executed with the data type extended (however, from this rule there is an exception on which we will focus later, after a detailed consideration of the format of various types). Single and Double are used to save memory. None of them also coincides with the type REAL. In systems with coprocessors, new types are processed markedly (2-3 times) faster, than REAL (this is taking into account the fact that the type REAL after the corresponding transformation was also processed by the coprocessor; If we compare the processing of the type extended on the machine with a coprocessor and real on the machine without a coprocessor, then there is a difference between 100 times the difference is achieved on individual operations). For programs with these types can be performed in systems without coprocessor, it was possible to connect a coprocessor software emulator to them. Processing these types by emulator was slower than Real processing. Starting from the 486th processor Intel takes a course on the integration of the processor and the coprocessor in one microcircuit. The percentage of marriage in chips is too large, so Intel goes to a trick: if the chips have marriages only in the coprocessor part, then jumpers blocking the coprocessor are burned on this chip, and the microcircuit is sold as a 80486SX processor, which has no built-in coprocessor (unlike the full version, which was called 80486dx). There were also inverse situations when the coprocessor of damage did not have, but the processor was inoperable. Such chips turned into a "coprocessor 80487". But this is from the field of exotic, and, as far as I know, this coprocessor has not reached Russia. Pentium processor in all its versions had a built-in coprocessor. Thus, with the arrival of this processor, the type Real became a burden, and Single, Double and Extended came to the fore. To minimize the necessary user alterations, Borland introduced a new compiler directive: ($ realcompatibility on / off). The default is off, which means the absence of full compatibility. In this case, the type of REAL in Delphi coincides with the Double type. If compatibility is included, the Real type coincides with its prototype from Pascal. There is also a type of REAL48, which is always, regardless of the settings, coincides with the old REAL. Further in this article under the word "REAL" I will always imply an old type. I note that all this appeared only in Delphi 4, in earlier versions, the type Real48 was absent, and the type REAL was always old, six. So now you can finally get to the size of the fields.
A type Size type, byte Mantissa size, bit Exhibitor size, bit
Single 4 23 8
Double. 8 52 11
Extended 10 64 15
Real 6 40 7
Other parameters of real types, such as a range and accuracy, are in the Delphi Help, in which I recommend to look more often. Consider the type of Single, as it is the shortest and, consequently, the simplest for understanding. The remaining types differ from it only quantitatively. In the future, the number in Single format, we will record as s Eeeeeeeee Mmmmmmmmmmmmmmmmmmmmmmm, where S means an iconic bit, E - bits exhibitors, M - Mantissa bit. It is in this order that these bits are stored in a four-bit value (there is a permutation of bytes; I remind you that in the Intel processors, bytes are rearranged in multibal values \u200b\u200bso that the younger byte goes first, and the older is the last). A binary number is stored in the Mantissa. To obtain the true meaning of the mantissa, it is necessary to mentally add the left side to it with a point (that is, for example, Mantissa 10100000000000000000 means binary fraction 1.101). Thus, having 23 binary discharge, we record numbers with an accuracy of the 24th binary discharges. Such a record of the number is called normalized. Exhibitor by definition is always an integer. But the method of recording exponentials in real numbers does not coincide with the usual way to record numbers with a sign. Zero in this view is recorded as 01111111. In the usual representation, this is 127. respectively, 10,000,000 (128 in the usual representation) means one, and 01111110 (126) means -1, and so on (that is, from a conventional unsigned number it is necessary to subtract 127 from a conventional unsolved number. And it turns out the number encoded in the exhibitor). Of the rules described above there are exceptions. So, if all the bits of the exhibitors are zero (i.e. there is a number -127), then you need to add not "1.", and "0." before it should be added to the Mantissa (denormalized entry). This allows you to increase the range of real numbers. If this exception would not be, the minimum possible positive number of type Single would be approximately 5.9 * 10 -39. And so the ability to use numbers to 1.4 * 10 -45 appears. The side effect of this is that numbers smaller than 1.17 * 10 -38 are lower than 24 binary discharge, accuracy. If all bits in the exhibitor are equal to one, and in the matrix - zero, then we get a combination, known as the INF (from English infinity - infinity). This combination is used when the result of the calculations exceeds the maximum number allowat. Depending on the value of the bit S infinity can be positive or negative. If, with such an exhibitor in the Mantissa, at least one bit is not equal to zero, such a combination is called NaN (not a number is not a number). Attempts to use NAN or INF combinations lead to a runtime error. To specify zero, all bits of mantissas and exhibitors should be zero (formally it means 0 * 10 -127). Taking into account the rules described above. If at least one bit of the exhibit will be zero (i.e. the exhibitor will be more -127), the recording will be considered normalized, and the zero martissa will be considered as a unit. Therefore, no other combinations of mantissa and exhibit values \u200b\u200bcannot give zero. The type of double is arranged in the same way, the difference is only in the number of discharges and in what value the exhibitors takes place for zero. So, we have 11 discharges for exhibitors. The value of 1023 is taken for zero. There are several otherwise arranged extended. In addition to quantitative differences, one high quality is added: the first discharge is clearly indicated in the Mantissa. That is, Mantissa 1010 ... Interpreted as 1.01, and not as 1.101, as it was in Single and Float types. Therefore, if the 23-bit Mantissa type Single provides 24-character accuracy, and the 52-bit Double Mantice is 53-bit, then the 64-bit Mantissa Extended provides 64-, and not 65-bit accuracy. Accordingly, with a denormalized form of recording form, the first category of the Mantissa explicitly contains 0. For zero, the exhibit is the value of 16383. The type REAL, as already mentioned, is a mansion. First, it uses another order of the bits, and, secondly, the denormalized form is not used. I did not work in detail with the type Real, because now it is necessary for historians, but not to programmers. Let me remind you, a little higher, I said that the coprocessor always performs all the operations in the extended format, rebelled at the same time that there is an exception to which I will return a little later. If you have read everything outlined above, now you have enough knowledge to understand what is the exception. The coprocessor has a special two-byte register, called the control word. The installation of individual bits of this register dictates the coprocessor of this or that behavior. First of all, this is due to what exceptions can be excited by the coprocessor. Other bits of this register are responsible for how the numbers will be rounded, as the coprocessor understands Infinity - all this can be obtained from the Intel documentation. We will also be interested in only two bits from this word - the eighth and ninth. It is they define how the numbers inside the coprocessor will be treated. If the eighth bit contains a unit (so established by default), then ten bytes of the internal registers of the coprocessor will be used completely, and we will get "full" extended. If this bit is zero, then everything is determined by the value of the bit 9. If it is equal to one, then only 53 of the mantissa discharge (the rest are always zero). If this bit is zero - only 24 mantissa discharge. This increases the calculation rate, but reduces accuracy. In other words, the accuracy of the speed of the coprocessor can be lowered to the type of double or even Single. But this also applies to the Mantissa, the exhibitor in any case will contain 15 bits, so that the extended type range is saved anyway. To work with the control word of the coprocessor in the System module, the Default8087CW variable is described: Word and the SET8087CW procedure (CW: Word). When you start the program, the DEFAULT8087CW variable records the control word that set the system when starting the program. The set8087cw feature writes a new value to the control word. At the same time, this new value is recorded in the Default8087CW variable. This behavior of this function is not always convenient - sometimes it is necessary to save the old value of the Default8087CW variable (however, it is easy to do, the installation of an additional variable). On the other hand, if the value of the control word is changed, without using SET8087CW (and in the future we will see that such changes can occur in addition to our will), then using the Default8087CW function, it is simply no possibility to learn the current value of the control word. In Delphi 6 and above, the GET8087CW function appeared, which allows you to know the value of the control word, and not a variable default8087cw. In earlier versions, the only way to get the meaning of this word is the use of an assembler, especially since Delphi has no problems with assembler inserts. You can set the value of the control word using the FLDCW command, read - using FNSTCW. Both of these commands have one argument - a variable type Word. To, for example, set the 53-digit accuracy without changing the other bits of the control word, you need to perform such a sequence of commands: ASM FNSTCW MYCW AND MYCW, 0FCFFH OR MYCW, 200H FLDCW MYCW END; Modern coprocessors are treated with such a speed that, with ordinary calculations, it is unlikely that it may be necessary to accelerate due to accuracy - the winnings will be negligible. This feature is mainly used in cases where the floating point calculations constitute a significant part of the program, and high accuracy has no fundamental value (for example, in the engines for 3D games). However, forget about this feature of the work of the coprocessor is not worth it, because it can prevent one unpleasant surprise, which is a little later.
Infinite fractions
From school, we all remember that not every number can be recorded by a finite decimal fraction. The infinite fractions are two species: periodical and non-periodical. An example of a non-periodic fraction is the number "PI", periodical - the number 1/3 or any other simple fraction, not representable in the form of a finite decimal fraction. For those who forgot mathematics, remind you that periodic fractions are such fractions that contain an infinitely repeated sequence of numbers. For example, 1/9 \u003d 0.083333333 ..., 0.08333333 ..., 1/7 \u003d 0.142857142857 ... Brackets are used to record such numbers - they enter them a repeating part. The same numbers should be recorded as follows: 1/9 \u003d 0.1 (1), 1/12 \u003d 0.08 (3), 1/7 \u003d 0.1 (428571). However, it was a slight retreat. We are not interested in the question of the periodicity or non-periodicity of the number, it is enough for us to know that not all numbers can be represented in the form of a finite decimal fraction. When working with such numbers, we always use not accurate, but an approximate value, so the answer is also approximate. This must be taken into account in your calculations. Until now, we talked about only decimal infinite fractions. But binary fractions can also be infinite. Even more than one, any number expressed by the final binary fraction may also be expressed and a decimal finite shot. But there are numbers (for example, 1/5) which are expressed by the final decimal fraction, but cannot be expressed by the final binary fraction. It greatly complicates the life of the programmers. Next, we consider examples in which real types behave inexplicably for a person who is not familiar with the internal format of their record. Each such example will be given an explanation. I hope that the study of these examples will help to understand which pitfalls wave a programmer using real types, and how these stones bypass. All examples are built in the same way: you need to throw two components on the form - the tag (TLABEL) and the (TButton) button. Since these are just examples, I did not come up with names for these components, let it be called Button1 and Label1. The Button1Click handler contains some code whose results are displayed via Label1. Thus, you need to start the program, click on the button and see what will be written in the label. I will only give the Button1Click handler code, since everything else is trivial. Let me remind you that in Pascal, it is allowed not to put a point with a comma in front of the end "Ohm (with the exception of END" and in the class description) and before the Until "Ohm. I prefer to use this opportunity, so I don't need to poke my finger in me that I forget to put points with a comma.

Example FIRST - "Incorrect value"

So, write this code: VAR R: Single; Begin R: \u003d 0.1; Label1.caption: \u003d Floattostr (R) END; What do we see when you click the button? Of course, not "0.1", otherwise it would not make sense to write this example. We will see "0.100000001490116". That is, the discrepancy in the ninth meaning figure. Well, from the help of Delphi, we know that the accuracy of the type of Single is 7-8 decimal discharges, so that at least no one is deceiving. What is the reason? Just the number 0.1 is not representable in the form of a finite binary fraction, it is 0.0 (0011). And this endless binary fraction will be engaged on the 24th signs; We do not get 0.1, but some approximate number (which is exactly see above). And if we assign a variable R not 0.1, and 0.5? Then we get on the screen 0.5, because 0.5 is represented in the form of a finite binary fraction. Slightly experimenting with different numbers, we note that those numbers that are expressed in the form of M / 2 N, where M, N are some integers (of course, n should not exceed 24, otherwise we do not have enough accuracy of type Single) . As an exercise, I propose to prove that any integer for a record of which is enough of the 24-y binary discharges, can be exactly transmitted by the Single type.

Example Second - Comparison

Now change the code like this: VAR R: Single; Begin R: \u003d 0.1; IF R \u003d 0.1 Then label1.caption: \u003d "equal" else label1.caption: \u003d "Not equal to" END; When you click the button, we will see the inscription "not equal." At first glance, it seems absurd. Indeed, we already know that the variable R gets the value of 0.100000001490116 instead of 0.1. But "0.1" in the right part of equality should also be transformed according to the same laws, because everything is predetermined on the computer. It is time to remember that Intel processors work only with a 10-byte type of extended, therefore the left, and the right side of equality is first converted to this type, and only then a comparison is made. Then the root number, which turned out to be in the variable R instead of 0.1, although it looks scary, but it seems in the form of a finite binary fraction. The information about the fact that it actually should mean "0.1", has not survived anywhere. When converting this number in Extended junior, redundant compared to the Single type of mantissa discharges are simply filled with zeros, and we will again get the same number, just recorded in the extended format. And "0.1" from the right side of equality is converted to extended without intermediate transformation into Single. A 0.1 is infinite in binary representation. Therefore, some of the younger discharges of the Mantissa will contain units. In other words, we will get although not exact representation of the number of 0.1, but still closer to the truth than 0.100000001490116. Because of such cunning transformations, it turns out that we compare two close, but still not equal numbers. From here - a natural result in the form of inscriptions "not equal." This is appropriate an analogy with decimal fractions. Suppose in one case we divide 1 to three with an accuracy of three characters, and we get 0.333. Then we divide 1 to three with an accuracy of four characters, and we get 0.3333. Now we want to compare these two numbers. To do this, give them accuracy of four categories. It turns out that we compare 0.3330 and 0.3333. Obviously, these are different numbers. If you try to replace the number 0.1 at 0.5, then we will get "equal." I think you already know why, but for the completeness of the text I will explain. 0.5 is the ultimate binary fraction. With direct bringing it to the type of extended in junior discharges are zeros. Exactly the same zeros are in these discharges when transforming a number of 0.5 type Single in the Extended type. Therefore, as a result, we compare two numbers. It seems like if we divided 1 to 4 with an accuracy of three to four significant digits. In the first case, they would receive 0.250, in the second - 0.2500. Lowing them both to accuracy in four signs, we obtain a comparison of 0.2500 and 0.2500. Obviously, these numbers are equal.

Example Third - Comparison of different types

A little complicate our example: VAR R1: Single; R2: Double; Begin R1: \u003d 0.1; R2: \u003d 0.1; IF R1 \u003d R2 Then label1.caption: \u003d "equal" else label1.caption: \u003d "not equal to" END; Scientists with bitter experience, you probably expect to see the inscription "not equal." Well, life will not disappoint you, this is what you will see. Double type is more accurate than Single (although its accuracy is also not enough to represent infinite fractions). In R2 we will receive not 0.100000001490116, but another number, with an accuracy of 15-16 decimal signs. I can't call exactly this number because Floattostr perceives it as 0.1, so, replacing the Single in the first example on a double, you will see 0.1 (only do not decease, it's not 0.1, just a function Floattostr has such a feature of work) . The numbers in both variables are given to the type extended, but at the same time they do not change and, as they were not equal, and remain unequal. This reminds the situation when we compare 0.333 and 0.3333, leading them to exactly five characters: numbers 0.33300 and 0.33330 are not equal. I already embarrass you to bother you with such obvious remarks, but still: if in this example, replaced 0.1 at 0.5, we will see "equal."

Example fourth - subtraction in the cycle

Consider another example illustrating the situation that often puzzles the novice programmer VAR R: Single; I: integer; Begin R: \u003d 1; For i: \u003d 1 to 10 DO R: \u003d R-0.1; Label1.caption: \u003d Floattostr (R) END; Of course, if as a result of the execution of this example, you would see zero, I would not spend time at him. But on the screen it will appear -7.3015691270939E-8. I think this turnover does not surprise anyone. We already know about the fact that the number 0.1 cannot be transmitted exactly in any of the real types, and the Single conversion in extended and back. At the same time rounding is constantly occur, and these roundings lead to what we get as a result not zero, but "almost zero".

Example Fifth - Surrupiriz from Microsoft

Change in the previous example, the type of R variable with Single on Double. The value displaced by the program will be 1.44327637948555E-16. It is a logical and predictable result, since the type of double is more accurate than Single and, therefore, all calculations are more accurate, we simply must get a more accurate result. Although, of course, absolute accuracy (that is, zero), for us remains an unattainable ideal. And now - the question is on the backfill. Will the result change if we replace double to more accurate extended? The answer is not so unequivocal, how I would like to see. In principle, after such a replacement, you must get -6.7762635780344e-20. But in some cases, the replacement of Double to the extended result will not change, and you will again get 1.44327637948555E-16. It depends on the operating system. It's all about using "defective" extended. When you start the program, any system sets such a control word of the coprocessor so that Extended has been full. But then the program causes many different Windows API functions. Some kind of (or some) from these numerous functions in incorrectly work with the control word, changing its value and not restoring when exit. Such a problem is found mainly in Windows 95 and old versions of Windows 98. There are also information that the control word can be cleaned and in Windows NT, and the effect is not immediately observed after installing the system, but only after a while, after the installation of others programs. The problem is precisely in the incorrect behavior of system functions; The value of the control word installed by the system when the program starts, is always the same. This problem is known: for example, in the original VCL codes, you can find the storage of the control word of the coprocessor before calling some API functions with its subsequent recovery. Comments report that the function can change the value of the control word, so it is necessary to save and restore it. Thus, we arrive at disappointing conclusion: to those problems with real numbers, which are due to the features of their hardware implementation, more and bugs are added. True, it is pleasing that recently these bugs are extremely rare - apparently, new versions of the system behave more responsibly. However, it is impossible to completely exclude such an opportunity, especially if your program will be used on outdated technique with outdated systems (for example, in educational institutions whose financing leaves much to be desired). In order for our example, it always issued the correct value -6.7762635780344E-20, it is enough to put at the beginning of our SET8087CW (get8087cw or $ 0100), and the program in any system will use the coprocessor in maximum accuracy mode. (If you use old versions of Delphi, this string can be replaced with SET8087CW (default8087cw), unless, of course, the default values \u200b\u200bof other control words are satisfied with you.) Since we started talking about the management word, let's experiment a little experiment with him. Change the first line on Set8087CW (Get8087CW and $ FCFF OR $ 0200). Thus, we translate the coprocessor into the mode of the 53rd gantissy representation accuracy. Now in any system we will see 1.44327637948555E-16, despite the use of extended. If we change the first line on the set8087cw (get8087cw and $ fcff), then we will operate in 24-ydot accuracy mode. Accordingly, there will be a result in any system -7.3015691270939E-8. Note that when booting into a 10-byte register of the approximate number of type extended in the reduced accuracy mode, the "extra" bits are not reset. Only the results of mathematical operations are presented with reduced accuracy. In addition, when comparing two numbers, all bits are also taken into account, regardless of accuracy. Therefore, the VAR R: Double code; // or Single Begin R: \u003d 0.1; IF R \u003d 0.1 Then label1.caption: \u003d "equal" else label1.caption: \u003d "Not equal to" END; When choosing any accuracy, will give "not equal."

Example Sixth - Machine Epsilon

When we are dealing with computing with limited accuracy, such a paradox occurs. Let, for example, we consider up to three significant digits with an accuracy. We add to the number 1.00 number 1.00 * 10 -4. If everything was honest, we would get 1.0001. But we have accuracy limited, so we are forced to round up to three meaningful numbers. The result is 1.00. In other words, we add some number to one, more zero, and as a result, due to limited accuracy, we get a single one again. The smallest positive number, which, when adding it to one, gives a result that is not equal to one, is called machine epsilon. The concept of machine episilon from newcomers is often confused with the concept of the smallest number, which can be recorded in the selected format. It is not right. Machine Epsilon is determined only by the size of the mantissa, and the minimum possible number is significantly less due to the shift of the floating binary point using the exponent. Before you look for a machine epsilon programmatically, try to find it from theoretical considerations. So, the Mantissa type extended contains 64 discharge. To encode a unit, the older bit of the mantissa must be equal to 1 (denormalized entry), the remaining bits are zero. Obviously, with such a record, the smallest of the numbers for which the X\u003e 1 condition is obtained, when the youngest bit of mantissa will also be equal to one, i.e. x \u003d 1.00 ... 001 (in binary representation; between the point and the youngest unit of 62 zero). Thus, the machine epsilon is X-1, i.e. 0.00 ... 001. In a more familiar decimal form of recording, it will be 2 -63, i.e. Approximately 1.084 * 10 -19. Now write a program for finding machine epsilon. VAR R: Extended; Begin R: \u003d 1; While 1 + R / 2\u003e 1 DO R: \u003d R / 2; Label1.caption: \u003d Floattostr (R) END; As a result, the number 1.0842021724855E-19 will appear on the screen in full compliance with theoretical calculations (if your system is present above the bug with the transfer of a processor into a reduced accuracy mode, instead of the number you get 2.22044604925031e-16, i.e. 2 -52 . To this not happened, correct the value of the control word). Now replace the extended type on double. The result will not change. On Single - again will not change. But such behavior only at first glance may seem strange. Let's consider the expression 1 + r / 2\u003e 1. So, all the calculations (including comparison) the coprocessor performs with the data of the type extended. The sequence of actions is as follows: the number R is loaded into the register of the coprocessor, which is converted to the extended type. It is further divided into 2, and then 1 is added to the result, and all this in Extended, no reverse conversion in Single or Double occurs. This number is then compared with unit. Obviously, the result of comparison should not depend on the source type R. In this article, I tried to explain the internal device of real numbers from the point of view of Intel processors and mention some of the problems that are connected with them. In fact, all the problems are reduced to two: first, not any real number can be represented accurately, and, secondly, not any real number, representable in the form of a finite decimal fraction, represents in the form of a finite binary fraction. The second problem, probably, brings more trouble to novice users, as it is less obvious. I consciously pretend to overcome these problems, since the optimal version depends very much on a specific task. The person who solved the causes of the emergence of problems will not be difficult in each particular case to choose the most acceptable solution. In this, in fact, the difference between the programmer and Lamer is: the first one is dealt in the task and finds a solution for it, the second can only throw on the form of ready-made components and eradicate the pieces of someone else's code. And I wrote this article for novice programmers, and not for beginner Lamers, from here and such a style.

Thank you so much by Elena Philippova for help finding information.

Discussion of material [01-07-2019 03:46] 77 posts

Now let's discuss Delphi data types that the programmer uses when writing a program. Any program on Delphi may contain data from many types:

  • whole and fractional numbers
  • symbols
  • strings of characters
  • logic values.

The Delphi language library includes 7 entire data types: shortint, smallint, longint, int64, byte, word, longword, whose characteristics are shown in the table below.

In addition, in support of the Delphi language, there are 6 different real-type (Real68, Single, Double, Extended, Comp, Currency, which differ from each other, first of all, by range of permissible values, by the number of significant digits, by the number of bytes, which It is necessary to store some data in the PC memory (characteristics of real types are shown below). Also, the Delphi language library includes the most versatile real type - type Real, equivalent to double.

Symbol type Delphi.

In addition to numeric types, Delphi has two symbolic types:

Type anSichar. - symbols with ANSI encoding, they are set in accordance with the number from 0 to 255;

Type WideChar. - Symbols with Enicode encoding, they are put in touch with the number from 0 to 65 535.

String type Delphi.

The string type in Delphi is indicated by the String ID. In the Delphi language, three string types are presented:

Type shortstring - inherent in the pC statically located in the PC memory, the length of which changes in the range from 0 to 255 characters;

Type longstring - this type is dynamically posted in the PC memory string with a length limited only by the amount of free memory;

Type WideString - Data type used in order to keep the necessary sequence of international characters, similar to whole proposals. Any string character having a type of WideString is a unicode symbol. Unlike the type of shortstring, the WIDESTRING type is a pointer that refers to variables.

Now that we already know the basis of the language and can even write small programs, you can go to a detailed study of the Delphi opportunities provided to work with the most different data types - arrays, recordings, sets, etc. Here we will consider issues of converting data types.

Custom data types

When developing programs, there is not enough of those data types that are presented by the programming language. For example, it is convenient to combine in one variable a number of one-type data at once, or provide storage of data from different types, such as strings and numbers. Fortunately, Object Pascal has the opportunity to create its own data types based on the already existing, combining them, or combining. For example, arrays (arrays) are used to create an ordered list of the same type of data, and to combine multiple types to one - Records.

Another aspect of the application of its own types of data that simplify the programming process and makes it a more understandable person is to use the set - a named set of several possible values.

Creating a particular type of data always begins with a declaration, or descriptions of a new data type. This is done in the header of the program or the module and starts with the keyword type. After a new data type is defined, you can create variables of a new type - in the same way as any simple. Well, the features of working with one or another user type of data are determined by what kind of type it belongs (array, recording, etc.), and in itself its implementation (size and dimension for - arrays, a set of other types - for records and t .P.).

Arrays

An array is an ordered structure consisting of a variety of uniform elements that have a common name. Thus, with just one variable, you can store a whole set of data, and each element of the array has its own index, or indexes in case the multidimensional array. The array is declared in Object Pascal using the ARRAY keyword, optional square brackets indicating the number of array elements, and data type for the elements of the array:

Array [indexes] of;

The number of indexes determines the dimension of the array. So, for a one-dimensional array (in mathematics - vector), only one index is required, and 2 index will be needed for a two-dimensional array (matrix). An arrays can look like this:

Type Myarray1 \u003d Array of Integer; Type Myarray2 \u003d Array of Integer;

In the first case, a one-dimensional array is defined by 100 elements of the type of integer numbers, in the second - a two-dimensional array of 10 to 10 dimension, i.e. Also on 100 elements of the type of integers. After the array is determined, you can create variables of the appropriate type:

VAR A1: MYARRAY1;

Another embodiment of the array is the simultaneous announcement of both the variable and the array descriptions:

VAR A1: Array of Integer;

If the desired size of the array is unknown in advance, then dynamic arrays can be used. They can be determined both with a preliminary ads of type, and without that - it is enough to create a variable and specify an array as its type:

VAR DYNARRAY: Array of Integer;

However, in order to proceed with the use of a dynamic array, it is first necessary to allocate a place in memory for it. This is done using the SetLength procedure, specifying the name of the dynamic array and the required memory size:

SetLength (Dynarray, 10);

The advantage of the dynamic array is that in the course of execution of the program, the amount of memory allocated under an array can be changed.

To appeal to a specific array element, an index (or indexes) of the element in the array is used. Thus, the first element in an array of type MYARRAY1 (Array) has an index equal to 1, and the last - 100. Accordingly, to refer to the first element of the array, let's say in order to assign the value to it, records of this type are used:

A1: \u003d 10;

Here we have appropriated an element of an array A1 with an index 1 Value 10. Data reading is made in the same way:

In this case, the variable x will be assigned the value of the 1st element of the A1 array. It is important that 2 conditions are complied with: first, that element of the array to which the appeal is to have, should exist. Those. If you appeal to the 11th element of an array consisting of 10 element, an error will be error to access memory. Secondly, the type of data assigned must coincide. In other words, if the array is defined as an integer, then only integer values \u200b\u200bcan be assigned. With the opposite situation (data reading), the rules are somewhat softer, since an integer can be assigned to with a variable as an integer and real type.

At the same time, an important (and extremely useful) feature of the arrays is that they can keep in them the data of any type, including both any simple and structural type, including records, objects and other arrays. In fact, a two-dimensional array can be described as a one-dimensional array, each element of which is also a one-dimensional array. From this point of view, it becomes clear why 2 types of ads and access to elements are allowed for two-dimensional arrays, and both are completely interchangeable and similar in meaning:

VAR A1: Array of Integer; VAR A2: Array of Array of Integer; ... A1: \u003d 5; A2: \u003d 5; A1: \u003d 5; A2: \u003d 5;

In this example, both declared arrays (A1 and A2) are completely identical. Similarly, appeals to elements of arrays - in both cases you can use both syntax with individual values \u200b\u200bof indexes and with indexes listed through the comma. Not only static arrays may be multidimensional, but also dynamic, while the 2nd version of the announcement is used. For example, a two-dimensional dynamic array of real numbers is declared as follows:

VAR DYNARRAY: Array Of Array Of Real;

When you allocate memory for such an array, it should also be taken into account its dimension. For example, if an array of 10 per 20 elements is required, you should use the SetLength procedure with the following parameters:

SetLength (Dynarray, 10, 20);

At the end of acquaintance with the arrays, we have left to consider the concentrated example of using cycles to fill the array. Suppose we have a simple array vector for 10 integers:

VAR MYARRAY: Array of Integer;

Thus, if we need to fill it with the values \u200b\u200bfollowing in order (say, numbers from 10 to 100 in step 10), then instead of sequentially assigning its value to each element, you can use the following cycle:

For i: \u003d 1 to 10 Do Myarray [i]: \u003d I * 10;

Here, the variable I, which is a cycle counter, with each iteration it is consistently increased by 1. As a result, each element of the MYARRAY array obtains the value of this variable multiplied by 10, i.e. 10, 20, 30, 40, etc. - What we needed. To make sure that it is at the same time to demonstrate a cycle to read data from the array, turn to the example shown in Listing 5.1 (on the CD, this example is in the Demo \\ Part1 \\ ArrayFor folder):

Listing 5.1. Recording and reading the data of arrays in the cycle

Program ArrayFor; ($ APPTYPE CONSOLE) VAR MYARRAY: Array of Integer; I: integer; Begin for i: \u003d 1 to 10 Do Myarray [i]: \u003d I * 10; // Filling an array for i: \u003d 1 to 10 Do Writeln (MYARRAY [I]); // Conclusion of the READLN array; // Waiting for input to prevent closing the End window.

Regarding arrays, one more note can be made: one data type already known to us - rows (string) can be viewed as an array of characters (CHAR). At the same time, no preliminary transformations are required: if we already have a string, you can always refer to its separate symbol as an element of the array:

VAR S: String; C: CHAR; ... S: \u003d Moscow; C: \u003d S;

In this case, the variable C will receive the letter "M" as its value, i.e. The first line symbol.

Set

Sometimes it is convenient to limit the possible values \u200b\u200bof the variable only part of the values \u200b\u200bfrom a plurality of all values \u200b\u200ballowed by its type. Suppose we need a variable type CHAR, which can only be values \u200b\u200bfrom lowercase latin characters. In this case, we use such a means as a subset, in this case, a subset of characters from a to z. You can determine it like this:

Type smletter \u003d a..z;

Thus, we received a new data type - SMLETTER, which is a "trimmed" type of char. When using this type of data, the variables of the SMLETTER type will not be able to receive values \u200b\u200bthat go beyond the specified range:

VAR A: SMLETTER; ... A: \u003d; // everything is right here, because Small B enters a subset A..z A: \u003d B; // error! Registration B is not included in a subset A..z

Practical candidates for subsets may be variables intended for storing individual dates (say, from 1 to 12 for months and from 1 to 31 - for days of the month), to check the values \u200b\u200bthat the program receives as a result of the user's input from the keyboard, and T .. It should only be borne in mind that subsets, for obvious reasons, can belong only to ordinary data types - whole numbers or symbols.

The development of subsets are sets, or sets (sets). They, like subsets, can be allocated to separate data types, but the allowable ranges of their values \u200b\u200bare somewhat different. First of all, a keyword is used to determine the set:

If you draw an analogy with the previous example, the range of lowercase Latin characters can be defined as follows:

Type Letters \u003d Set of Shar; VAR A: Letters; ... a: \u003d;

Thus, the definition of the set consists of two stages: the type is first determined on the basis of which a subset (Letters) is built, then the variable of this type (a) is declared, and the variable is already applied to the variable. The advantage here is that, firstly, in the course of the program, you can change the permissible ranges of values, and secondly, the ranges themselves are much more flexible. In particular, they may contain in themselves both rows of individual values \u200b\u200band subsets, or their combinations in any sequence. For example, if we need to highlight only some characters, say, up to k, as well as numbers 1, 3, 4, 5 and 9, the definition of the group will be as follows:

Type Letters \u003d Set of Char; VAR A: Letters; ... a: \u003d;

To check whether this or that value is a member of the set, the IN operation is used. For example, to check whether the user-entered symbol refers (we denote it as a variable "C") to the set A, there is enough such an expression:

IF C IN A THEN ...

To demonstrate the work of sets and operations in, we turn to the example shown in Listing 5.2 (Demo \\ Part1 \\ Ranges).

Listing 5.2. In and subset operation

Program Rangeset; ($ APPTYPE CONSOLE) Type Letters \u003d Set of Char; VAR A: Letters; C: CHAR; Begin A: \u003d; // Definition of the READLN group (C); // Reading user entry if c IN A THEN WRITELN (INPUT IS OK!) ELSE WRITELN (INPUT IS WRONG!); readln; // Waiting for input to prevent closing the End window.

Another variety of multiple is the listing. The use of transfer is intended primarily to improve readability (perception) of the program code. Suppose the program needs to repeatedly determine the current keyboard layout, and 3 states are provided - Russian, English and other. Of course, each state can be assigned a digit, say, 1, 2 and 3, respectively, however, in the course of writing the program, every time you have to remember what one or another digit means:

If Keylang \u003d 2 Then ... // After the month, remember that it means 2!

The listed types come to the rescue. They are defined as follows:

= (, ...);

For example, in the embodiment for three keyboard layout values \u200b\u200bwe will get:

Type TKEYLANG \u003d (Klrussian, Klenglish, Klother);

Agree that Klenglish is much clearer than just a number "2". Moreover, on the basis of the list specified in this way, you can create a type-set. For this, after the source type with the range is defined, the type name is determined on its basis:

Type TKEYLANG \u003d (Klrussian, Klenglish, Klother); TKEYLANGS \u003d Set of TKeylang;

ATTENTION
Note that the names of the types begin with the letter "T". Although this is not the requirement of the language, however, start the names of complex types with this letter (from the word type - type) de facto is the standard.

Thus, in the program it will be possible to use no original type in which the composition of the set is determined, and the new type, which is derived:

Var Keylang: TKEYLANGS; ... if Keylang \u003d Klenglish then;

NOTE
Announcement of type-enumerations in 2 stages is the most common practice. At the same time, the original type is determined by the name in the singular (TKEYLANG), and the derivative - in the multiple, with "s" at the end (TKEYLANGS).

Entries

Another useful variety of user data types are records (Records). A record, like an array, can store a whole set of data, but at the same time they should not belong to one type. In other words, if arrays are intended to store any data series, then recording - to combine heterogeneous information under the general name. For example, a record is simply an indispensable data type for storing such information as addresses or person information. And in that and in another case, under the overall name, data should be collected - string, numerical, etc. For example, to address this postal code, the names of the city and the street, as well as the rooms at home and apartments. All this dirty information can be collected under the total sign of one entry.

An entry starts with the keyword Record, followed by the listing of all the elements included in it, called the entry fields, and ends with the key word.

Record :; ...:; end;

With regard to the postal address, the definition of the recording may look like this:

Type Taddress \u003d Record PostIndex: Integer; City: String; Street: String; HOUSENR: Integer; FlatNR: Integer; end;

Appeal to individual entries of records is performed using point notation, i.e. When after the name of the variable, which is a record, put a point, and immediately after the point indicate the name of the field to which you need to appeal. For example, we can create a TADDress type variable and call it, say, MyADDR, after which you fill out someone else's fields using the point to access them:

Var MyAddr: Taddress; ... MyADDR.PostIndex: \u003d 119071; Myaddr.city :\u003d Moscow; In the event that the entry field is another entry, the point is used twice. So, if we have another entry, to store information about the person, then one of its fields will probably have the address, and we already have a suitable data type for this, so you can use it as a field: Type TPerson \u003d Record Name : string; Phone: String; ADDRESS: TADDRESS; end;

Thus, all the TADDRESS fields will also belong to TPerson type, but it is necessary to access them not directly, but through the TPerson recording field of the corresponding type, i.e. Through Address. Such fields are called composite:

Var Anybody: TPerson; ... anybody.name:\u003d Vasya Ivanov; Anybody.address.postIndex: \u003d 119071; Anybody.address.city :\u003d Moscow;

At the same time, you can go otherwise: create a variable of type TADDress, fill it with values, and then assign the type TPerson variable to the corresponding field. In the example shown in Listing 5.3, both methods of working with composite fields are demonstrated, as well as demonstrated that with all fields you can do the same as with separate variables of the same type.

Listing 5.3. Entries

Program Recdemo; ($ APPTYPE CONSOLE) Type Taddress \u003d Record PostIndex: Integer; City: String; Street: String; HOUSENR: Integer; FlatNR: Integer; end; TPerson \u003d Record Name: String; Phone: String; ADDRESS: TADDRESS; end; Var Anybody: TPerson; ADDRESS: TADDRESS; Begin Write (name :); readln (anybody.name); Write (Phone :); readln (anybody.phone); Write (Postal Index :); readln (address.postindex); Write (City :); readln (address.city); Write (Street :); readln (address.street); WRITE (House Number :); readln (address.housenr); WRITE (Flat Number :); readln (address.flatnr); Anybody.address: \u003d Address; Writeln (anybody.name); Writeln (anybody.phone); Writeln (anybody.address.postindex); Writeln (anybody.address.city); Writeln (Anybody.Address.street); Writeln (anybody.address.houseNR); Writeln (anybody.address.flatnr); readln; end.

The program shown in Listing sequentially offers the user to enter properties - first for recording about the person, and then for the address, after which the person's address field is assigned the value of the address address. After that, all fields are sequentially displayed. The source code of the program can be found in the Demo \\ Part1 \\ Records folder (Recdemo.dpr file).

At the end of the topic of entries, we consider another feature characteristic of Object Pascal, namely - the ability to combine in one type of recording with different fields. These records are called variant and are declared as well as the usual, except that they contain an additional part starting with the keyword CASE:

Record [:; ...:;] Case [:] of: (); ...: (); end;

As an example, this case can be created when you need to create a "OPENDER" record type, which requires the name and size of payment. The fact is that these the most employees can be both accepted for a permanent job, and has monthly salary and temporary for which the hourly pay is applied. It is important to note that only one type of salary can be at the same time. Accordingly, we can use a variant record type, as a trait in which the Boolean field "Salaried" will perform ("OKLADE"):

Type Temployee \u003d Record Name: String; Jobtitle: String; Case Salaried: Boolean of True: (Salary: Currency); FALSE: (Hourly: Currency); end;

Here, depending on whether the value of a SALARIED field, a lie or truth, will be the value of the TEMPLOYEE type or truth, it will have either the Salary field or Hourly. You can view an example of using this option to write in the Varrec.dpr file.

Special data types

In addition to the data types, simple and user, in Object Pascal there are a number of other, specialized types. For example, use TDATETIME type for time. In principle, this type of alogure is a real type Double, however, to work with data such as time ranges, it is more convenient to use a special TDATETIME type.

Since this type is, in fact, a real number, then the data is stored in it as follows: The whole part of the number determines the date for which the number of days from December 31, 1899 is taken, and the fractional determines the time in the milliseconds that have passed since the beginning of the current day. The advantage of TDATETIME type is that it provides a whole set of ready-made functions that allow you to work with dates and time. The list is shown in Table 5.1.

Table 5.1. Functions for working with date and time
FunctionDescription
NowReturns the current date and time
Date.Returns the current date (whole part of TDATETIME)
Time.Returns the current time (fractional part TDATETIME)
DateTimetostr.Converts the date and time in a string based on system settings
DateTimetOcCopies the date and time to the specified string variable
DateTostr.Converts the date in the string
Timetostr.Converts time to row
FormatdateTime.Converts the date and time to the specified format
StrtodateTime.Converts a string containing a proper date and time written in the TDATETIME variable
StrTodate.Converts a string at a date in TDATETIME format
Strtotime.Converts a string during TDATETIME format
DayOfweek.Returns the day number of the week (from 1 to 7) for the specified date. Consider that the 1st day of the week - Sunday
Decodedate.Encloses a value of type TDATETIME for 3 integers, which are a year, month and day of the month
Decodetime.Encloses a value of type TDATETIME for 4 integers, representing hours, minutes, seconds and milliseconds
Encodedate.Unites 3 entire years, month, month and day, in one value of type TDATETIME
EncodeTime.Combines 4 integers, representing hours, minutes, seconds and milliseconds? In one value of TDATETIME

A typical example of using functions to work with dates may look like this:

Var Today, Yesterday: TDATETIME; S: String; ... today: \u003d now (); yesterday: \u003d Today - 1; S: \u003d TateToStr (Yesterday);

Here, the variable S will be assigned a value corresponding to yesterday in the format adopted in the system (for example, "16.07.2005"). You can view a more complete example of working with dates in Demo \\ Part1 \\ Dates.

In addition to dates, consider another data type - files (Files). The files are a certain sequence of the same type of elements placed on the outer medium that is not in the PC RAM. In a typical case, such a carrier is a hard disk. This data type can be described as a one-dimensional array without specifying the size. To produce certain operations on these data, use a special file type variable. Moreover, depending on which data it is necessary to work, you specify one or another type of file. For example, a special type of textFile is used to work with text files, and if the file contains a number of numbers, then determine which type of numbers in it is used:

VAR F1: TextFile; // Text file F2: File of Integer; // File with integers F3: File of Double; // File with real numbers

If the file is inepitized, (for example, binary), then use the type of File without any additions:

VAR F4: File; // binary file or file in advance of unknown type

Working with file variables has a number of features. First of all, simply declare a file type variable is not enough - it is also necessary to link it to any specific file on the disk. After that, the file should be opened, while specifying the file mode - it can be opened for reading or writing, or reading and writing simultaneously.

NOTE
The file itself is called the file descriptor, i.e. In fact, it only indicates a program to a place in memory through which access to the file using the operating system tools.

To work with files, there is also a number of procedures and functions. Among them, you can mark already familiar to us read / readln and write / writeln. So that these procedures work with files, as the first parameter indicate the file variable name (file descriptor):

Writeln (F, text to write to the file);

But before you write to a file, or start reading the data from it, as already mentioned, you need to associate a variable with the file (assign a descriptor) and open it, simply assigning the access mode. Assigning a file descriptor is made using the ASSIGNFILE procedure, for example

ASSIGNFILE (F, C: \\ File.txt);

As for the opening of the file, then things are somewhat more difficult, because the type of file and the access mode should be taken into account. So, in relation to text files, the RESET, REWRITE and APPEND procedures are used, opening the reading file, overwrite and add (entry to the end of the file), respectively.

Finally, it should be noted that after the operations with the file are manufactured, it must be closed. To close the file, use the CloseFile procedure. Thus, the use of all these procedures for reading and writing a file in the general case looks in such a way as shown in Listing 5.4:

Listing 5.4. Record and read in files

PROGRAM READWRITE; ($ APPTYPE CONSOLE) Uses Sysutils; VAR F: TextFile; S: String; Begin AssignFile (F, C: \\ TEST.TXT); // Assign a handle to the Text.txt Rewrite file (F); // Open the Writeln (F, S) entry file; // We write to the CloseFile file (F); // Close the RESET file (F); // Open the reading file readln (F, S); // Read the data from the CloseFile file (F); // Close the END file;

Another example of working with files can be viewed in Demo \\ Part1 \\ Files. At the same time, in practice, file types of data are not often used in modern programming in the Delphi environment, since the VCL offers a number of more convenient and elegant methods for storing data on disk, ranging from individual classes methods and ending with threads and databases.

Compatibility and conversion types

When considering simple types, we have already raised the issue of their compatibility and the transformation into each other. Now it's time to consider this aspect more carefully. In particular, if an integer is reduced to a real one, how to be in case the reverse transformation is required? The output in this situation is to use special type conversion functions. So, for converting a real number to the integer, the functions of the Round and Trunc are used. Their difference is that the first rounded value to the whole, relying on standard mathematical rules, and the second - simply discarded the fractional part of the number. Note that if you just need to drop the fractional part of the number, leaving the data type unchanged, you should use another function - Int. Examples of their use are shown below:

VAR I: Integer; R: REAL; ... R: \u003d 5.75; I: \u003d Round (R); // I will receive a value of 6 i: \u003d trunc (r); // I will receive a value of 5 R: \u003d int (R); // R will receive 5.0

A much larger number of functions is provided for converting numeric types to string and vice versa. With some of them, intended for dates, we are already familiar (see Table 5.1). Others are presented in Table 5.2.

Table 5.2. Functions to convert numbers into strings and vice versa
FunctionDescription
Inttostr.Converts an integer in the string
StrToint.Converts a string into an integer, if the transformation is not possible, it causes an error
StrtointDef.Converts a string into an integer, if the transformation is not possible, returns the number specified as the second argument
Floattostr.Converts a real number into a string
Floattostrf.Converts a real number into a string based on the specified format
Strtofloat.Converts a string into a real number, if the transformation is not possible, it causes an error
Strtofloatdef.Converts a string into a real number, if the transformation is not possible, returns the number specified as the second argument.
Currtostr.Converts the number of type CURRENCY to the string
Currtostrf.Converts the number of type CURRENCY to a string based on the specified format
Strtocurr.Converts a string to the type of Currency, in case of the impossibility of the conversion causes an error
Strtocurrdef.Converts a string to the type of Currency, in case of the impossibility of the conversion, returns the number specified as the second argument

As a format in Floattostrf functions, one of the predefined formatting options, as well as the number of semicolons and the total number is also implied. For example, in order to leave no more than 2 decimal places, you can use the following expression:

STR: \u003d Floattostrf (X, FFGeneral, 10, 2);

Here FFGeneral is an indication of the output format, 10 defines the maximum possible number of signs in the number at all, and 2 is the maximum permissible number of decimal places. In addition to FFGeneral, which defines the most generalized format of the presentation of numbers, there are other:

  • ffexponent - an exhibitor format (for example, 1.45E10);
  • fffixed - fixed format (for example, 145000.01);
  • fFNUMBER - "capital" format (for example, 1,450,000.0);
  • fFCURRENCY - currency format (for example, 145 000.00Р).

Thus, it is easy to give a number to the row of the desired format, it is quite simple - it is important only to decide what you need to get. And another function - CHR - allows you to transform small numbers (up to 255) into symbols, i.e. In fact, from type byte makes Char.

As for the inverse transformations (rows in numbers), then it should be abide by certain caution, since not any string can be a number. First of all, it concerns the conversion of real number, since it is often a comma instead of the point separating the mantissa. In cases where the row transformation is not possible, the program execution error occurs. It can be processed independently (error processing will be told in the second part of the book), or to charge this function of the function - in this case, you should use functions with the DEF suffix (STRTOTIDEF, STRTOFLOATDEF and STRTOCURRDEF). As the second argument, they take the value to be used if the transformation is not possible:

X: \u003d STRTOINTDEF (STR, -1);

In this case, if the string cannot recognize the number, the variable x will be assigned to -1.

With all the variety of data types, in Object Pascal there is a type of another type that does not have type - option. He is called Variant. Variables variant type can take values \u200b\u200bof any simple type, as well as some special. In a typical case, the use of an option type may look like this:

VAR V: Variant; ... v: \u003d 5; v: \u003d string value; V: \u003d 10.54;

This example illustrates how one T the same variable is sequentially assigned data to 3 different types - integer, string and real. In this case, no error occurs. However, the variant data is processed much slower than typed - almost as slowly as programs in Basic. (By the way, in the classic Basic just only the variant data and were). In addition, the use of non-type data at all, and in a strictly typed language - especially, fraught with the unpredictable behavior of the program and other errors. Therefore, we will leave this type for the internal use of Delphi - in the VCL it is used to work with OLE and databases.

Pointers

Pointers (Pointers) is a type of variables that store the address in the memory of the computer, which is located another variable. In fact, the pointer does not contain value, but refers to it.

Pointers can be set in two fundamentally different ways. First, you can use a special type - Pointer. This will create a non-type pointer, under which every time it will be necessary to allocate the memory using the GETMEM function. Another, as a rule, a more preferred method is that the indicator of the desired type is immediately created. This is done using the symbol "^" preceding the name of the type:

VAR P: \u200b\u200b^ Integer;

Here we defined a P index, which is a pointer to an integer-type variable.

After the pointer is created, you can bind it from a variable of a suitable type using the @ operation:

VAR P: \u200b\u200b^ Integer; x: integer; ... p: \u003d @x; Now to the variable x, you can contact both directly and through its pointer. In the case of circulation through the pointer, the symbol "^" is also used: x: \u003d 10; P ^: \u003d 10;

In both cases, the variable X will receive a value of 10.

Another way to use the pointer, in addition to binding to an existing variable, is the allocation of memory for it and further work as reference to a certain abstract variable (in fact, directly to the area in memory allocated for data storage). In this case, the code will look like this:

VAR P: \u200b\u200b^ Integer; ... new (p); // Selecting the memory required for storage of data type Integer P ^: \u003d 10; // Data entertainment in the dedicated DISPOSE memory unit (P); // Release of Memory

When working with pointers, such a method should be used to independently take care of the allocation and cleaning of memory for data.

In general, pointers are usually used to interact with system functions of the operating system (in particular, with Windows API), to interact with third-party programs and dynamically plug-in libraries.

Objects

The most difficult and interesting types of data are objects. In modern versions of Delphi, objects are 3 main types: Objects are actually, as well as classes and interfaces. Object type (OBJECT) Delphi from the predecessor is the Pascal with Objects language, and is currently practically not used. The main type of object data in modern programs is class (Class). As for the interfaces (Interface), they are a type of classes, and are intended to interact with system object function functions.

The subject of objects is quite extensive, since it is the basis for the paradigm of object-oriented programming. OOP in Object Pascal is considered in the second part of this publication.

We continue our training! In Delphi, variables play a very important role. In the process of operation of the program in variables, you can both store and extract information. Variables may have different types. For example, in order to write some text to the variable String. In order to write to the variable number use type Integer.

Here is a list of basic types of variables in Delphi:

  • Integer - Whole numbers from the range: -2147483648 .. + 2147483647
  • Shortin. - integers from the range: -128 .. + 127
  • Byte - integers from the range: 0 .. + 255
  • Real - both as well as fractional numbers from the range: 5E-324..1.7E + 308
  • Double. - similar to the type REAL
  • String - String data type
  • Char. - symbol data type
  • Bollean - Logical data type. Can take True - Truth or False - Lie
We finished with theory, now let's open Delphi 7 and create a new project. After that we throw a component familiar to us Button. and not yet familiar Label. Component Label This such useful thing in which you can record some signature. For example, a signature for another component or simply record the author of the program. Try finding the component Label ourselves hovering on all components in the tab Standard And reading the pop-up tip. Who is difficult to find, then fourth component from the left, not counting the cursor icon.

I did everything and I got like this:

Now we need to create an event OnClick. On the button, I hope you remember how to do it.
Variables are announced between keywords procedure. and begin.. Announcement begins with a keyword var., then writes name variable and through colon her a type. Ends everything as always a point with a comma.

Create a variable S. Type String In the procedure OnClick.: procedure TFORM1.Button1Click (Sender: Togject); VAR S: String; Begin End; After that between the keywords begin End. We assign variable value equal to "My first variable". Assignment is written as follows. We write name variable, assignment operator := and value. If we record information type StringThe information is single quotes.

General view: Procedure TFORM1.Button1Click (Sender: Togject); VAR S: String; Begin S: \u003d "My first variable"; end; Now, if you compile the program and click on the button, nothing significant will happen, just a value will be recorded in the variable and that's it. Let's try to output from the variable. It is also done just as written. We will output the value in our Label.

Syntax such: label1.caption: \u003d s; We will analyze this code in detail. First we wrote Label1, then we write a point and in Delphi a huge list with the properties of this component appears. You can of course ride and find there CaptionBut we will be smarter! We, after put the point, write still the letter C. and Delphi sorted all properties and find all that start with the letter C.. The first list is just a property. Caption.

Choose it from the list and click on ENTER. Note that we wrote, but after clicking ENTER, Delphi himself adds the name of the property. Next, again, the appropriation operator and our variable goes.

You will surely ask: "Why is the variable if you could write label1.caption: \u003d" My first variable ";?". The answer is simple. It is necessary then that we study the variables :).
No, in fact, it is also possible to assign so much, but imagine this situation that you wrote a very large, popular program and you, there in the program, fifty The components are assigned the same value, and here you faced the task: "Change this value to a more universal and understandable for the user."

What are you going to do?

  • In the first The case of all these components is assigned to all these components and the same variable and to change all these fifty components the value you just need to change the value in the variable.

  • In second Case You are sitting 20 minutes and copy everything and copy the value to all fifty components.
Make the output yourself.

And so, continue! In general, it should be like this: procedure TForm1.Button1Click (Sender: Togject); VAR S: String; Begin S: \u003d "My first variable"; Label1.caption: \u003d s; end; Complete our program and click on Button. (Baton / button). Immediately component Label instead Label1 will show My first variable.

I would like to finish it, since I'm already tired of writing a lesson :), but I have not introduced you to the type Integer And how to assign a variable with such a type. You think that you need to assign it just like a type variable StringBut you are mistaken.
The fact is that property Caption In general, all components can only be assigned text values. How will we assign a numeric type if you can only text? Everything is easier nowhere. Between the types of variables, it is possible to switch, that is, it is possible to make textual from a numeric type and assign it to the component Label. This is now we will deal with.

First you need to start first :). Let's declare a variable named I. and type Integerby adding it to the variable S.. Code: Procedure TForm1.Button1Click (Sender: Togject); VAR S: String; I: integer; Begin ... further assign a variable I. value 21 . I: \u003d 21; Note that the numeric value is written without single quotes! Now assign the property Caption Variable value I., for this you need to use the operator INTTOSTR (). It seems to convert a numeric type in the textual. In brackets indicate the variable you want to convert.

General code view: Procedure TFORM1.Button1Click (Sender: Togject); VAR S: String; I: integer; Begin S: \u003d "My first variable"; Label1.caption: \u003d s; I: \u003d 21; Label1.caption: \u003d INTTOSTR (I); end; Compile the program and you will see that Label will display the value of the variable I., i.e 21 .

That's it! Good luck!
See you in the next lesson!