DIY Calculator

Posted: 10/26/2019 9:16:35 PM
dewster

From: Northern NJ, USA

Joined: 2/17/2012

I'm going to take a stab at making a console-based (i.e. command window) maths calculator.  If all goes well there's a possibility that could turn into a real dedicated physical calculator at some point, but for now I'd like to replace all the crap (and admittedly some not-so-crap) calculators I've been downloading and using on the desktop.  I don't like having to use a mouse when calculating, most calculators aren't RPN, and all calculators have major issues when changing bases (like clearing / truncating).

Goals:
- General purpose scientific and programmer's calculator with no frills.
- 100% RPN (reverse polish notation).
- Input hex or decimal: use "0x" prefix for hex input.
- Output hex or decimal: if possible just a display mode and not a complete internal switch / truncation.
- Auto delimiter insertion (commas or decimal points).
- Input delimiter white space (not necessarily the return key, like AutoCad).
- Fixed, shallow stack with replicating top value (like HP) with all stack values displayed.
- No programmability!
- No statistical functions.
- No logic functions (and, or, xor, not).
- Use the F1 thru F8 keys for functions, probably with shift/control key or arrow key based expansion.
- Several STO/RCL slots, possibly written to a text file for external use.

I'll be able to re-use much of my Hive sim code here, and both projects will likely benefit from expanded command line manipulation code.

More complex / programmable calculators are a waste IMO, when the going gets tough everyone turns to a spreadsheet or similar.

Posted: 10/27/2019 7:25:51 AM
ILYA

From: Theremin Motherland

Joined: 11/13/2005

dewster,
maybe you would be interested to have a look at design of my calculator, which I have been using for many years.

It is an emulator of Soviet calculator B3-34 (with RPN!) with extra functions, which I found very useful:
- 18 memory registers (one-click to call),
- permanently displayed register content (0...9, a, b, c, d, R, L, C, f),
- RLCF register-based subcalculator,
- parallel resistors/inductances (or serial capacitances) calculations (F + ||).


Cons is it has only decimal representation (the hex/binary is planned in future).


(can be downloaded here).

Posted: 10/27/2019 6:21:07 PM
dewster

From: Northern NJ, USA

Joined: 2/17/2012

Very interesting ILYA!  I really like the way the memory register buttons display their values.  On an RPN machine x^2 seems a bit redundant?  log2 is a good inclusion for programming stuff.  And with the y^x and 1/x I suppose a "y to the root x" isn't all that necessary. 

With my earlier HP calcs instead of doing say 3 enter 4 / for 3/4, I'd do 3 enter 4 1/x *, and thought it might be better to remove the - and / as primary keys and replace them with +/- and 1/x.  Doing so would remove these non-commutative ops, which makes them a little easier to think about as you're doing them.

Function keys on a hand held calculator act like any other key (press function, let it up, then press the key), probably due to the keyboard scanning?  My function keys will have to act like PC keyboard shift / control / alt, where they are held down while the key is pressed.

How does the RLCF sub calculator work?

Posted: 10/27/2019 7:55:17 PM
ILYA

From: Theremin Motherland

Joined: 11/13/2005


"How does the RLCF sub calculator work?" - dewster


Buttons "R:", "L:", "C:" and "f:" are ordinary memory registers like others. You can put values as below:

Code:
"123.45"  "П"   "C:"   (direct input to "C" register)

or

Code:
"9:"   "П"   "L:" (copy register "9" to register "L")


Rule:
Two of last loaded registers in the group are marked.
Third unknown parameter is calculated via "F" (result is placed into target register and display).


For example if "C:" and "L:" are last loaded registers:
        "F"  "f:"  calculates the resonance frequence
   (or "F"  "R:"  calculates reactance of C or L at resonance freq).

If  "C:" and "f:" are last loaded registers:
        "F"  "L:" calculates inductance
   (or "F"  "R:" calculates reactance of C).


And so on.
Also you can change marking with click the labels "R:", "L:", "C:" or "f:".

Posted: 10/30/2019 12:31:05 PM
dewster

From: Northern NJ, USA

Joined: 2/17/2012

Numeric Representation

Struggling with the way C++ converts text to float and vice-versa, then ran across what appears to be a mother lode of info on this site: https://www.exploringbinary.com/

I'm also grappling with how to internally store / represent these things, as there can be useful in-band information in the binary float, like NaN, overflow, etc.  Doubles are probably good enough precision.

Given how many layers of cruft there are between the code and the console keyboard / screen, I was expecting more of a fight to access the float hardware.

