News:

MASM32 SDK Description, downloads and other helpful links
MASM32.com New Forum Link
masmforum WebSite

Basic-style Str$

Started by jj2007, July 05, 2009, 11:45:44 PM

Previous topic - Next topic

jj2007

Hmmm... interesting. However, I guess we are stuck with the problem that we do not know a priori what was the intended value - we get what the FPU can give us, in a 19.x digits resolution. So instead of trying to "interpret" the intentions of the number's unknown author, we should simply cut off at 19 digits. Which, by the way, seems not to be quite understood by the CRT guys - they allow higher, and therefore fake, precision than they can theoretically achieve:

1/7 with Str$, max precision:
0.1428571428571428571
same with crt_printf:
0.1428571428571429
0.14285714285714285
0.142857142857142850
0.1428571428571428500


The first crt value is correctly rounded - the others are plain wrong.

include \masm32\include\masm32rt.inc
include \masm32\macros\FloatStr.asm

.data
r7inv real8 0.0
r7 real8 7.0

.code
start:
print Str$("1/7 with Str$, max precision:\n%Jf\nsame with crt_printf:\n", 1/7)
fld1
fld r7
fdiv
fst r7inv
invoke crt_printf, chr$("%.16f"), r7inv
print chr$(13, 10)
invoke crt_printf, chr$("%.17f"), r7inv
print chr$(13, 10)
invoke crt_printf, chr$("%.18f"), r7inv
print chr$(13, 10)
invoke crt_printf, chr$("%.19f"), r7inv
getkey
exit
end start

dedndave

you shouldn't have to know the intended value
1) get 20 digits - or 21, or even 22 if possible (that way digit #21 has not already been rounded)
2) examine the first 2 (or more) digits to determine the round-off length (4 would be great - if 3688 or less -> 20 digits)
3) round the string to that length

that method will yield
3.1415926535897932385
instead of
3.141592653589793238
or
3.141592653589793239

jj2007

Quote from: dedndave on July 14, 2009, 10:21:43 PM
you shouldn't have to know the intended value
1) get 20 digits - or 21, or even 22 if possible

Not possible...

Quote3.1415926535897932382959     next lower extended real value
3.1415926535897932385128  FPU Pi
3.1415926535897932387296    next higher extended real value

The 5 is digit 20, and it is not even the intended value. It just happens to be a bit (pun intended) closer to the intended 4 than 2 or 7...

dedndave

i evaluated the value to way more than 22 digits
so, it is possible
it is a usable digit - on a value that has a 3 at the end - still usable - see previous post
or just ignore me - lol
i know you too well, my friend - you are thinking "that is going to cost xxx clock cycles" - lol

jj2007

Quote from: dedndave on July 14, 2009, 10:36:39 PM
i evaluated the value to way more than 22 digits
so, it is possible
it is a usable digit - on a value that has a 3 at the end - still usable - see previous post
or just ignore me - lol
i know you too well, my friend - you are thinking "that is going to cost xxx clock cycles" - lol

The engineer's rule is "if you display a digit, it should be the correct one, otherwise do not display it". CRT violates that rule.

Dave, I will ignore you for technical reasons - it's well after midnight over here. Little suggestion: Change example - 1/7 is a good one., since we know exactly the corect output.

dedndave

QuoteThe engineer's rule is "if you display a digit, it should be the correct one, otherwise do not display it".
i am an engineer
and that isn't the rule - lol
to get the full 191/2 digits, you can do as i suggested
or just toss out the 1/2 digit
fact is, that is probably more convenient in terms of formatting text (easier to make all the columns the same width)

MichaelW

Quote from: jj2007 on July 14, 2009, 10:43:35 PM
The engineer's rule is "if you display a digit, it should be the correct one, otherwise do not display it". CRT violates that rule.

