Decimal points and functions

This is one of those issues that arise from me coming from a different background than what I imagine a typical Lansa developer, indeed a typical i series developer, come from. It is a useful reminder what decimals actually mean.

For example, I have a repository field that is defined like this:

Define Field(#JSN) Type(*SIGNED) Length(4) Decimals(2)

That means, a signed decimal value with 2 digits before the decimal point and 2 digits after the decimal point. In a function, I calculate the value and exchange it back to application calling. In that application, I (unfortunately) have an override, changing the number of decimals.

Override Field(#JSN) Decimals(1)

This was probably done to format the display of the value but where I imagined the value would be passed in a lower level format and then parsed in relation to the definition, this is not what happens.

Instead, the value is still the same 4 digits, but one of those that was after the decimal point is now before the decimal point. The value has been multiplied by 10^1; a value of 0.21 is now 2.1.

Because the override only moved the decimal point.

Advertisements

Newlines in Lansa 12 and 14

As someone mentioned a while back on the forum, getting a newline, or indeed any character from a character code, is difficult to find. But it is easy, once you know how:

(10).AsUnicodeString

returns a carriage return in ANSI. I’d probably prefer

(13).AsUnicodeString + (10).AsUnicodeString

as I am primarily targeting the Windows platform.

However, this is a solution for Lansa v. 14, not v. 12, where AsUnicodeString does not exist. In Lansa v. 12 you write

(13).AsByte.AsString + (10).AsByte.AsString

 

 

About password entropy

A quick follow-up on .IsValidPassword: The highest entropy of a password that would be accepted by .IsValidPassword(strong) can be calculated like this:

8 characters:
1 upper - 26 characters
1 lower - 26 characters
1 numeric - 10 characters
1 special - 7 characters
4 remaining - 26+26+10+7+1(space) = 70 characters

log2(26+26+10+7)*4+log2(26)*2+log2(10)+log2(7) ~~ 40 bits of entropy

If the requirement was simply 8 characters, the entropy could be

log2(70)*8 ~~ 49 bits of entropy

Entropy is used to calculate how long it would take to crack a password, provided it wasn’t an already know password:

Number of guesses = 2^(entropy - 1)

If we can make 1,500 guesses per second, divide by (1500 * 86400) (86400 is the number of seconds per day):

40 bits of entropy ~ 4,000 days
49 bits of entropy ~ 2.1 million days

What about words? Using words doesn’t necessary increase entropy, as words are part of a finite dictionary. The complete dictionary is somewhere between 100,000 and 200,000 but most people only use 3,000 words.

log2(3000)*4 ~~ 46 bits of entropy ~ 271,000 days
log2(3000)*5 ~~ 58 bits of entropy ~ 1.1 billion days

Using 4 words from your own dictionary would give a stronger password than what .IsValidPassword(strong) accepts, even if those 4 words would not get accepted by that method.

Password strength

I am not impressed with the Lansa v. 14 SP2 addition of .IsValidPassword (see documentation):

passwords-are-you-kidding-me

I am sorry. This is completely insufficient and wrong. Also, this is no secret as everyone knows that these rules are insufficient and wrong. Or should know.

So, what are the right rules?

  • Long passwords – this equals greater entropy
  • Use words that can be remembered – this way the password doesn’t have to be written down or stored digitally
  • Do not require use of mixed case and/or special characters – as this lowers entropy

Server modules

Did I mention that the Lansa documentation is incomplete?

Problem: Create a simple server module to accept a bit of data and simply reply with a string. Let that data be set with querystring parameters.

If I wanted to send the data as parameters in a POST request I would just do this:

POST to http://[DOMAIN/BASEPATH]/w=[WEBROUTINE]&r=[SRVROUTINE]&vlweb=1&part=[PARTITION]&lang=[LANGUAGE]

with, for instance,

FIELD1=DATA1&FIELD2=DATA2

But if the client expects to be able to send the data as part of the querystring, in a GET request, I need to do this:

GET to http://[DOMAIN/BASEPATH]/w=[WEBROUTINE]&r=[SRVROUTINE]&vlweb=1&part=[PARTITION]&lang=[LANGUAGE]&f(FIELD1)=DATA1&f(FIELD2)=DATA2

I could not find that f(…) trick, putting the parameters inside the parentheses, in the documentation but a very lucky search on the forum helped me.

My sample server module looks like this:

Begin_Com Role(*EXTENDS #PRIM_SRVM)

Define Field(#PROC_OUTPUT) Type(*string) Length(65535)

Srvroutine Name(DATA) Response(#RESPONSE)
  Field_Map For(*INPUT) Field(#FIELD1)
  Field_Map For(*INPUT) Field(#FIELD2)
  * do something with FIELD1 and FIELD2 here
  #PROC_OUTPUT := "Done"
  #RESPONSE.ContentString := #PROC_OUTPUT
Endroutine

End_Com

 

 

Trigonomic functions; also, precedence

Surprise! I am no friend of the Lansa documentation, so I thought I would add a few notes here.

I am grateful that there is an intrinsic function for floats as ArcTangent2, but which parameter is X and which is Y? The documentation is pretty sure that it is like this

#X.ArcTangent2( #Y )           /* not exactly... */

but I find that the parameters are reversed, if you take the thinking from elsewhere, like my previous programming experiences and for instance Wikipedia. A better explanation in the documentation could have helped me here, but I managed to find out that the problem was here. So I changed my code to

#Y.ArcTangent2( #X )           /* now similar to atan2(y,x) elsewhere */

Then comes the question of precedence… The difference between these two lines is obvious:

#A1 := (#DT / 2).Sine * (#DT / 2).Sine + #RT1.Cosine * #RT2.Cosine * (#DN / 2).Sine * (#DN / 2).Sine
#A2 := ((#DT / 2).Sine * (#DT / 2).Sine) + (#RT1.Cosine * #RT2.Cosine * (#DN / 2).Sine * (#DN / 2).Sine)

But I was surprised that they returned different results. I expected that both returned the same as the second one. Instead, the first returned the same as

#A3 := ((#DT / 2).Sine * (#DT / 2).Sine) + #RT1.Cosine) * (#RT2.Cosine * (#DN / 2).Sine * (#DN / 2).Sine)

I realize now that 1 + 2 * 3 in RDML(X) equals 9, whereas I would have expected 7 from my experience in many other languages. Now I know.

XSLT 1.0 and upper/lowercasing

And today I again found out about the translate function in XSLT so I will store this here:

<xsl:variable name="lowercaseselect="'abcdefghijklmnopqrstuvwxyzàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿžšœ'" />
<xsl:variable name="uppercaseselect="'ABCDEFGHIJKLMNOPQRSTUVWXYZÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞŸŽŠŒ'" />
<xsl:value-of select="translate(/lxml:data/lxml:lists/lxml:list[@name='LSTFORP']/lxml:list-entries/lxml:entry[2]/lxml:column[@name='WFPLTEKST'], $uppercase, $lowercase)" />
This does two things: It finds the value of the WFPLTEKST field in the 2nd entry in the Lansa list LSTFORP and changes the case from upper to lower.

ROUND_UP – no, I don’t think so

The documentation for CHANGE’s ROUND_UP parameter states, among other things, that

If the intermediate work fields involved do not have at least one more decimal position than the field nominated in the FIELD parameter, then the ROUND_UP parameter will be ignored and treated as if ROUND_UP(*NO) was specified.

I have difficulty finding documentation on how intermediate work fields are created, if no PRECISION parameter is given, but there are a few implied hints in the documentation for PRECISION.

So, in my concrete example, I have one 9,0 number divided by a 10,0 number. The documentation implies the intermediate work fields will be 10,0. My result field is a 3,0 number.

The actual numbers are 122,682,586 and 1,000,000, respectively. I really would like to get 123 as the result in this particular case.

Just doing a

Change Field(#RESULT) To('#BIG / #SMALL')

gives me 122. Doing

Change Field(#RESULT) To('#BIG / #SMALL') Round_Up(*YES)

gives me 123. Great, but not something that ought to have worked. Doing the right thing,

Change Field(#RESULT) To('#BIG / #SMALL') Precision(11 1) Round_Up(*YES)

also gives me 123. And, though the parameter is called ROUND_UP, it actually does the “half adjust” which I found a description of that seems like it would do the right thing.


Note: Embarrassingly, I was under the impression that the length of a numeric field was the number of digits before the decimal. It was just recently I found out it was the length including the decimals.

Irritating warnings during check-in

The following RDML code generates 4 warnings:

Delete From_File(KMXDAT) Where('(#SASNR *EQ #WSASNR) *AND (#OWNER *EQ #WOWNER) *AND (#CHAIN *EQ #WCHAIN)')
From Host FFC: Func KMOX / Cmd DELETE / Seq 1082 ....
** Field SASNR was appended, or needs appending, to list of fields because of use in WHERE parameter
** Field OWNER was appended, or needs appending, to list of fields because of use in WHERE parameter
** Field CHAIN was appended, or needs appending, to list of fields because of use in WHERE parameter

However, if I have a logical view with those 3 fields as keys and use them, I get no warnings:

Delete From_File(KMXDAT) With_Key(#WSASNR #WOWNER #WCHAIN)

I understand why I get those warnings but as it is not only illogical but also impossible to add those 3 fields to a delete, they are irritating.