Still not clear on how to make a "double clickable" executable in Linux.  I can run the compiled object from the command line by appending "./" but other apps are runnable from the file browser (something they're trying to outlaw, it seems).

[EDIT] My favorite stack overflow Q&A "how to" threads usually go like this:

Q: "How can I do this (entirely reasonable, usually low-level) thing in C++ that I absolutely must do?"
A1: "Why do you want to do that?"
A2: "Don't do that!"
A3: "That's not legal in C++."
A4: "Here's how, but don't do it (dangerous, non-portable, undefined but everyone does it, etc.)."
A5: "Here's a better way that you probably shouldn't do either."
A6: "Here's an even better way that I don't personally recommend."
A7: "Here's the best way, but only an idiot would actually do it."
etc.

https://stackoverflow.com/questions/4328342/float-bits-and-strict-aliasing/4328396#4328396

Posted: 11/3/2019 2:08:21 PM
dewster

From: Northern NJ, USA

Joined: 2/17/2012

"...I was expecting more of a fight to access the float hardware."  - moi

How young and naive I was back then! ;-)  Sure, you can trivially extract the parts that make up a double (sign, exponent, & mantissa) but they are in binary.  Which is fine if you want to display binary or hex (oh how I wish the "natural" base we had "picked" to do our arithmetic in were 4), but for decimal you have to convert a base 2 float to a base 10 float, which is fraught with precision issues.  So fraught, apparently, that many programming language math packages got them subtly wrong.  It seems that C++ now has the double  string conversions right, but the crux of the issue for this project is output formatting, which I don't believe is sufficient in C++ to supply an HP calculator type display.  In particular, I don't see a direct way to do engineering (mantissa % 10^3) type formatting, or specific formatting of hex floats in terms of precision or "decimal" places .

A double has 64 bits: 1 sign bit, 11 exponent bits, and 52 mantissa bits.  For values that aren't subnormal, the mantissa is prepended with a 53rd MSb of '1'.  Manipulating the 52 bit mantissa via doubles is problematic because the precision that the double can store either matches the mantissa (for subnorms) or is one bit less (norms).  There are some "guard bits" in the float hardware, and these increase the precision of the result via rounding when shoehorning it back into the double storage space of 64 bits, but there is no way to insert anything into these guard bits as input. 

Floats do a lot of nice stuff automatically, but float containers are rather vague and limited in C++.  There is a "long double" but there is no guarantee that it has more precision than a double, which is kinda nuts.  (I believe the double itself can fall back to just a float in some cases, which is really nuts, though apparently knowable.)  The long double "usually" uses the 80 bit float hardware in an x86 processor, but all bets are off with portability.  This is one major flaw in C++ IMO.  I understand that it's based on C legacy, which did "best effort" with ints, longs, etc. regarding the hardware, but they should have totally nailed down the widths via typing (which they did after the fact, at least for the signed and unsigned integers: int32_t, uint64_t, etc.) so that precision and modulo behavior would track among the various underlying hardware, and then provided library implementations of things not directly supported by the target hardware.  I mean, how hard / not obvious is this to know that it's the right thing to do?  Math has to be 100% portable for algorithms not to break. Though, I guess when your main memory is 64k, it's hard to see the big picture or the needs of the future.

I could have used C++ functions that convert doubles to strings, extract the various fields into sub strings, then convert these back into separate numbers, then play with them, then convert them back into display strings, but that seems really Rube Goldberg-ian.  So I bit the bullet and wrote my own double fields extractor, and it seems to be working OK.  I used one long double, but if it falls back to a double the precision shouldn't be affected too much.  It's not 100% accurate, but the results seem to be good to several least significant bits.  Given the 53 bit mantissa of a double, there are log10(2^53) = 15.9546, or ~16 significant decimal digits in a double.  The signed exponent is returned as a int32_t, the signed mantissa as a long double.  A separate function looks for NaNs and infinities.

[EDIT] I can see now why many calculator SW writers often end up implementing their own BCD math libraries.

Here's the gist of the conversion:

Code:
	d_fields(d_i, sf, ef, ff);  // get d fields
	int32_t exp_2 = ef - EF_BIAS;  // remove exp_2 bias
	double mant_2 = d_i;  // the whole input double, actually
	int32_t exp_10 = ceil(exp_2 * log10(2));  // initial exp_10
	mant_2 *= pow(10.0, -exp_10);  // base 2 => 10 conversion
	d_fields(mant_2, sf, ef, ff);  // get mant fields
	mant_2 = (!ef) ? ff : ff + FF_MSB;  // (ef == 0) : subnorm
	exp_2 = ef - EF_BIAS;
	long double mant_10 = (long double)mant_2 * powl(2, exp_2 - 52);
	// adjust if not base 10 exponential form (mostly for subnorms)
	while ( mant_10 = 10 ) { mant_10 /= 10; exp_10++; }
	mant_o = powl(-1, sf) * mant_10;
	exp_o = exp_10;