IMO that is a reasonable rule, but the CRT does not violate it. The CRT allows you to specify the number of significant digits, and specifying more digits than the precision for the type violates the rule. One can take issue with the decision to eliminate support for 80-bit long doubles, but it's not like the technical details are some sort of secret. The decimal-digit precision for the relevant types is normally specified in float.h:

#define DBL_DIG   15        /* # of decimal digits of precision */
#define FLT_DIG   6         /* # of decimal digits of precision */
#define LDBL_DIG  DBL_DIG   /* # of decimal digits of precision */

eschew obfuscation

dedndave

19 digits IS overkill - for 98% of all applications
as i said before, i normally convert to single for display
sometimes, i display full resolution so that i may decide how many digits are meaningful
the only things that i may want to display with great accuracy and resolution are times or frequencies
after giving it more thought, 19 digits can even be misleading
that is, if much calculation has been performed
once you have made a few simple multiplications, divisions, additions, or subtractions,
the 19th digit is merely an attempt to retain more detail than needed
any trigonometry or exponentiation really makes that digit meaningless - lol
also, the constants that you load may have a slight deviation from the "intended" value, to begin with
10(Pi-3) is an example of how quickly digits may be lost
on the other hand, if i load 2 constants and multiply them, the 19th digit should be "in the right neighbourhood" - lol

jj2007

Quote from: MichaelW on July 15, 2009, 12:39:52 AM
Quote from: jj2007 on July 14, 2009, 10:43:35 PM
The engineer's rule is "if you display a digit, it should be the correct one, otherwise do not display it". CRT violates that rule.

IMO that is a reasonable rule, but the CRT does not violate it. The CRT allows you to specify the number of significant digits, and specifying more digits than the precision for the type violates the rule


I cannot completely disagree :bg

(sounds familiar: "this bug is by design")

MichaelW

A maximum of 19 digits would be reasonable for an 80-bit long double. For example, in a float.h copyrighted 1987, when 80-bit long doubles were supported:

#define LDBL_DIG  19

But I agree that the repetitive approximations involved in complex calculations do tend to make the last few digits less meaningful, and I think this was at least part of the justification for removing support for 80-bit long doubles.
eschew obfuscation

jj2007

Quote from: MichaelW on July 15, 2009, 07:05:28 AM

But I agree that the repetitive approximations involved in complex calculations do tend to make the last few digits less meaningful, and I think this was at least part of the justification for removing support for 80-bit long doubles.


It is not that bad, actually:
                        1x234567890123456789 digits precision
PI                      3.14159265358979323846...
Str$                    3.141592653589793238
Pi*333/333              3.141592653589793238
Pi+1.2345678-1.2345678  3.141592653589793238
Pi+1.2345*10/10-1.2345  3.141592653589793238


Str$ is not intelligent enough to eliminate the 333/333 - the calculations are actually being performed, apparently without losing a single digit.

EDIT: A check with Olly reveals that PI is
4000 C90FDAA2 2168C235 before the calculations and
4000 C90FDAA2 2168C234 after the calculations.
According to Dave's routine, this translates to
3.1415926535897932385128  original PI
3.1415926535897932382959  PI*333/333
They both round to the same 19-digit number, so the last digit (8) is not lost but with a slightly different set of calculations it might have become a 9. Note this is an issue only for calculating numbers, not for simply displaying an extended real.

EDIT(2): For the calculation Pi+1.2345*10/10-1.2345, the internal result is 4000 C90FDAA2 2168C232. Olly displays it as 3.1415926535897932380

jj2007

I added a Basic-style Chr$(). The Masm32 library version does not allow
mov eax, "A"
print chr$(eax)  ; fails


The new Chr$() is compatible with chr$ but allows to embed registers or variables into the text. Example:

