As an alternative to Masm32
FloatToStr and
crt_sprintf, the attached FloatStr.asm provides an easy way to print numbers using the print
Str$(123) syntax.
Reasons to use it (at your own risk, of course):
- faster than FloatToStr (*2) and crt_sprintf (*14)
- flexible (integer, reals, immediate reals, registers can be freely mixed)
- full REAL10 precision
- free :bg
Reasons not to use it:
- not tested for all possible combinations
Testing float$
PI 3.14159265358979323846 (there are more digits...)
Str$ 3.141592653589793238
crt 3.14159265358979310
mov eax, 333: eax/3 = 111
Forced real: 123.0000000
Forced integer: 333/4 = 83
Str$ performs simple calculation (up to 5 arguments,
CAUTION: sequential, i.e. 2*5+2*5=(10+2)*5):
See the result of a calculation involving 5 different data types
(imm32, immReal, reg32, real10, reg8):
esi=52, ebx=23:
120/2.5+esi+MyReal10+bl = 123.4567890123456789
Timings on Celeron M:
291 cycles for Str$
605 cycles for FloatToStr
4317 cycles for crt_sprintf
99 cycles for dwtoa (int32 only)
To use it, extract all files to a folder of your choice and
run InstallFloatStr.batNote this is the replacement for the slightly inexact
float$ shown here (http://www.masm32.com/board/index.php?topic=9756.0).
EDIT: Qwords do not print directly, but
edx::eax can be used:
.data
MyQword dq 1234567890123456789
.code
mov eax, dword ptr MyQword
mov edx, dword ptr MyQword[4]
print Str$("\nA qword: %i", edx::eax)
QuoteA qword: 1234567890123456789
Edit(2+3+4): New version with increased precision attached.
Edit(5): Chr$(eax) example added
Edit(6): MovVal example added:
MovVal MyReal10, chr$("-123456.789e-33")Edit(7): Bugfix - used to return 0.0 if ecx was zero on proc entry
[attachment deleted by admin]
cool Jochen
looks like you worked hard on this one
i thought you might have something that displayed 64-bit integers up your sleeve - lol
i noticed that your value for Pi has 21 usable (i.e. correct) digits
i only got 20 - i may need to re-visit my conversion method
EDIT
oh - lol - that is a string - ya got me
Quote from: dedndave on July 07, 2009, 03:08:26 AM
i noticed that your value for Pi has 21 usable (i.e. correct) digits
i only got 20 - i may need to re-visit my conversion method
EDIT
oh - lol - that is a string - ya got me
Yes, that was a string - but with the attached new version, you do get 19 digits, see below.
For getting the full 19, use
Str$("%Jf", MyReal10).
Note that the inherent FPU precision implies that the 19th digit is not guaranteed to be exact. Use uppercase
I for 18 digits.
1x234567890123456789 digits precision
PI 3.14159265358979323846... (there are many more digits...)
Str$ 3.141592653589793238 (jj2007)
crt 3.14159265358979310 (CRT printf or sprintf)
FloatTo 3.141593 (Masm32 lib FloatToStr)
Log2(e) 1.44269504088896340736...
Str$ 1.442695040888963407
crt 1.44269504088896340
FloatTo 1.442695
Lg2(10) 3.3219280948873623480...
Str$ 3.321928094887362348
crt 3.32192809488736220
FloatTo 3.321928
Lg10(2) 0.30102999566398119521...
Str$ 0.3010299956639811952
crt 0.30102999566398120
FloatTo 0.30103
Lge(2) 0.69314718055994530942...
Str$ 0.6931471805599453094
crt 0.69314718055994529
FloatTo 0.6931472
mov eax, 333: eax/3 = 111
A qword: 1234567890123456789 (using edx::eax)
Forced real: 123.0000000
Forced integer: 333/4 = 83
Str$ performs simple calculations
(up to 5 arguments, CAUTION: sequential, i.e. 2*5+2*5=(10+2)*5):
See the result of a calculation involving 5 different data types
(imm32, immReal, reg32, real10, reg8, esi=52, ebx=23):
120/2.5+esi+MyReal10+bl = 123.4567890123456789
Syntax: print Str$("\nThe value is %9f", MyVar1*MyVar2/bx+12.34+esi)
Qualifiers: \n newline, \t tab
%i = int32; %f = real, 7 digits (default)
%3f = real, 3 digits; %Af = real, 10 digits; %Jf = real, 19 digits
EDIT: To avoid confusion, latest version from now on always attached to first post.
i ran the FPU value for Pi through my eval.exe program:
+3.14159265358979323851280895940618620443274267017841339111328125 FPU representation of Pi
+3.1415926535897932384626.......................... actual value of Pi
20 usable decimal digits (just barely - lol)
Quote from: dedndave on July 14, 2009, 11:58:24 AM
i ran the FPU value for Pi through my eval.exe program:
+3.14159265358979323851280895940618620443274267017841339111328125 FPU representation of Pi
+3.1415926535897932384626.......................... actual value of Pi
20 usable decimal digits (just barely - lol)
Str$ yields 3.141592653589793
2384626... and that is what you also get as valid digits. The 5 is already incorrect, so it's 19 digits :bg
Raymond's Simply FPU says REAL10 has 19.5 digits precision. No way to go beyond that... unless we embark for REAL16.
it is considered a usable digit, as the "4626" rounds up to "5000"
it is coincidental, however - it applies to this specific value - not all values
if you have to represent Pi with 20 decimal digits, what would the last digit be? (answer: 5)
the value shown is the precise evaluation of the FPU's FLDPI binary representation of Pi
(3.14159265358979323851280895940618620443274267017841339111328125)
if Ray returns a different value, he is cheating somehow - lol
i would think the FLDPI has the geometrically closest value to Pi that may be represented in extended float
QuoteStr$ yields 3.1415926535897932384626
that is 23 decimal digits - i think you are looking at a value that was defined as an ascii string
QuoteRaymond's Simply FPU says REAL10 has 19.5 digits precision. No way to go beyond that
that contradicts the 23 digit value
Quote from: dedndave on July 14, 2009, 01:24:27 PM
it is considered a usable digit, as the "4626" rounds up to "5000"
Sure?
12.34690 rounded to the nearest tenth is 12.3 (http://www.321know.com/est-dec-round.htm)
Quote
QuoteStr$ yields 3.1415926535897932384626
that is 23 decimal digits
I meant the ones up to 3238 - that's why I put them in blue.
yes - i am sure
1.4626 rounded to the nearest tenth is 1.5
3.141592653589793238295968524909085317631252110004425048828125 next lower extended real value
3.14159265358979323851280895940618620443274267017841339111328125 FPU Pi
3.1415926535897932387296493939032870912342332303524017333984375 next higher extended real value
notice how they all end with 5 - that is because ALL binary fractions end with 5 when represented in decimal
binary decimal
.1 .5
.01 .25
.001 .125
and so on
Quote from: dedndave on July 14, 2009, 01:55:54 PM
3.141592653589793238295968524909085317631252110004425048828125 next lower extended real value
3.14159265358979323851280895940618620443274267017841339111328125 FPU Pi
3.1415926535897932387296493939032870912342332303524017333984375 next higher extended real value
I see what you mean, Dave. The problem is that we are demanding something from the FPU that it cannot perform: Getting the 46. The nearest to the 46 that is representable with 64 digits is the 51 - but one tick of the LSB is -22/+21, so even the 5 is no longer a valid digit, if not by accident. Olly cuts off as 32380, which is slightly incorrect in that the 0 should not be shown. Crt sprintf behaves even worse...
that's right
here is the problem:
if you round 3.14159265358979323851 to 19 digits, it is 3.141592653589793239 - you induce an error by rounding
well - that is half the problem - lol
before we ever got our hands on the binary value from the FPU,
the guys at intel had to round the correct value of Pi to the nearest value representable by their numbering system
THEN, we come along and round it again to convert it to a decimal string
3.1415926535897932384626... actual value of Pi
3.1415926535897932385128... closest extended real binary value
3.141592653589793239 our best interpretation of that binary value
3.141592653589793238 actual value of Pi rounded to 19 digits
i suppose "our best interpretation" might actually be the 20 digit value 3.1415926535897932385
but, that is stretching the 191/2 digit resolution of the 64-bit binary
i don't think it is practical to always render 20 digits
you might do something like this:
if the leading digit is 1-4, render 20 digits
otherwise, render 19 digits
(the 1-4 is a guess - we would have to find the correct value to use in place of the 4)
to complicate issues a bit, we could examine a few leading digits to determine the switch-over point
typically, when they refer to "1/2 digit", they mean a leading 1
many digital volt-meters display 31/2 or 41/2 digits
most of them mean a leading 1
a few use a leading 3 - they display 5 digits up to 3.9999, then switch to 4 digits at 4.000
EDIT
examining the 2 leading digits would be a practical way to determine the switch point
Quote from: dedndave on July 14, 2009, 03:05:44 PM
a few use a leading 3 - they display 5 digits up to 3.9999, then switch to 4 digits at 4.000
Like this?
include \masm32\include\masm32rt.inc
include \masm32\macros\FloatStr.asm
.code
start:
print Str$("%8f\n", 3.99999999)
print Str$("%9f\n", 3.99999999)
exit
end start
4.0000000
3.99999999
you have the right idea, Jochen
now all you need is the right cross-over point
you should examine with care whether or not digits are "usable"
3.141592653589793238 295968524909085317631252110004425048828125 next lower extended real value
3.141592653589793238 51280895940618620443274267017841339111328125 FPU Pi
3.141592653589793238 7296493939032870912342332303524017333984375 next higher extended real value
at this point, we no longer take into consideration what the original intended value was meant to be (Pi in this case)
the value you are converting into a string has a precise evaluation
in this case, that would be 3.14159265358979323851, as you have no intention of displaying more than 20 digits
rounding it to 19 digits would be 3.141592653589793239
anything we display from 3.1415926535897932381 to 3.1415926535897932389 is closer to the precise value than rounding it to 19 digits
that one is coincidental, because the 20th digit is a 5
if the 20th digit were a 3, rounding the value to 19 digits would round downward (that 3 would be truncated)
notice that the 20th digit could be a 2, and it would still be closer to the precise value than if it were rounded to 19 digits
in fact, we could display 20 digits with anything from 2 to 5 at the end and it would be closer to the precise value than if it were rounded off
the point is, "usable digits" are not neccessarily the correct value
they are usable, so long as they get us closer to the precise value than if they were rounded off
i am guessing that if the first 2 signifigant digits of a value are 10 through 36, 20 usable digits are available
if they are 37 through 99, only 19 digits are usable
that guess is based on 2 x (2^64) = 36,893,488,147,419,103,232
at that value, each change of 1 to the lsb changes the the result by 2
403F_FFFFFFFF_FFFFFFFF: +36893488147419103230
4040_80000000_00000000: +36893488147419103232
4040_80000000_00000001: +36893488147419103236
4043_9FFFFFFF_FFFFFFFF: +368934881474191032288
4043_A0000000_00000000: +368934881474191032320
4043_A0000000_00000001: +368934881474191032352
4046_C7FFFFFF_FFFFFFFF: +3689348814741910322944
4046_C8000000_00000000: +3689348814741910323200
4046_C8000000_00000001: +3689348814741910323456
i added hex output to the prog - lol
maybe i shoulld do another decade or two
you can see what happens as i go above that value - the lsd changes by 4 instead of 2 in the first example
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
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
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...
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
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.
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 19
1/
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)
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 */
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
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")
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.
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 2168C23
2. Olly displays it as 3.1415926535897932380
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.
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
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
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.