Forum > FPC development
[SOLVED]Confusion with signed types: is ( 256 * 1 = 0 ) a design issue or a bug?
old_DOS_err:
Hi,
[Edited, oops, missed initialising the var in the below code. There is a second post from me below that has the correct code, with the problem replicated.]
Currently doing a front end (DOS) for a little utility for making data files of a given size with repeating data (current main project is extending a duplicate finder, base already works and using that, to find duplicates based on content not name, duplicates get moved out to a designated folder). I have about 20 main library files, file work, string, stringlist, etc. Currently updating my string number unit, had a function in there whereby the user could enter a number in string form, optionally with commas, and the function removed the commas and returned the actual value (generally do Boolean functions and return the var in either Var or Out param). Like most of my number functions, this used DWord.
Decided to update this function, thought it would be useful to extend the range to include negative and QWord (though wouldn't be needed for the front end, I do like to be thorough when I can, and I would have a general number converter). Removing the commas was extremely easy and took only 1 line using StringReplace. You know you have those moments as a programmer (and probably in life) where you find yourself thinking "Why oh Why did I start this?" First problem was getting around pascal's very tight type checking (dear old $V is gone, though not sure how useful it would have been anyway). Eventually worked out a way with untyped reference param and pointer. Got weird results, realized it was a register issue (fixed that). Since I call the converter with just the var and there is no way to know anything about the Var (SizeOf on the untyped reference returns 0 for some reason, can't use Array of Const, I do use that quite often, because there is a 'bug' and it doesn't take DWord, get around by either converting to a string or cast to QWord, but no use here).
Finally got the bugs out of code. Had to replace StrToInt, it had too many issues and too many variations (not tried old reliable Val yet, but if that has gone from the old Turbo Pascal style to the Free Pascal style, then suspect I will be out of luck). So wrote my own string to number converter, easy enough (though really should be unnecessary). Running auto testing (the one good aspect of getting old, is what I have lost in speed of coding I have gained in commenting and testing). The unsigned number types all worked fine (had to reduce the QWord test, it was taking way too long to test EVERY number, used large steps until near the end then went back to single step).
This is the function header, just to make it a bit clearer:
Function p_str_num__str_to_num( Const s_in : String; Var n_ret; size_of_var : Byte; signed_var : Boolean = False ) : Boolean;
p_str_num__ is my own prefix, all my units have their own prefix, so I know it's mine and which unit it is in at a glance.
s_in is the string to convert, this gets tested, so if any errors, exits with False.
n_ret is the var where the number is returned to, this can be any type.
size_of_var, because the function has no way of knowing how big the var is, whilst not ideal, it gets around that problem (e.g. for a Word type, pass 2, for LongInt, pass 4).
signed_var, normally with unsigned, so only need to add True for signed.
Then started testing the signed, positive first. Int8 fine, Int16, not fine. Got an error when it reached 256, i.e. called the convert function with "256" and byte size 2. After some tracking down, found the culprit and to say I was baffled is putting it mildly. I extracted the core essence of the problem, just to make sure I hadn't done something stupid (99% of the time I think there is a bug in pascal, find that I've done something stupid).
[Removed the invalid test extract, the code in the post below is correct and shows the problem.]
Just for completeness, here is the section of the actual test code:
--- Code: Pascal [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---Var i : QWord; i16 : Int16 = 0; // signed Word - weird, this solved the 0 problem, it seems this value was not correct going in, but this is never used, would use Out but had issues s, s2 : ShortString; // just the relevant vars // i16WriteLn( 'i16 +' );// [note I wanted the string number to be independent of the index (in the large types had to change that due to huge step, sadly pascal doesn't include Step in For loop)]s := '0';For i := 0 To 32767 Do Begin // [hard coded in the max values, normally prefer hex, but decimal looked better here] If Not p_str_num__str_to_num( s, i16, 2, p_str_num__has_sign ) Then Begin WriteLn; WriteLn( '| ERROR CONVERTING: s = ', s, ', i = ', i ); Exit; End; s2 := i16.ToString; // [tested ToString over a range of values and types and found it to be perfect] If s <> s2 Then Begin WriteLn; WriteLn( '| ERROR: converted number does not match' ); WriteLn( ' s = ', s, ', i = ', i, ', i16 = ', i16 ); Exit; End; If Not p_str_num__add_1_to_str_num( s ) Then Begin // this is my own function to add 1 to any string number, no size limit apart from ShortString WriteLn( '| ERROR: problem with add 1' ); Exit; End;End;
Was reading about the whole RHS business with Free pascal after running into QWord problems (nice solution by Thaddy to locally turn off range checking). But this has got me stumped.
So the core question is does anyone have experience of doing mixed type arithmetic where they have found a simple way around pascal jumping the gun and presuming the type or going to the lowest type?
Luckily it wasn't crucial, just means either ditching any idea of working with signed numbers (was toying with idea going the whole way and write the core in assembler, already have quite a bit of assembler code in my pascal, but first it would reduce the hope of making the code portable and more importantly, I'd either have to limit the program to 64 bit machines, or exclude 8 byte types, or try and emulate pascal great 32 bit get around with the Hi Lo record.
[Added: Really sorry, totally forgot my manners. Thank you for any help offered. Phil,]
Program Details:
Lazarus: 3.4 (date: 2024-05-25) FPC: 3.2.2 Version: Win64
Operating system: Windows Pro 10, 22H2
Hardware: Intel Core i5-3570K CPU, 3.40GHz, RAM: 8GB, 64 bit
Bio (give a take a year or 2):
1980s: commercial programmer in small companies, self taught, including assembler (main language dBASEIII)
1990s: Computing degree, introduced to Turbo Pascal, loved it, final year project: my own programming language
Took a long break from computing
2018: returned to hobby programming, found Free Pascal and Lazarus (hooray, didn't have to use C++)
Write mostly file management programs, done a few programs for friends (may one day put stuff on a website)
Thaddy:
That would be caught with {$rangechecks on} if the type is declared as a signed type like shortint or the compiler can see that the range -128..127 is covered.
(This was already the case back in the days of TP)
old_DOS_err:
Hi Thaddy,
Thank you for the fast reply and nice to hear from you, seen your posts many times whilst reading this forum. At least once you have provided a solution when someone has asked a similar question that I have been stuck on. Sorry taken a little while getting back, running more tests and checks.
BTW, forgot to mention this is my first post. Got close to posting a few times, but I'm old school, so do like to work it out myself. But this one beat me. Sorry, a few little glitches in the first code, and some grammar not corrected in the post, was rushing a bit at the end.
Oops, my extracted test code had a major boo boo, that was one of the 99%. That's what I get for rushing and not treble checking. Was extending the extracted tests and all coming up 0, then noticed I had forgot to initialize s to 1, it was zero, hence e * s = 0. I'm blaming senility (funny how senility has dogged much of my coding for over 40 years).
Anyway, went back and checked and the original error is still there, but you were right Thaddy, I used your method for turning off range checking to get around the QWord problem, rather than typecast everything, which was fine for the unsigned. Turns out this has a quite a different outcome in the land of signed types.
The following is much closer to the original and has a correct view of the problem. Essentially, with range checking on I cannot do mixed type calcs, even though not out of range, but with the range checking off, I get the wrong result. Double checked this time, put break point on the original at "256", the string number converting, and with the range checking off, the 2 vars, e and s, are 256 and 1, yet when multiplied, get 0!
--- Code: Pascal [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---Program demo_256;{$mode objfpc}{$H+}{$R+}{$Q+}Uses Classes, SysUtils; { much better test of original problem, in which 256 * 1 = 0 used untyped reference to get around the type problem, but wonder now if this is bringing in new problems with signed vars I've kept in the tries, just so you can see what I have already tried.}Procedure assign_test( Var n ); // take any type Var s : Int8 = 1; // this is the sign - initialize positive pi16 : PShortInt; // signed Word e : Int32; // this is var where the value gets stored into as it builds, really want to keep it down to just 2 pairs, otherwise defeats general type Begin // the core code is copied straight out of the actual conversion routine e := 256; // put a conditional test in the conversion code to stop at "256", could see from hovering on the vars e was 256 and s was 1 pi16 := @n; // now the pointer shares the address with the var passed in (this may be a problem for signed vars, fine for unsigned) WriteLn( '| assign_test: start: e = ', e, ', pi16^ = ', pi16^ ); //pi16^ := Int16( e * s ); // now this hits a range error {$push}{$warn 4110 off}{$R-} // excellent one from Thaddy pi16^ := Int16( e * s ); // now this doesn't hit range error, but guess what, yep, comes as a 0, which was the original error replicated {$pop} WriteLn( '| assign_test: end: e = ', e, ', pi16^ = ', pi16^ ); End; (*Procedure assign_test( Var n ); // this has all the extra tries in, wanted to keep the above cleaner Var s : Int8 = 1; // this is the sign - initialize positive pi16 : PShortInt; // signed Word e : Int32; // this is var where the value gets stored into as it builds, really want to keep it down to just 2 pairs, otherwise defeats general type //i16 : Int16; // new, but not the way I want to go, introducing vars for every type Begin // the core code is copied straight out of the actual conversion routine e := 256; // put a conditional test in the conversion code to stop at "256", could see from hovering on the vars e was 256 and s was 1 pi16 := @n; // now the pointer shares the address with the var passed in (this may be a problem for signed vars, fine for unsigned) WriteLn( '| assign_test: start: e = ', e, ', pi16^ = ', pi16^ ); //pi16^ := Int16( e * s ); // now this hits a range error //pi16^ := Int16( Int16( e ) * s ); // nope, still hits the range error // since know can assign to different types with casting, introducing a new Int16 (ShortInt) var //i16 := Int16( e ); // only need this if using the extra var method //pi16^ := i16 * s; // still hits range check //pi16^ := i16 * Int16( s ); // still hits range check //pi16^ := Int16( i16 * s ); // still hits range check {$push}{$warn 4110 off}{$R-} // excellent one from Thaddy //pi16^ := Int16( e * s ); // now this doesn't hit range error, but guess what, yep, comes as a 0, which was the original error replicated pi16^ := Int16( e ) * Int16( s ); // thought if I equalised them, no joy, still 0 //pi16^ := ShortInt( e ) * ShortInt( s ); // just checking, it is pascal {$pop} WriteLn( '| assign_test: end: e = ', e, ', pi16^ = ', pi16^ ); End;*) Procedure assign_test2( Var n ); // going to drop below 256 to see if that is the problem Var s : Int8 = 1; // this is the sign - initialize positive pi16 : PShortInt; // signed Word e : Int32; // got a really weird value error on int word, passed in '0', seemed to be 0 int i16, the pointer, yet when got back to test, the value of the var was Begin // ok, this just got weirder, still hit the range check at 250, but in removing the range check, now pi16 showed -6, but now i16 on return correctly showed 250! e := 250; // now below pi16 := @n; WriteLn( '| assign_test: start: e = ', e, ', pi16^ = ', pi16^ ); // correctly show 250 and 10 //pi16^ := Int16( e * s ); // really weird now this hits a range error {$push}{$warn 4110 off}{$R-} // excellent one from Thaddy pi16^ := Int16( e * s ); // now this doesn't hit range error, but guess what, yep, comes out as 0, which was the original error replicated {$pop} WriteLn( '| assign_test: end: e = ', e, ', pi16^ = ', pi16^ ); End; Procedure test_i16; Var i16 : Int16 = 0; // the return var s : Int8 = 1; // this is the sign e : Int32 = 0; // the converted value Begin e := 256; i16 := Int16( e ); WriteLn( '| Int16( e ): i16 = ', i16 ); // 256 e := 10; // arbitrary, since passing as Var don't want this to equal 256 assign_test( e ); // this is now the same method as the actual and should be 256 //assign_test2( e ); // this is now the same method as the actual and should be 256 i16 := Int16( e * s ); WriteLn( '| i16 = ', i16 ); // YEP 0, HOW??????? End; Begin test_i16; WriteLn; WriteLn('Finished...'); ReadLn;End.
If anyone has done mixed type signed calculations, can you spot what I am missing, or please let me know if there is a way around this impasse, without either abandoning signed types altogether or going back to assembler (or as done a few times, create overload copies for every type, even then with what I've seen not sure even that would work).
Thank you for any help (forgetting my manners, sorry),
Phil
Thaddy:
--- Code: Pascal [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---pi16 : PShortInt; // signed Word? No, shortint is a signed byte!// should be pi16 : PSmallInt; // This is a signed WordSee:
https://www.freepascal.org/docs-html/current/prog/progsu154.html
Wrong pointer type can hide range errors, moreover pointer types in general can under circumstances hide range errors.
But using the correct type should fix it.
BTW with range checks off and or overflow checks off, 0 -zero - is the expected result, since the low byte of 256 is....., wait for it ..... 0.
If you got the range check error it was correct, because of the wrong type.
old_DOS_err:
Thanks again Thaddy for the prompt reply.
The reason why it is PshortInt is that is it a general container, I build a number into that. I also have i64 : PInt64 for 8 byte values. I've never had a type issue, other than parameters of course, using DWord as a container, then stepping down to the correct size. This method worked fine for unsigned. I write file managent programs, so never have to deal with signed numbers. And yes, as an assembler program I know about 256, if i was incrementing values in assembler, I would just check the OF flag, probably the sign flag as well (SF I think, don't think I've used it, says a lot). I had hoped that pascal was a bit more flexible. But I think the upshot of this experience is that if using signed, then I cannot assume any leeway from pascal.
Ok, thanks again. I'm going to look at the original code and I may have to introduce specific variables for all signed types (as I say, already have ShortInt and Int64, so just need to add Int16 and Int8, which I already have in pointer form (never had such a short function with so many variables, but that is the price I suppose for trying to get around the type restrictions on parameters).
I hate giving up on code, bit of terrier that way, good and bad quality to have. But it would be nice to see this conversion routine to conclusion. This was my first post and after the hash I made of it, may well be my last :)
If I still can't get the signed to work, but feel they should do, I'd like to do another post, just rather better constrcted than this. Where would you recommend I post that to? Wasn't sure if this was the right category.
Phil
Navigation
[0] Message Index
[#] Next page