QuoteMyTest proc
LOCAL Lv1:DWORD, Lv2:BYTE
  mov Lv1, "c"
  mov Lv2, "d"
  mov eax, "a"
  print Chr$(13, 10, "Chr$ accepts registers: ", eax, ", global: ", gChar0, " and local DWORD: ", Lv1, " or BYTE: ", Lv2, " variables", 13, 10, 10)

  mov ebx, "A"
  .Repeat
     print Str$(ebx)
     print Chr$(" corresponds to ", ebx, 13, 10)
       inc ebx
  .Until ebx>"J"
  ret
MyTest endp

Output:
QuoteChr$ accepts registers: a, global: b and local DWORD: c or BYTE: d variables

65 corresponds to A
66 corresponds to B
67 corresponds to C
68 corresponds to D
69 corresponds to E
70 corresponds to F
71 corresponds to G
72 corresponds to H
73 corresponds to I
74 corresponds to J

You can use al, cl, dl etc. instead of eax, ecx, edx. Variables can be any size, but only the lobyte will be used.
Attachment below the top post.

dedndave

i like your library, my friend
although, i wrote a function for this that is as fast as i could make it - lol
as Hutch said, "it's console mode - who cares how fast it is"
the fact that you have to call an API function at all seems slow
if i wanted to make a really fast one, i suppose i would stick the char into the video buffer

jj2007

Quote from: dedndave on July 26, 2009, 10:28:52 PM
i like your library, my friend
although, i wrote a function for this that is as fast as i could make it - lol
as Hutch said, "it's console mode - who cares how fast it is"
the fact that you have to call an API function at all seems slow
if i wanted to make a really fast one, i suppose i would stick the char into the video buffer

Well, Chr$ is not necessarily for console output - you might as well use it for a MessageBox. I wanted a function as flexible as the BASIC equivalent, and optimised for size (but there is hardly a "speedier" version):
00401001       |.  57                      push edi
00401002       |.  BF 881B4000             mov edi, 00401B88  <<< string buffer
00401007       |.  8807                    mov [edi], al
00401009       |.  C647 01 00              mov byte ptr [edi+1], 0
0040100D       |.  8BC7                    mov eax, edi
0040100F       |.  5F                      pop edi
00401010       |.  8BD0                    mov edx, eax
00401012       |.  57                      push edi
00401013       |.  BF 881B4000             mov edi, 00401B88  <<< string buffer, the same again
00401018       |.  8807                    mov [edi], al
0040101A       |.  C647 01 00              mov byte ptr [edi+1], 0
0040101E       |.  8BC7                    mov eax, edi
00401020       |.  5F                      pop edi
00401021       |.  8BD0                    mov edx, eax

jj2007

Time for an update: The counterpart to Str$ is Val("123.456"). The new version, attached at the top of this thread, implements Val as MovVal dest, src. Examples:

MyPI   db   "3.141592653589793238", 0

MovVal eax, chr$("123.456")
print Str$("MbVal eax, 123.456:\t%i\n",eax)

MovVal ecx, chr$("456.789")
print Str$("MbVal ecx, 456.789:\t%i\n",ecx)
MovVal edx, chr$("123.456e7")
print Str$("MbVal edx, 123.456e7:\t%i\n\n",edx)

print "Test PI with various destination sizes:", 13, 10, 9, 9
print offset MyPI, 13, 10

MovVal MbReal10, offset MyPI ; works fine
print Str$("MbVal10:  \t%Jf\n",MbReal10)

MovVal MbReal8, offset MyPI
print Str$("MbVal8:   \t%Jf\n",MbReal8)

MovVal MbReal4, offset MyPI
print Str$("MbVal4:   \t%Jf\n\n",MbReal4)


Output:
MovVal eax, 123.456:     123
MovVal ecx, 456.789:     457
MovVal edx, 123.456e7:   1234560000

Test PI with various destination sizes:
                3.141592653589793238
MovVal10:        3.141592653589793238
MovVal8:         3.141592653589793116
MovVal4:         3.141592741012573242


As usual, timings are competitive - see MovValTimings.exe in the archive.