flux capacitors and gigga-watts



Hi!

Qbasic is great as a hobbyist programmable scientific calculator. My special interest is electronics so I write many little ditties to analyze circuitry. But, I get tired of the confusing numerical output format that is default in Qbasic. Some examples:

An output of electrical inductance might look like this: 0.01267983 Henrys. This value might be correct, but is difficult to read because it is in non-standard form. What we really want to see is this: "12.68 milli-Henrys". This form uses a standard scientific prefix and rounds the value off to the four most significant digits.

To be "readable", 0.0001267983 Henrys needs to be output as "126.8 micro-henrys". And, 2099999486 watts need to be output as "2.100 gigga-watts".

Note that the prefix "gigga" is a term coined by the noted physicist, Emmett Brown who disappeared one stormy night in late 1985, taking with him the secrets of his Flux Capacitor and setting back the development of time travel theory for many decades. Reports that Doctor Brown was killed by middle-eastern terrorists are unfounded and his disappearance has remained a mystery to this day.

Yea. Well anyway... The problem is formatting the output to print only what is important -- the rounded MSD's -- regardless of where the decimal point is. I haven't found any way to do it using basic's "USING". Anybody have any ideas?

I wrote a small function to do the prefixes:

[code]
DECLARE FUNCTION pformat$ (n!, units$)

'a little test stub:
start:
INPUT x!
CLS
PRINT x!; "watts = "; pformat$(x!, "watts")
GOTO start


FUNCTION pformat$ (n!, units$)

'Nine standard scientific prefixes.....
DIM prefix$(8)
prefix$(0) = "Tera-" '1e12 - trillions
prefix$(1) = "Giga-" '1e9 (or "gigga") - billions
prefix$(2) = "Mega-" '1e6 - millions
prefix$(3) = "Kilo-" '1e3 - thousands
prefix$(4) = " " 'units 1e0 - ones
prefix$(5) = "milli-" '1e-3 - thousandths
prefix$(6) = "micro-" '1e-6 - millionths
prefix$(7) = "nano-" '1e-9 - thousand-millionths
prefix$(8) = "pico-" '1e-12 - million-millionths

limit! = 1E+12
FOR i% = 0 TO 8
IF ABS(n!) > limit! THEN
pformat$ = STR$(n! / limit!) + " " + prefix$(i%) + units$
EXIT FOR
END IF
limit! = limit! / 1000!
NEXT i%

'crude error handling...
IF i% = 9 THEN
PRINT "input error"
STOP
END IF

END FUNCTION
[/code]
And a slightly more simple one that just uses common abbreviations for the prefixes as is common in science:

[code]
DECLARE FUNCTION pformat$ (n!, units$)

'just a test stub:
start:
INPUT x!
CLS
PRINT pformat$(x!, "watt"); " Flux Capacitor"
GOTO start


FUNCTION pformat$ (n!, units$)

'prefixes ...
' "T" - Tera - 1e12 - trillions
' "G" - giga - 1e9 - billions (or "gigga")
' "M" - mega - 1e6 - millions
' "K" - kilo - 1e3 - thousands
' " " - (no prefix) - 1e0 - ones
' "m" - milli - 1e-3 - thousandths
' "u" - micro - 1e-6 - millionths
' "n" - nano - 1e-9 - thousand-millionths
' "p" - pico - 1e-12 - million-millionths

CONST prefix$ = "TGMK munp"

limit! = 1E+12
FOR i% = 1 TO 9
IF ABS(n!) > limit! THEN
pformat$ = STR$(n! / limit!) + " " + MID$(prefix$, i%, 1) + units$
EXIT FOR
END IF
limit! = limit! / 1000!
NEXT i%

'some crude error handling...
IF i% = 10 THEN
PRINT "input error"
STOP
END IF

END FUNCTION
[/code]
But inputting 2099999486 as in the above "Gigga-watt" example, the output is 2.099999 Gwatts. How can I make it round of to four MSD's so the output is 2.100 Gwatts?

Ideas, suggestions, algorithms, code welcome! Thanks!

rg

