Trailing space in i to string conversion in ABAP - abap

On a SAP system, ABAP version 7.40 SP05, I just encountered a failure in unit tests on string comparison, but both strings should be the same?! Turns out it's not the case, as preceding conversion from i to string seems to produce extra trailing space in one of the strings.
This code bit:
DATA(i) = 111.
DATA(s1) = CONV string( i ).
DATA(s2) = '111'.
DATA(s3) = |111|.
Produces (as seen in debugger):
S1 111 3100310031002000 CString{4}
S2 111 310031003100 C(3)
S3 111 310031003100 CString{3}
The converted one has an extra trailing space. How does this happen and how can I prevent this to happen in i to string conversions? Obviously stuff like this makes me debug for a long time to find what is up (because unless I check the hex values, the debuger does not show that extra space...).

To understand why the space is added in the first place, check the documentation on the default conversion rules that are applied by CONV:
The character "-" is set at the last position for a negative value,
and a blank is set for a positive value.
Since you can't use the formatting options of string expressions with the CONV operator, I'd suggest changing the code to use |{ i }| (which might be a good idea for other values as well, since you'll probably need some formatting options when comparing date / time values in unit tests anyway).

You cannot prevent it. The best way I found so far in ABAP is use CONDENSE s1
DATA i type i VALUE 12.
DATA idx TYPE string.
idx = i. " idx = '12 '.
CONDENSE idx. " idx = '12'.

Related

Is this a bug in VBA's IsNumeric and CDbl() functions?

Consider the following VBA function:
Function castAndAdd(inputValue As Variant) As Variant
If IsNumeric(inputValue) Then
castAndAdd = CDbl(inputValue) + 4
Else
castAndAdd = inputValue
End If
End Function
Calling it from the immediate window gives this output:
?castAndAdd("5,7")
61
?castAndAdd("5, 7")
5, 7
Stepping through the "5,7" call, I find that IsNumeric("5,7") returns true. I was thinking that maybe it gives this result because in Europe a comma is used as a decimal separator; this result is odd because I'm in the United States, so my locale should determine that Excel only recognizes a period as a decimal separator, right?
Even if we set aside the Europe/US issue, the bigger problem is that CDbl("5,7") returns 57, so that CDbl("5,7") + 4 returns 61, not 9.7 as I would have expected if the comma is a decimal separator. Is this a bug, or am I just not understanding how to use CDbl()?
The comma is not recognized as decimal, but as thousands separator. The mechanism is not so smart to require that then at least three digits should follow, but essentially it strips any of the thousands separators in interpreting it as a number.
So even CDbl("4,5,,6,7") would yield 4567 as a number. All this is true when the comma is the thousands separator. If, as in some European countries, the point is the thousands separator, then a similar thing will happen with points.

Limitting character input to specific characters

I'm making a fully working add and subtract program as a nice little easy project. One thing I would love to know is if there is a way to restrict input to certain characters (such as 1 and 0 for the binary inputs and A and B for the add or subtract inputs). I could always replace all characters that aren't these with empty strings to get rid of them, but doing something like this is quite tedious.
Here is some simple code to filter out the specified characters from a user's input:
local filter = "10abAB"
local input = io.read()
input = input:gsub("[^" .. filter .. "]", "")
The filter variable is just set to whatever characters you want to be allowed in the user's input. As an example, if you want to allow c, add c: local filter = "10abcABC".
Although I assume that you get input from io.read(), it is possible that you get it from somewhere else, so you can just replace io.read() with whatever you need there.
The third line of code in my example is what actually filters out the text. It uses string:gsub to do this, meaning that it could also be written like this:
input = string.gsub(input, "[^" .. filter .. "]", "").
The benefit of writing it like this is that it's clear that input is meant to be a string.
The gsub pattern is [^10abAB], which means that any characters that aren't part of that pattern will be filtered out, due to the ^ before them and the replacement pattern, which is the empty string that is the last argument in the method call.
Bonus super-short one-liner that you probably shouldn't use:
local input = io.read():gsub("[^10abAB]", "")

Read free format with no advance

In a given file record, I need to read the first two integer elements at first, and then the rest of the line (a large number of real elements), because the assignment depend on the first 2. Suppose the format of the first two integer elements is not really well defined.
The best way to solve the problem could be something:
read(unitfile, "(I0,I0)", advance='no') ii, jj
read(unitfile,*) aa(ii,jj,:)
But it seems to me the "(I0)" specification is not allowed in gfortran.
Basically the file read in unitfile could be something like:
0 0 <floats>
0 10 <floats>
10 0 <floats>
100 0 <floats>
100 100 <floats>
which is hard to be read with any fortran-like fixed field format specification.
Is there any other way to get around this, apparently trivial, problem?
This applies string manipulations to get the individual components, separated by blanks ' ' and/or tabs (char(9)):
program test
implicit none
character(len=256) :: string, substring
integer :: ii, jj, unitfile, stat, posBT(2), pos
real, allocatable :: a(:)
open(file='in.txt', newunit=unitfile, status='old' )
read(unitfile,'(a)') string
! Crop whitespaces
string = adjustl(trim(string))
! Get first part:
posBT(1) = index(string,' ') ! Blank
posBT(2) = index(string,char(9)) ! Tab
pos = minval( posBT, posBT > 0 )
substring = string(1:pos)
string = adjustl(string(pos+1:))
read(substring,*) ii
! Get second part:
posBT(1) = index(string,' ') ! Blank
posBT(2) = index(string,char(9)) ! Tab
pos = minval( posBT, posBT > 0 )
substring = string(1:pos)
string = adjustl(string(pos+1:))
read(substring,*) jj
! Do stuff
allocate( a(ii+jj), stat=stat )
if (stat/=0) stop 'Cannot allocate memory'
read(string,*) a
print *,a
! Clean-up
close(unitfile)
deallocate(a)
end program
For a file in.txt like:
1 2 3.0 4.0 5.0
This results in
./a.out
3.00000000 4.00000000 5.00000000
NOTE: This is just a quick&dirty example, adjust it to your needs.
[This answer has been significantly revised: the original was unsafe. Thanks to IanH for pointing that out.]
I generally try to avoid doing formatted input which isn't list-directed, when I can afford it. There's already an answer with string parsing for great generality, but I'll offer some suggestions for a simpler setting.
When you are relaxed about trusting the input, such as when it's just the formatting that's a bit tricky (or you 're happy leaving it to your compiler's bounds checking), you can approach your example case with
read(unitfile, *) ii, jj, aa(ii, jj, :)
Alternatively, if the array section is more complicated than given directly by the first two columns, it can be by an expression, or even by functions
read(unitfile, *) ii, jj, aa(fi(ii,jj), fj(ii,jj), :fn(ii,jj))
with pure integer function fi(ii,jj) etc. There is even some possibility of having range validation in those functions (returning a size 0 section, for example).
In a more general case, but staying list-directed, one could use a buffer for the real variables
read(unitfile, *) ii, jj, buffer(:) ! Or ... buffer(:fn(ii,jj))
! Validate ii and jj before attempting to access aa with them
aa(.., .., :) = buffer
where buffer is of suitable size.
Your first considered approach suggests you have some reasonable idea of the structure of the lines, including length, but when the number of reals is unknown from ii and jj, or when the type (and polymorphism reading isn't allowed) is not known, then things do indeed get tricky. Also, if one is very sensitive about validating input, or even providing meaningful detailed user feedback on error, this is not optimal.
Finally, iostat helps.

Check if character field contains only digits

I read data from a excel file.
The cols of internal table all are char128, there are 2 cols contain only digital with decimal point. So I need to check the fields only contain digital or digital with decimal point.
The function module NUMERIC_CHECK, just can check only digital, if the digital with decimal point it will be useless.
You may use CO (contains only):
IF value CO '1234567890.'.
"OK
ELSE.
"Error"
ENDIF.
Maybe you need also a space in your IF _ CO-statement.
This check does not detect multiple decimals points (e.g. 123.45.67.89).
Newer versions of ABAP support regular expressions.
If you have also spaces in your string, you may add them into the CO-value:: IF value CO '1234567890 .'.
You might try to use REGEX. The report DEMO_REGEX_TOY lets you input strings and test regular expressions against them.
Someone more experienced with REGEX in general might be able to make this a little more versatile but here's what I came up with:
-?[0-9]+(\.[0-9]*)?
-? Optionally match the '-' character (allows for negatives or non-negatives
[0-9]+ matches digits (the + makes it match 1 or more)
\.? optionally matches the '.' character (the \ is needed as '.' is an operator)
([0-9]+)? optionally matches any digits after the decimal
if you want to check if the string is a valid decimal, you could use the following module function : 'HRCM_STRING_TO_AMOUNT_CONVERT', which allow you to convert a string to its numeric counterpart, given the string, the decimal separator and the thousand separator.
regards
Another way to check for number:
data: float_value type f.
try.
float_value = <your string value here>.
catch cx_sy_conversion_no_number.
" not a valid number
endtry.

Hsqldb - how to remove the padding on char fields

I'm finding that Char fields are being padded.
Is there any way to stop this happening.
I've tried using the property
SET PROPERTY "sql.enforce_strict_size" FALSE
but doesn't seem to help.
Indeed, the MySQL docs specify that "When CHAR values are retrieved, trailing spaces are removed." This is odd, as other databases seem to always keep the padding (i can confirm that for Oracle). The SQL-92 standard indicates that right-padded spaces are part of the char, for example in the definition of the CAST function on p. 148. When source (SV=source value) and target (TV=target value, LTD=length of target datatype), then:
ii) If the length in characters of SV is larger than LTD, then
TV is the first LTD characters of SV. If any of the re-
maining characters of SV are non-<space> characters, then a
completion condition is raised: warning-string data, right
truncation.
iii) If the length in characters M of SV is smaller than LTD,
then TV is SV extended on the right by LTD-M <space>s.
Maybe that's just another one of MySQL's many oddities and gotchas.
And to answer your question: if you don't want the trailing spaces, you should use VARCHAR instead.
I thought 'char' by definition are space padded to fill the field. They are considered fixed lenght and will be space padded to be fixed length.
The data type 'varchar' is defined as variable char where they are not space padded to fill the field.
I could be wrong though since I normally work on SQL Server.