On 6/26/2025 8:40 PM, Richard Heathfield wrote:
On 27/06/2025 01:39, Lawrence D'Oliveiro wrote:
[...]if C is going to become more suitable for such high-
precision calculations, it might need to become more Python-like.
C is not in search of a reason to exist. If you want Python, you know where to find it.
FWIW, in my compiler I had added support for dynamic/variant types as an extension. Don't end up using it all that often though.
But, yeah, could in premise support bigint or bigfloat representations through this mechanism. Though, big downside would mean they would need to be heap-backed.
There were two subtypes of the variant type:
64-bit: More common, uses high bits as type tag.
Supports 62 bit fixnum and flonum types.
128-bit: Rare, but can deal directly with larger values.
Supports 124 bit fixnum and flonum types.
Though, had also added _BitInt(n) with support for large integer types, though divided into categories based on n:
n= 1.. 64:
Represented in a normal inter register.
Memory storage size is one of the power-of-2 sizes.
Uses native operations;
May sign or zero extend values as needed.
n= 65..128:
Represented in a register pair;
May use 64 or 128 bit operations as available.
n=129..256:
Represented as a pointer to a memory object (32 bytes);
Uses runtime calls for 256-bit operations;
...
n larger than 256:
Represented as a pointer to a memory object;
Size is padded up to the next multiple of 128 bits.
Uses runtime calls that deal with variable-length values.
...
For large integers, _BitInt(n) would be preferable as it wouldn't need to heap-back values (unless they got so large as that the compiler switches to heap backed objects). Though, IIRC, the biggest supported BitInt size at present is 16384 bits (2048 bytes), which is below the cutoff where the compiler heap-allocates things.
No support for big floating-point types ("_BitFloat"?), would need to come up with some way to deal with them, if they were to be added.
The exact pattern is unclear, though one likely option is to assume that all FPU types larger than 256 bit and above have a 31 bit exponent (assertion: Probably no one is going to need an exponent bigger than this).
So, say:
Binary16 : S.E5.M10
Binary32 : S.E8.M23
Binary64 : S.E11.M52
Binary128: S.E15.M112
Binary256: S.E19.M236
Binary512: S.E31.M480 (?)
...
Some intermediate sizes, if supported, could be assumed to be a truncated form of the next size up. So, for example, if one requests a 48-bit floating point type, it is assumed to be a Binary64 with low-order bits cut off (with the storage size padded up in the same way as for _BitInt).
One downside is, as it exists, there is no way to address types being non-power-of-2 in memory. As noted, _BitInt as implemented pads up the storage.
So, say:
v=*(_BitInt(48) *)ptr1;
*(_BitInt(48) *)ptr2=v;
Would be ambiguous, and as-is would just use 64-bit loads and stores.
It might be desirable in some cases to express a type that is N bits in-memory (possibly along with having an explicit endianess).
Looking it up online, apparently Clang uses ((n+7)/8) bytes of storage for _BitInt, rather than padding up the storage. I guess I could consider this, though likely only to be done this way in the case of an explicit pointer de-reference. Though, this would make a messy corner case for dealing with arrays (do arrays and pointer ops use the padded or non-padded size).
As-is, my compiler would assume the padded-up sizes.
If it were just up to me, might add some more types:
_SBitIntLE(n), signed bitint, exact bytes, little endian
_UBitIntLE(n), unsigned bitint, exact bytes, little endian
_SBitIntBE(n), signed bitint, exact bytes, big endian
_UBitIntBE(n), unsigned bitint, exact bytes, big endian
Where in this case the LE/BE qualifier also implies that it is an in-memory format.
Though, my compiler had also added "__ltlendian" and "__bigendian" modifiers, so, it is possible that no new type is added per se, but rather that if _BitInt is used with one of:
__ltlendian, __bigendian, __packed
It is assumed to use a byte-sized.
Well, or just use __packed as the qualifier.
So, say:
v=*(_BitInt(48) *)ptr1; //64b load
*(_BitInt(48) *)ptr2=v; //64b store
But:
v=*(__packed _BitInt(48) *)ptr1; //48b (6 byte) load
*(__packed _BitInt(48) *)ptr2=v; //48b (6 byte) store
Wouldn't want to default to using byte-exact storage as (at least on my ISA and also RISC-V) doing byte-exact loads/stores would have a significant performance and code-size penalty. Though, realistically, only store may need to be special cased.
Also, I am left realizing I have a non-zero temptation for an:
S.E7.F8 16-bit format.
Ironically, partly because:
It better fits typical use cases than S.E8.F7;
The use of an 8-bit mantissa makes it easier to use head math.
Though... Am I the only person to ever head-math this way?
Namely, representing some values in-mind as 4 hex digits.
Though, my mental arithmetic skills still kinda suck, but still.
Can't find much mention of anyone else using FP or non-decimal systems for mental arithmetic, but in this case, having the mantissa as an exact number of hex digits makes it easier (my mental processes for dealing with numbers don't like bitfields not being aligned to multiples of 4 bits). Though, if doing this stuff, often extreme corner cutting was needed, as mental math skills suck (easier to turn x*y => x+y-0x3F00 or similar, and just live with the inaccuracy, ...).
And, for things like divide, like, no, I am not going to try running Newton-Raphson in head-math (also I suck at things like long-division).
I can also think readily in decimal as well, though digits are at a premium (there being a fairly high per-digit cost). I think my mind deals with it in a BCD like form (with a mental division partly because of the comparably high cost of converting between decimal and hexadecimal numbers).
For "guesstimating" a number (in integer form), can do something analogous to figuring out the log-2 of the divisor and then right-shifting the left input by this amount. Like, usually if one can guess within a power-of-2 of the actual value, often good enough.
So, not many accurate answers, but yeah.
Also I recently noticed that if try to mental hexadecimal multiplication, my mind has a notably harder time with multiples of 3, 6, and 7 (to such a point that I had to fall back onto using iterative addition). Then again, I had most often used multiples of a power-of-2 row (1/2/4/8), so it stands to reason that other rows would be harder.
Can't seem to find much information about what is normal or standard here, beyond just references to the sorts of stuff people typically teach in school.
I was just left thinking about this recently.
But, then seemingly my own thinking processes seem weird enough that it almost seems like I am just making stuff up.
Though, there is often some amount of "weird analog stuff" in all this as well.
Can note though, that I kinda suck at traditional "symbolic manipulation" tasks, things like pretty much everything is routed through visual/spatial representations, but even this is non-standard (not so much colorful graphics or real-world objects, whole lots more abstracted glyphs and monochrome though; and lots of text).
It is often more like I am dealing with thoughts like it is all text in a text-editor (within in a space of floating windows of text and similar). Actually, not too much different than a typical PC desktop, just more 3D and with pretty much everything in "dark mode" (and with less use of color). Also pretty much all of the text looks like 8x8 fonts, and imagined objects seem to often have what looks like pixel artifacts (like everything is actually at a fairly low resolution as well).
And, there seems to be one part of my mind that doesn't like dealing with more than 8 digits of input (2x 4-digit in, 4 digit out). And, say, keeping a 12 or 16 digit number in this part of working-memory doesn't work so well.
Like, my thinking processes are kinda janky and suck (and have seemingly gotten worse over the years as well).
...