Comments

  • Hi,

    Try this

    num = 2.4563548#
    PRINT num
    num = INT((num + .00005) * 10000) / 10000
    PRINT num

    Adding .00005 will increase the fourth decimal place by one if the
    fifth decimal place is 5 or larger.

    Multiplying by 10000 puts the first 4 decimal places to the left of the decimal point.

    Using 'INT ' removes the remaining decimal places.

    Dividing by 10000 puts the first four decimal places back to the right
    of the decimal point.

    Hope this helps


    Pappy
    You learn something new everyday.

  • [b][red]This message was edited by the walrus at 2003-11-24 10:13:52[/red][/b][hr]
    I love Back to the Future! Anyway, I tried to produce a solution for you. I tested it with the 2 examples you gave and it worked ok, but there could easily be some flaws in it.. This'll give you an idea on how to do it, atleast.

    [code]
    DECLARE FUNCTION xformat$ (iStr AS STRING, sDigits AS INTEGER)
    DECLARE FUNCTION pformat$ (n!, units$)

    'just a test stub:
    start:
    INPUT x!
    CLS
    PRINT xformat$(pformat$(x!, "watt"), 4); " Flux Capacitor"
    GOTO start

    FUNCTION pformat$ (n!, units$)

    'prefixes ...
    ' "T" - Tera - 1e12 - trillions
    ' "G" - giga - 1e9 - billions (or "gigga")
    ' "M" - mega - 1e6 - millions
    ' "K" - kilo - 1e3 - thousands
    ' " " - (no prefix) - 1e0 - ones
    ' "m" - milli - 1e-3 - thousandths
    ' "u" - micro - 1e-6 - millionths
    ' "n" - nano - 1e-9 - thousand-millionths
    ' "p" - pico - 1e-12 - million-millionths

    CONST prefix$ = "TGMK munp"

    limit! = 1E+12
    FOR i% = 1 TO 9
    IF ABS(n!) > limit! THEN
    pformat$ = STR$(n! / limit!) + " " + MID$(prefix$, i%, 1) + units$
    EXIT FOR
    END IF
    limit! = limit! / 1000!
    NEXT i%

    'some crude error handling...
    IF i% = 10 THEN
    PRINT "input error"
    STOP
    END IF

    END FUNCTION

    FUNCTION xformat$ (iStr AS STRING, sDigits AS INTEGER)
    DIM nStr AS LONG, xVal AS DOUBLE, xStr AS STRING, iiStr AS STRING
    DIM zStr AS STRING
    iiStr = RTRIM$(LTRIM$(iStr))
    nStr = INSTR(iiStr, " ")
    IF nStr = 0 THEN
    zStr = ""
    ELSE
    zStr = LTRIM$(RIGHT$(iiStr, LEN(iiStr) - nStr))
    END IF
    nStr = INSTR(iiStr, ".")
    xVal = INT(VAL(iiStr) * 10 ^ (sDigits - nStr + 1) + .5) / 10 ^ (sDigits - nStr + 1)
    xStr = LTRIM$(STR$(xVal))
    IF LEN(xStr) < sDigits AND INSTR(xStr, ".") = 0 THEN
    xStr = xStr + STRING$(sDigits - LEN(xStr), "0")
    ELSEIF LEN(xStr) < sDigits + 1 AND INSTR(xStr, ".") <> 0 THEN
    xStr = xStr + STRING$(sDigits - LEN(xStr) + 1, "0")
    END IF
    xformat$ = xStr + " " + zStr
    END FUNCTION
    [/code]


    And I don't know if you've run across this yet, but when you're making these microscopic calculations, you'll have much more exact results if you convert to integer (mili or whatever the case may be) before making the calculations, and then either display the data in converted form or convert back to it's original form if you need to.. QBasic, or computers in general, have a flaw when dealing with floating point numbers.. The problem is that they work on binary and we, in the world around us, work on base 10. Try this:

    [code]
    DIM y AS DOUBLE
    CLS
    y = 1
    FOR x = 1 TO 22
    y = y + .1
    PRINT y
    NEXT
    [/code]

    We, in the base 10 world, know that 1 + .1 + .1 + .1.. (22 times) = 3.2 exactly, but the computer says it equals 3.20000003278256. The reason for this is that there's no way to use .1 as a number in binary.. If you convert it to binary you end up with a repeating number after the decimal point (or "binary point".. whatever).. thus it has to be cut off somewhere, and that's why it starts producing errors. Now, in truth, 3.20000003278256 doesn't make any difference from 3.2, especially when you round to significant digits, but try it with y being a SINGLE instead of just a double. After just 22 loops it thinks 1 + .1 + .1.. (22 times) = 3.199999, which although is still 3.200 with significant digits, should point out that after many calculations it wouln't be hard for a flaw to creep in.. Especially when working with numbers like .00000001 instead of .1. That is why, if you're trying to get exact numbers with very small floating points, it is a good idea to convert to an integer expression, and then back. For example:

    [code]
    DIM y AS LONG
    CLS
    y = 10
    DO
    y = y + 1
    PRINT y / 10
    LOOP
    [/code]

    You can run the above code for days without being given any incorrect answers. The only time you'll have an error is when y reaches 2,147,483,648 and can no longer hold such a large number as a LONG. Of course in the real world, adding DOUBLES works fine, and of course that is the point of significant digits, is it not? To account for any errors that may have been made by uncontrollable factors while reaching the answer.. Of course, in reality you can probably go on using DOUBLES and be completely satisfied. I just thought you may want to know, since scientists can also sometimes be sticklers for exact answers, and if you loop a DOUBLE enough (A LOT) you could end up with a slightly incorrect answer.






  • : I love Back to the Future! Anyway, I tried to produce a solution for you. I tested it with the 2 examples you gave and it worked ok, but there could easily be some flaws in it.. This'll give you an idea on how to do it, atleast.

    Well, it worked on some, but not all the inputs. Although, in retrospect I think at least some of the problems were in my pformat() code. But as you suggested, it got me off onto another path that has led to a solution.

    : And I don't know if you've run across this yet, but when you're making these microscopic calculations, you'll have much more exact results if you convert to integer (mili or whatever the case may be) before making the calculations, and then either display the data in converted form or convert back to it's original form if you need to.. QBasic, or computers in general, have a flaw when dealing with floating point numbers.. The problem is that they work on binary and we, in the world around us, work on base 10.

    Yes! In fact I run into decimal/binaryfp/decimal conversion inaccuracies all the time. Most of the time it's not a problem in hobby-type electronics and using qbasic's single precision gives more than enough accuracy for real-world applications. It is one thing to calculate the value of a capacitor to 7 or 15 digits, but if you walk into the parts house and say "Gimme a 0.000000116573000000001 Farad capacitor", all you will get is a very strange look. What you really want is a ".12 microfarad" Most component parts values are accurate only to within 5 or 10 percent which translates to only 2 or 3 significant figures.

    This is what I am looking for in my programs -- calculations with not-perfect, but reasonably accurate precision, and real-world formatted outputs.

    Computer precision and very large or very small numbers lead to a situation where qbasic will switch to scientific notation for output or string conversion. It is hard to predict when the switch will happen. At times it was happening as soon as I converted the incoming double to a string, and at other times it would happen during the round-off. This was one major reason I was having so much trouble with the format routine. As for the input conversion, I finally accepted that it WILL happen, and prepared for both types of string conversion. Your "conversion to long integer" idea looks like it would do the job, but I went one step farther -- I just rounded by processing the string, character by character:

    [code]
    'this version is compatible with QB 4.5

    DECLARE FUNCTION pformat$ (n AS DOUBLE, d AS INTEGER, u AS STRING)
    CLS
    DO
    INPUT a#
    PRINT TAB(10); a#, pformat(a#, 3, "parsecs")
    LOOP

    FUNCTION pformat$ (n AS DOUBLE, d AS INTEGER, u AS STRING)

    'the unit prefixes: pico, nano, micro, milli, (none), Kilo, Mega, Giga, Tera
    CONST prefix = "pnum KMGT"

    DIM nstr AS STRING 'string representaion of the number
    DIM sign AS STRING 'the sign of the number
    DIM outv AS STRING 'output value
    DIM outu AS STRING 'output units
    DIM e AS INTEGER 'scientific notation exponent of 10
    DIM i AS INTEGER 'general purpose counter
    DIM t AS INTEGER 'temporary general purpose

    nstr = STR$(n) 'get the string version of the number
    nstr = RTRIM$(nstr) 'strip off the trailing blank
    sign = LEFT$(nstr, 1) 'save the number's sign...
    nstr = MID$(nstr, 2) '...then strip the sign off the number

    'what's left might be either a traditional decimal number or
    'a number in scientific notation...

    t = INSTR(nstr, "D") 'is it in scientific notation??
    IF t THEN 'yep! it's in scientific notation...
    e = VAL(MID$(nstr, t + 1)) 'get the exponent...
    nstr = LEFT$(nstr, t - 1) '...then remove it from the string
    nstr = LEFT$(nstr, 1) + MID$(nstr, 3) 'remove the decimal point
    ELSE 'nope! -- it's simply a decimal...
    e = INSTR(nstr, ".") 'get the position of the dec point
    IF e = 0 THEN 'if no dp then it is implied....
    nstr = nstr + "." '...so we will just add our own dp
    e = INSTR(nstr, ".") 'get the position of the dec point
    END IF
    nstr = LEFT$(nstr, e - 1) + MID$(nstr, e + 1) 'strip out the dp
    e = e - 2 'adjust the dp to reflect the value of an exponent
    END IF

    ' Whether QB handed us a simple decimal number or a number in
    ' scientific notation, we now have a string representation of the
    ' significant digits of the number; that if normalized by placing
    ' a radix immediately following the most significant digit, becomes
    ' the mantissa of the number, with the signed exponent in e, and
    ' the mantissa's sign character in sign.

    nstr = nstr + STRING$(d, "0") 'add some trailing zeros to make nice

    ' strip off the leading zeros to leave only significant digits...
    IF VAL(nstr) THEN '...but not if they're ALL zeros
    i = LEFT$(nstr, 1) = "0" 'check if the first one is a zero...
    DO WHILE i
    nstr = MID$(nstr, 2) '...strip it off...
    e = e - 1 '...and adjust the exponent
    i = LEFT$(nstr, 1) = "0" 'check next digit...
    LOOP
    END IF

    nstr = LEFT$(nstr, d + 1) 'strip off all but the desired SD's + 1

    'round off the number...
    IF RIGHT$(nstr, 1) >= "5" THEN 'round up...
    FOR i = d TO 1 STEP -1
    MID$(nstr, i, 1) = CHR$(ASC(MID$(nstr, i, 1)) + 1) ' "elevate" the chr
    IF MID$(nstr, i, 1) = ":" THEN 'if a 9 was "elevated" to a ":"...
    MID$(nstr, i, 1) = "0" '...make it a 0, then go carry a 1...
    ELSE
    EXIT FOR '...otherwise, we're done.
    END IF
    NEXT i
    'fix-up an elevated MSD ...
    IF LEFT$(nstr, 1) = "0" THEN 'The MSD changed from a 9 to a 0...
    nstr = "1" + nstr '...carry the 1...
    nstr = LEFT$(nstr, LEN(nstr) - 1) 'loose the LSD to keep things even
    e = e + 1 'adding a new MSD moves the radix
    END IF
    END IF

    'we're done with the extra digit -- so now we can toss it ...
    nstr = LEFT$(nstr, LEN(nstr) - 1)

    'for testing ... prints the number in scientific notation...
    'PRINT LEFT$(nstr, 1) + "." + MID$(nstr, 2) + " x 10 ^" + STR$(e)

    'add prefix and normalize value...
    FOR i = 12 TO -12 STEP -3
    IF e >= i THEN
    e = e - i
    outu = MID$(prefix, i 3 + 5, 1)
    EXIT FOR
    END IF
    NEXT i
    IF e <= -13 THEN 'the value is to small to be useful in practice
    pformat = " < 1 p" + u
    EXIT FUNCTION
    END IF

    outu = " " + outu + u

    'format the mantissa and pad with blanks and zeros where needed...
    SELECT CASE e
    CASE IS = d - 1 'radix is at immediate right of SD's - just show it
    outv = " " + sign + nstr
    CASE IS < d - 1 'radix is in the middle of the SD's
    outv = sign + LEFT$(nstr, e + 1) + "." + MID$(nstr, e + 2)
    CASE IS < 0 'radix is to the left of the SD's - pad with left zeros
    outv = sign + "0." + STRING$(ABS(e) - 1, "0") + nstr
    CASE IS >= d 'radix is to right of SD's - pad with right zeros
    outv = " " + sign + nstr + STRING$(e - d + 1, "0")
    END SELECT

    pformat = outv + outu

    END FUNCTION

    [/code]
    AND THEN I was thumbing through the VBDOS manuals and came across the FORMAT$ function (not available in QB4.5 and slightly different in your 7.1) that does most of the work for me! I was pissed that I spent so much time on the thing when it could have been so much easier! Oh well. I will save the first version for use with QB4.5. The new version:
    [code]
    DECLARE FUNCTION pformat (n AS DOUBLE, d AS INTEGER, u AS STRING) AS STRING

    DO
    INPUT a#
    PRINT pformat(a#, 3, "lightyears")
    LOOP

    FUNCTION pformat (n AS DOUBLE, d AS INTEGER, u AS STRING) AS STRING
    'this function requires VBDOS (or possibly QB7.1 with some
    'modification -- change FORMAT$ to FORMATD$ and ???

    'the unit prefixes: pico, nano, micro, milli, (none), Kilo, Mega, Giga, Tera
    CONST prefix = "pnum KMGT"

    DIM fmt AS STRING 'the format specifier string
    DIM nstr AS STRING 'string representaion of the number
    DIM sign AS STRING 'the sign of the number
    DIM outv AS STRING 'output value
    DIM outu AS STRING 'output units
    DIM e AS INTEGER 'scientific notation exponent of 10
    DIM i AS INTEGER 'general purpose counter
    DIM t AS INTEGER 'temporary general purpose

    fmt = STRING$(d, "#") + "E-##" 'create a VBDOS/QB7.1 format specifier
    nstr = FORMAT$(n, fmt) 'get the scientific version of n

    e = VAL(MID$(nstr, d + 2)) 'separate the exponent
    nstr = LEFT$(nstr, d) 'separate the (already rounded!) mantissa
    e = e + d - 1 'normalize the mantissa -- dp after MSD

    'for testing ... prints the number in scientific notation...
    'PRINT LEFT$(nstr, 1) + "." + MID$(nstr, 2) + " x 10 ^" + STR$(e)

    'add prefix and normalize value...
    FOR i = 12 TO -12 STEP -3
    IF e >= i THEN
    e = e - i
    outu = MID$(prefix, i 3 + 5, 1)
    EXIT FOR
    END IF
    NEXT i
    IF e <= -13 THEN 'the value is to small to be useful in practice
    pformat = " < 1 p" + u
    EXIT FUNCTION
    END IF

    outu = " " + outu + u

    'format the mantissa and pad with blanks and zeros where needed...
    SELECT CASE e
    CASE IS = d - 1 'radix is at immediate right of SD's - just show it
    outv = " " + sign + nstr
    CASE IS < d - 1 'radix is in the middle of the SD's
    outv = sign + LEFT$(nstr, e + 1) + "." + MID$(nstr, e + 2)
    CASE IS < 0 'radix is to the left of the SD's - pad with left zeros
    outv = sign + "0." + STRING$(ABS(e) - 1, "0") + nstr
    CASE IS >= d 'radix is to right of SD's - pad with right zeros
    outv = " " + sign + nstr + STRING$(e - d + 1, "0")
    END SELECT
    pformat = outv + outu

    END FUNCTION
    [/code]
    Both seem to work ok but still need some more testing. I put the second version in a Lowpass Filter calculator here:
    ftp://ftp.sonic.net/pub/users/amckenna/QBASIC/lowpass/
    if your are interested. It is a VBDOS program but of course you can read the QB source that is in the LOWPASS.FRM file.

    Although I don't mean to imply that Back to the Future is in the same class as Casablanca or The African Queen, it is one of those movies that you can watch over and over again. I hate to put part 1 in the DVD because then I will want to spend the time to watch the whole trilogy!

    Thanks for the code / ideas.

    Enjoy!

    rg

Sign In or Register to comment.

Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

Categories