Posted: 11/4/2019 3:28:08 PM
dewster

From: Northern NJ, USA

Joined: 2/17/2012

Numeric representation I/O in C++ is fighting me so hard I'm about to give up on this project. 

There's a thing called "hexfloat" but C++ doesn't give you a way to control the output precision / decimal places.  Output formatting should correctly round to a certain number of display digits, whether those are hex or decimal, but it seems only the decimal display can be controlled via cout.precision() - hexfloat lets it all hang out regardless.  And cin doesn't automatically do hex input (let alone hexfloat) to a double, even with various flags unset.  It's rather perverse that hex I/O is so difficult when it's basically just binary.

There's are several arbitrary precision math libraries out there, and given that I'm not sure why I'm putting up with the limitations of HW floats, since speed isn't an issue but precision is.  But I first have to get robust input and controllable output formatting.

[EDIT] One main goal for my combo scientific and programmer's calculator is to give correct hex values, and if the decimal display of this is a little off due to base conversion I don't really care.  I'm thinking I only really care about 32 bits of hex info, as DSP calcs are generally sufficiently precise at this width.

Posted: 11/8/2019 12:33:04 PM
dewster

From: Northern NJ, USA

Joined: 2/17/2012

Several days (on and off) exploring what's available in C++ to do calculator stuff.  Feeling my way rather blindly, have to see a clear path through to the end.

- Tried the boost library which supports a 128 bit float.  Got it to compile, but some builds were failing.  Gave up for now.
- In g++ there's a __float128 data type that isn't fully supported by fundamental things like cin and cout, but the basic math functions seem to be there.  Printing to a char array works OK, though auto insertion of commas doesn't.  It seems you can control the precision of the hexfloat output, as well as the decimal varieties, so there's probably enough there to support this calculator. The input function strtoflt128() takes all kinds of user alphanumeric input and magically turns it into a 128 bit float, so that seems robust enough to incorporate into this calculator.
- I have new insight into / respect for the Intel 80 bit hardware float.  It's always portrayed as a little nuts / overkill / legacy, though I never really looked into it. Turns out you can fit a 64 bit int in it without loss of precision, which is really interesting.  Funny how 2^n storage width practicalities trumped mathematical use.

Still trying to come to grips with my needs and goals regarding hex I/O.  I can see why it's such a "bag on the side" for many calculators.

[EDIT] For one thing, I'd like to implement 32 bit modulo trig functions for hex I/O, rather than the radians or degrees types.  Log2 and Exp2 should be there too.  The hex float seems like a bad fit for display, since it forces a one to the left of the decimal and thus throws all the hex characters off.  Unlike the decimal conversion, rolling my own conversion hex display here shouldn't be too difficult.

Posted: 11/11/2019 3:29:55 PM
dewster

From: Northern NJ, USA

Joined: 2/17/2012

Still working on the calculator.  As usual, the ideas phase is taking its own sweet time.  Some fruit:

- Found the C++ function "frexpq()" which separates a quad float into signed fraction and exponent, and the function "ldexpq()" which reassembles them back into a quad float.  These are more portable and useful than ad-hoc float to int casting via pointers or union.  The C++ reference page is absolutely invaluable: http://www.cplusplus.com/reference/

- Using the above functions it is possible to round display values to a desired bit precision (e.g. to what is displayed) by shifting the fraction to the left the desired number of bits of precision, doing a "roundq()", then shifting the fraction back to the right (the second shift can be incorporated into the quad reassembly).

- There are 112 bits of mantissa in a quad float, so the hex display could have as many as 112 / 4 = 28 hex digits, which could be displayed as 28 / 4 = 7 groups of 4.  Minimum here would be two groups of four both before and after the decimal point: 0x0000,0000.0000,0000 which is 32.32 bits.  I believe it would be good to have more resolution displayed, and am contemplating the various options.  The hex display will have an exponent, and it should probably be in "chunks" of groups of 4 or 8 hex digits (2^16 or 2^32) so hex values align with the Hive data width of 32 bits (and 32 bits seems totally sufficient for audio DSP work).

- I don't believe it will be possible to have the function keys operate immediately, as the minus key is used to enter negative values of mantissa and exponent.  The negative mantissa is the fly in the ointment here, as one would enter the '-' first, and not embedded in a string of numbers following the exponent character.  So the number pad can't function as a simple calculator unless I implement some kind of "change sign" function (indeed, this is why the "CHS" or "+/-" key is on HP calcs, as the '-' key is an action key, not a numeric entry key) but that could be complicated.

- I'm thinking of perhaps allowing multiple entries on the command line separated by spaces, with the enter key sticking things separated by white space on the stack and executing any functions.

You must be logged in to post a reply. Please log in or register for a new account.