- marked as enhancement
Refactor handling of number format
Currently the desired format of a number type is saved as a single char
inside each type. Especially now that we have three types that each save the same (!) char, this is unsatisfactory. This we could be solved by simply removing the char format
attribute from HNumber
and CNumber
.
However, as of issue #586, it becomes clear that a single char will not be up to the task. For instance, a complex number could be formatted as binary, fixed point, and polar all at the same time. The current system limits us to choose one option only.
I propose to add the necessary infrastructure to deal with this more properly.
Now, I do not have a very clear vision of how to do it, so that's why I'm asking. Currently I envision something similar to this:
- First, replace the
char
by anenum
. -
Each number type offers its own enum of possible formats that can be passed to its
format
function. For instance,HMath
might allowGeneralDecimal
FixedDecimal
ScientificDecimal
EngineeringDecimal
GeneralBinary
GeneralHexadecimal
GeneralOctal
-
CMath::format
on the other hand requires additionally eitherCartesian
andPolar
.
I know there were some concerns about storing the format inside the actual number types. Why exactly is that a problem? Would it help if we stored everything in Quantity
only?
Marking it as milestone as I see it a requirement for #586.
Comments (14)
-
reporter -
Well, storing the representation of the number with its value is a bit icky. I'd still prefer it if the format existed on a level above
Quantity
(the elusive Value story), but for the moment, I feel the format should only be stored inQuantity
itself, yes. I played with that a bit on the number refactor PR, I think? It was pretty easy, the biggest issue wasencodeIeee754
since it sets its result to hexadecimal; ifHNumber
loses its format, that action has to be moved either intoDMath
or functions.cpp directly.From the example in your second paragraph, are you suggesting to split the number base from the decimal format so e.g. you could have a number formatted as engineering binary? I can't really tell if that's something that makes sense, but if we can do that, we absolutely should.
First, I agree that the formats should be specified as enum values in some fashion. Now. Due to how the number stack is structured,
CMath::format
needs to know both the complex number format as well as the "real" parts of the format so it can pass that toHMath::format
. Accordingly, theformat
argument for these functions needs to be some value that encapsulates two or three distinct pieces of information.CMath::format
pulls out the piece that specifies either cartesian or polar and acts on it, then passes the format value to itsHMath::format
calls.I've sketched some more concrete ideas; option a), distinct enums and a simple struct:
enum RealFormat { GeneralDecimal, ... } enum ComplexFormat { Cartesian, Polar } struct Format { RealFormat realFormat; ComplexFormat complexFormat; };
Option b), mush the different formats into a single value with some bit mangling, reserving some bits for "real" formats and some further bits for "complex" formats:
enum Format { GeneralDecimal, // = 0 ... Cartesian = 0x100, Polar, // = 0x101 // SomeFancyOtherFormat = 0x10000, } GeneralDecimalAndPolar = Format::GeneralDecimal | Format::Polar; if (fmt & Format::Polar) // check if polar form switch (fmt & 0xFF00) { // keep only the "complex" bits so we can switch on then: case Format::Polar: ...
-
reporter Thanks for taking the time of writing this.
From the example in your second paragraph, are you suggesting to split the number base from the decimal format so e.g. you could have a number formatted as engineering binary? I can't really tell if that's something that makes sense, but if we can do that, we absolutely should.
Exactly. I don't know if this will be possible (or even useful), but now we can't even tell!
I've also come to the conclusion of storing everything in
Quantity
is the way to go. Option a) seems much more appalling to me. Come to think, I imagine a class for each type's formatting might be useful.struct HNumber::Format { enum base {Binary, Decimal, Octal, Hexadecimal}; // possibly sexagesimal enum radixChar {Point, Comma}; unsigned int precision; enum notation {General, Fixed, Scientific, Engineering}; } struct CNumber::Format : HNumber::Format { enum notation {Cartesian, Polar}; } struct Quantity::format : CNumber::Format { // Maybe unit & unitName should go here? They also only serve display purposes. CNumber unit; QString unitName; }
Note that I think real and imaginary parts should really have the same format. In polar representation there cannot even be such a distinction anymore!
Also note that not necessarily all possible combinations must be honored. If it turns out that binary+engineering (which seems hardly useful) is difficult to implement, the number is just formatted in whichever way makes most convenient (for the user, not the programmer :) instead.
I'd consider adding an
Unspecified
key to each enum, which'd serve as default, and tell thenumberformatter
to pick the value from the settings.While the classic bitwise magic immediately allows for this very nifty
format = Binary + Polar
style of programming, I am afraid it won't cut the complexity we see here. Just look at the code snippet I just wrote. How should that possibly fit inside an integer? ;) -
Hm. Yeah OK, having a class or several for the format seems worthwhile.
Pretty sure neither unit nor name can be in the format. Changing the format on a
Quantity
shouldn't change the effective value of the number, but that's what changing the unit does; ergo the unit needs to be in the number, not the format. And as for the unit name, the number still needs to print properly "without" a format, i.e. with an empty, "default" format value. So the number needs to know its unit and unit name without the format. The format could have a field to overwrite the unit name for display, but I'm not sure where that'd be useful.So thinking out loud:
- Conceptually, a "format" has a bunch of properties, each of which can be either a particular value or unspecified/empty/null.
- Every
Quantity
has a format, which can be as unspecified or as particular as it wants. - There is an application-wide "default format" that is constructed from user settings in some fashion. In practice, it probably won't have unspecified fields, but I don't think that's strictly necessary.
- At some point, in numberformatter I guess, we need to merge the default format with the number's format, filling every unspecified field in the number's format with the default format's value.
- The number stack all the way down to
HMath::format
should still be able to cope with "unspecified" values and use some sensible default.
-
reporter Changing the format on a
Quantity
shouldn't change the effective value of the number, but that's what changing the unit doesNot really. The dimension is part of the 'value', but the unit really is only a display thing. I agree though that we better keep it out of the format.
-
repo owner - changed component to results
-
reporter I have started to work on this. Turns out decoupling base from mode (e.g scientific vs fixed) is indeed doable. Win!
However, I fail to see what the Fixed Decimal mode does, except from raising the limit upon which the representation is silently converted to scientific notation. Shouldn't it rather be used for enforcing a certain number of digits, possibly trailing zeros?
For instance:
0.12 = 0.1200
I guess this would be useful for finance guys. EDIT: Nvm, I have just figured out that that's what happens if you select a precision other than Auto.
-
reporter Hm, actually my point still stands. Can somebody tell me the difference between Fixed and General? Really the only difference I can distinguish is that Fixed is much more conservative with when it switches to scientific notation.
In other news, I have implemented the refactor as discussed. The only thing that is not working yet is that
numberformatter
fetches default formatting from the settings. Point is, the engine does indeed support providing the base and the mode (scientific, ...) independently. So I see two paths we can go from here:- Keep the menu/shortcuts/etc the same they are. If anyone wants to output a binary in scientific mode, we'll have to make a new
scientific
function. - Change the menu, so that the user can independently select base and mode. This will require new keyboard shortcuts.
Since I really doubt the new combinations will be useful, I think I'll go with the first method.
EDIT: Done. Will remake the tests, and then submit a PR.
- Keep the menu/shortcuts/etc the same they are. If anyone wants to output a binary in scientific mode, we'll have to make a new
-
I believe "Fixed" means something like "represent the numbers without using exponent as much as possible". The behavior depends on the number of digits that can be shown (precision), and it is somehow equivalent to fixed point representation. "General" is like an automatic representation mode which mixes "Fixed" and "Scientific" representations depending on the exponent value.
Indeed, the behavior of "Fixed" and "General" look the same if you consider it's only a matter of exponent values, but the fact the "Fixed" representation fallbacks to "Scientific" when it can not format the result without exponents is more a limitation than a feature of that mode.
TBH, I don't really see the point of the "General" mode, but use the "Fixed" mode a lot when not doing scientific calculations.
Since I really doubt the new combinations will be useful, I think I'll go with the first method.
It can be useful when studying floating point numbers representation in hexa/binary, but that's only something you do in school anyway :)
-
repo owner This is all legacy stuff and TBH I've always wanted to reformulate those settings (that have existed almost from day 1 and predate my presence in the project). Feel free to propose changes. I believe some of you are somehow related to electronics and may be aware of what makes more sense here.
-
reporter I put some more thought into it, and I really think that Scientific binary is of no use whatsoever. If we would separate base and mode in the menu, than a user who wants to switch from general decimal to fixed binary, would need to flip two switches, rather than one at the moment.
We could of course keep the keyboard shortcuts the way they are (for instance F2: general decimal, F6: fixed binary, etc.), essentially executing macros. But I see no reason for why the shortcuts should give different functionalities than the menu.
Finally, we could implement
scientific()
,fixed()
andengineering()
functions, to complementhex()
and friends. That's actually a very flexible yet convenient approach.TL;DR: Let's keep it the way it is. It is actually well thought out.
As a last comment: Binary scientific notation is a concept not that widely known to even exist. I am very much afraid that it will confuse many more people than it will be used by.
-
repo owner Yes, I'd like to have e.g.
eng()
. It could also mimic the behavior of theENG
(andENG⁻¹
orShift+ENG
) key in classic calculators. -
Yeah, the current settings seem reasonable to me. Correct me if I'm wrong, but bases != 10 are pretty much exclusively used to cope with the love computers have for base-2; and when you're looking at numbers at that level, you're mostly dealing in integers anyway. Having it as a "hidden" feature by combining e.g.
bin
andscientific
seems fair, but I think I'm ok with the current menu contents. -
repo owner - changed status to closed
See pull request #65.
- Log in to comment