Non-IEEE (software) float option - Page 1
Forum section: electronics / general

author: peter-h
author_rank: Super Contributor
timestamp: June 12, 2024, 02:47:13 pm
content: In the old Z80 etc days, you had e.g.- Hisoft Pascal, non-IEEE floats, FP divide taking say 500us, and Borland Turbo Pascal may have been the same- IAR Pascal or C, IEEE floats, FP divide taking say 10msNow, aside from IAR compilers back then generating bloated code (the runtimes were often written in C, which is a rubbish way to do stuff like software floats) the IEEE float format does produce much longer runtimes. A part of this may be the specific requirements on e.g. rounding.On the more basic CPUs today there are no hardware floats (I think those are always IEEE compliant) so getting software floats to run several times faster would be useful to a lot of people.The non-IEEE code is also a lot more compact. In another thread someone mentioned 20k for software floats (arm32?). The Z80 non-IEEE version was under 4k, and Z80 code is not that compact.Another curious thing is that in Hisoft Pascal, the simple code meant that the 24-bit mantissa was an "exact integer" if you did whole-number add/subtract i.e. if you wanted a 24 bit counter (their ints were only int16) you could use a float and just keep adding 1 and it would do it right, all the way to (2^24)-1, whereas I am not sure IEEE floats do exactly that.I do see a problem with generic tools like GCC not wanting to do this, instead writing their software floats in C, but that will immediately produce a big performance hit.

author: brucehoult
author_rank: Super Contributor
timestamp: June 12, 2024, 03:01:10 pm
content: Quote from: peter-h on June 12, 2024, 02:47:13 pmThere is for example the FP library used by Arduino on AVR. It's around 5us for add/sub/mul on a 16 MHz chip.QuoteOf course they do. Any IEEE implementation (hardware or software) is absolutely guaranteed to give exact results for add/sub/mul of integers out to the limits of 23 bits on single precision or 53 bits in double precision. And in fact to a result of 2^23 or 2^53. After that the odd numbers can't be represented but you get all the even numbers out to 2^24 or 2^54.This is a consequence of IEEE implementations being required to give bit exact results for EVERY operation for which the result is representable -- and the nearest value for the rest. I mean for the fundamental operations -- this doesn't apply to trig and logs.

author: ejeffrey
author_rank: Super Contributor
timestamp: June 13, 2024, 01:23:02 am
content: Quote from: brucehoult on June 12, 2024, 03:01:10 pmHence the use of a single base numeric data type in languages like JavaScript and Matlab. Of course the "overflow" behavior is different...

author: brucehoult
author_rank: Super Contributor
timestamp: June 13, 2024, 03:34:38 am
content: Quote from: brucehoult on June 12, 2024, 03:01:10 pmOops I miscalculated.With the exponent at the value where the different between successive numbers is 1.0 (no fractions any more), you get 2^23 or 2^53 integers between 2^23 and 2^24-1, or between 2^53 and 2^54-1. It is the next higher exponent value, after 2^24 and 2^54 where odd numbers can't be represented.So, yeah, fp32 gives you exact arithmetic on integers to 16,777,216 (2^24) and fp64 to 18,014,398,509,481,984 (2^54). And the same for negative.

author: peter-h
author_rank: Super Contributor
timestamp: June 13, 2024, 06:05:11 am
content: What I was getting at is whether there is a big overhead in coding IEEE compliant floats.All those 1970s coders implementing non-IEEE floats must have been doing it for a reason. I knew a number of them personally and they were super bright coders. People like Clive Smith-Stubbs (Hitech C) and Dave Nutkins (HiSoft).

author: brucehoult
author_rank: Super Contributor
timestamp: June 13, 2024, 07:42:34 am
content: Quote from: peter-h on June 13, 2024, 06:05:11 amOf course there is. Correctly handling NaNs and Infinities and denorms takes a lot of code and slows down every single operation. It's also very expensive to make sure the last bit is always correct.Many microcontroller applications of floating point don't need 23 bit accuracy -- if the final result is going to be used to control an output voltage or servo position then it's fine as long as 8-16 accurate bits are available after all calculations.Just as one example, for multiply for an IEEE-compliant result you need to do 9 8x8->16 multiplies and add them all up.If you don't need exact then you can just do:a0*b0 -> r0,r1a1*b0 -> r1,r2a0*b1 -> r1,r2a1*b1 -> r2,r3a0*b2 -> r2,r3a2*b0 -> r2,r3If almost 24 bit accuracy is good enough then you're doneIf you have MUL and MULH instructions rather than a single 8*8->16 instruction then you can just do the MULH on the last three, so you've done 6 MULH plus 3 MUL instead of 9 MULH plus 9 MUL for the full IEEE result. That's only half as many multiplications as the full IEEE calculation. (Similar argument applies if you don't have MUL at all but are doing shift-and-add)a2*b1 -> r3,r4a1*b2 -> r3,r4a2*b2 -> r4,r5Full accuracy needs these too.How inaccurate is it if you stop early? You're ignoring five r3 result bytes and three r4 results. The r4 could be 0xFF + 0xFF + 0xFF = 0x2FD. So it could need to propagate +2 (let's call it +3 if the value is over 0x280) into the r3 calculation which therefore could be 0xFF + 0xFF + 0xFF + 0x3 = 0x300.So your final result could be 3 units low -- you've only got 21 fully-accurate bits not 23.But it will run twice as fast.(this isn't a fully rigorous analysis, but close enough ...)

author: westfw
author_rank: Super Contributor
timestamp: June 13, 2024, 08:29:56 am
content: QuoteWell, there was no IEEE standard in the 70s... :-)I imagine being able to design floating point formats and functions around the particular features of the micro at hand must have been helpful...

author: brucehoult
author_rank: Super Contributor
timestamp: June 13, 2024, 11:07:20 am
content: Quote from: westfw on June 13, 2024, 08:29:56 amWeirdly the early Microsoft BASICs (including AppleSoft) used a 5 byte FP format, with an 8-bit exponent and a 31-bit significand. They stored this in a 7 byte symbol table entry, along with 2 byte variable name (with variable type fp / int / string / fn) encoded in the hi bits.

author: newbrain
author_rank: Super Contributor
timestamp: June 13, 2024, 12:16:48 pm
content: Quote from: brucehoult on June 13, 2024, 11:07:20 amEven more weirdly, the Grundy NewBrain computer used 6 bytes for FP, 5 bytes for the mantissa, 1 bit for sign, and 7 bits for exponent.The radix was not 2, but 256 - so both range (10¹⁵⁰ - 10⁻¹⁵⁰) and precision were better than average computer at the time.I noticed that while at uni: a dynamic, Runge Kutta based, simulation (written in Pascal) converged flawlessly on the NewBrain, but was extremely critical and often failed to converge at all on an Apple][e+ with exactly the same source code.

author: brucehoult
author_rank: Super Contributor
timestamp: June 13, 2024, 02:24:38 pm
content: Quote from: newbrain on June 13, 2024, 12:16:48 pmUCSD (therefore Apple) Pascal used 32 bit "real" in a format "similar to the proposed IEEE standard"..

author: coppice
author_rank: Super Contributor
timestamp: June 13, 2024, 02:42:40 pm
content: Quote from: peter-h on June 12, 2024, 02:47:13 pmThere were also a lot of tools that came with 2 optional floating point libraries - one fast, and one IEEE compliant. The speed difference could be quite big. In many packages the fast and slow libraries both used IEEE format, but the fast one dodged a lot of the rounding and other checks. In some packages the format in the fast and slow libraries was different, to avoid the overhead of shunting bits around. Even with bigger machines you have options like gcc's --ffast-math, which dodges some of the time consuming aspects of full IEEE compliance, even using an IEEE maths unit.Quote from: peter-h on June 12, 2024, 02:47:13 pmI don't think anyone expects a shipping GCC compiler will use that generic C code. Its a very useful tool when you are implementing a new ISA with GCC, as it gives you a working floating point package with essentially no effort, and lets you sort out the compiler aspects of floating arithmetic quickly. However, once floating point works, you really need to rework the support library to be optimised for the ISA.

author: peter-h
author_rank: Super Contributor
timestamp: June 13, 2024, 03:54:16 pm
content: QuoteIs that available on GCC v11 for 32F4? I mean does that option actually do something?It might make a big difference ondoublefloats, which don't use the float hardware.In fact this whole topic is pretty relevant on double floats which is rarely hardware-float-supported.And there are almost no real-world applications which need all the bits of a float to be perfect. One tends to use floats to get the easy dynamic range. Most "physical" stuff can be done with integers (i.e. fixed point) but you need to be very careful with the ranges.Also remember thatdoubleis the default for most of the float C functions, so a lot of code people write will unknowingly be running much slower... maybe 10x to 100x slower.

author: coppice
author_rank: Super Contributor
timestamp: June 13, 2024, 04:22:26 pm
content: Quote from: peter-h on June 13, 2024, 03:54:16 pmI have mostly used it on x86_64 machines, and it often speeds things up quite a bit there. I've never analysed the resulting code to see what its actually doing. Suck it and see, I guess.QuoteOn MCUs the use of "f" at the end of many things can lead to a substantial improvement in speed. People are generally pretty sloppy about distinguishing float and double in their C code. How often do you see the "f" added to the end of constants in float (not double) code?

author: westfw
author_rank: Super Contributor
timestamp: June 13, 2024, 05:03:14 pm
content: QuoteAFAIK, Arm Cortex M0 and M0+ are still using the generic C floating point code. :-(

author: coppice
author_rank: Super Contributor
timestamp: June 13, 2024, 05:50:29 pm
content: Quote from: westfw on June 13, 2024, 05:03:14 pmThat may reflect that nobody cares. This would be strange, though, as a lot of MCU applications do most of their work in integer or fixed point, and then do some final calculation in floating point to arrive at a result which is displayed or passed on. Since there are only a few floating point operations in that kind of setup, the speed may not matter too much, but the size of the code probably does. Don't discount floating point on small MCUs. A lot of them are doing just a little of it.

author: Siwastaja
author_rank: Super Contributor
timestamp: June 14, 2024, 06:54:17 am
content: Quote from: coppice on June 13, 2024, 05:50:29 pmAgreed. On higher-end MCUs (with HW floating point), onemightactually use floating point even in performance critical parts (often still not, for reasons such as, on ARM Cortex-M, extra penalty on ISR after lazy stacking of FP registers gets triggered), but for the small ones, everything where timing matters is done in fixed point anyway. Still, even on those, there are plenty of cases where performance is irrelevant; user interfaces as you say, but also calculating long-time averages, running infrequent control loops (e.g. once a second), etc. Think about a heating controller (second-timescale is OK due to thermal masses) vs. motor or switch mode controller control loop (in tens - hundreds of kHz).And as such, on a micro without HW FP, it doesn't really mater much if the operation takes 50 or 200us, since even that faster version is too slow to be used willy-nilly. When you use them in slow parts of your code, even slower is almost always OK, but on the other hand saving even just half a kilobyte makes a real difference on devices with less than 16KB or so flash, and as such I prefer size-optimized SW FP implementations.

author: peter-h
author_rank: Super Contributor
timestamp: June 14, 2024, 09:21:51 am
content: Quoteand non-IEEEshouldbe a lot smaller. So the option would be useful.I too use floats in relatively time critical parts but the 32F4 has hardware single floats.I ought to look up the exact circumstances where an inadvertent double operation might be triggeredQuoteIn sayCode:#define pi 3.141592645float x,y;y = x * pi;surely the multiply will be single float?If I didprintf("%7.5f",y);then y will be promoted to doublewithin the printfbecause that is how printf is customarily defined; correct? And the printf will run about 100x slower... but there is no way to change that unless you have the printf source code (which actually I have, because the newlib printf has various issues... various past threads, like using the heap, non thread safe, etc).

author: gf
author_rank: Super Contributor
timestamp: June 14, 2024, 10:18:41 am
content: Quote from: peter-h on June 14, 2024, 09:21:51 amNo, the mutiply is double.Only if both operands were single, the multiply would be single as well.

author: peter-h
author_rank: Super Contributor
timestamp: June 14, 2024, 11:27:16 am
content: OK how aboutCode:float x = 2.5;float y = 3.5;y = x * y;

author: GromBeestje
author_rank: Frequent Contributor
timestamp: June 14, 2024, 11:51:08 am
content: Quote from: peter-h on June 14, 2024, 11:27:16 amWhen assigning doubles to floats, they get demoted at assignment time. Thus the multiplication is at single precision.

author: peter-h
author_rank: Super Contributor
timestamp: June 14, 2024, 03:19:32 pm
content: Another learning experience!I have a lot of stuff likefloat g_gain_512 = 1.0;g_gain_512 = ((ain0*512.0/32768.0))/480.0;So I reckon- the compile time calc is done as double at compile time (hardly matters)- the ain0* multiply will also be done as doubleThere are certainly places in my code where stuff is done as a double, unintentionally, but none of them are even remotely time-critical.In the future I will usefloat g_gain_512 = 1.0F;Is there some easy way to search for all instances of such code? I am using Cube IDE. A regex for a float is something like [-+]?[0-9]*\.? [0-9]* but it turns up a load of nonsense. Maybe there is a plug-in which detects such code.I also worry what happens with e.g.float fred = 0.5;The 0.5 is by default double, but how is this stored in the initialised data section? If it is stored as 8 bytes then the startup copy FLASH -> RAM will produce garbage because the RAM space allocated to fred is 4 bytes. How is that handled? It obviously works correctly so the storage of a variable must be as per its type.

author: eutectique
author_rank: Frequent Contributor
timestamp: June 14, 2024, 04:34:56 pm
content: Quote from: peter-h on June 14, 2024, 03:19:32 pmBetter still would be instructing the compiler to use floats:float g_gain_512 = (ain0 * 512.0f / 32768.0f) / 480.0f;

author: gf
author_rank: Super Contributor
timestamp: June 14, 2024, 05:49:42 pm
content: Quote from: peter-h on June 14, 2024, 03:19:32 pmDon't worry. Here, the implicit double -> float conversion happens at compile time.

author: Siwastaja
author_rank: Super Contributor
timestamp: June 15, 2024, 11:36:02 am
content: It's a good idea toalwaysuse the f suffix witheveryliteral when you want single-precision floats. This is so that you don't need to remember the promotion rules by heart, or look up the standard every time. Same thing as using parenthesis to explicitly convey the desired order of operations, helpful for yourself and other human readers.The more you spam the f suffix, the more you remind yourself and others the importance of it. And sometimes it's very important for performance.Quote from: peter-h on June 14, 2024, 09:21:51 amAgreed. Only rarely you need to interact with other systems in binary floats directly, and in such cases you want the IEEE layout (you could still use a non-IEEE implementation which ignores some corner conditions or rounds slightly incorrectly, but uses the same number of bits for each component, same radix, same layout). But in most cases, you don't even need that compatibility; floats are internal to your code only, so it would be nice to tell the compiler "hey, how this is represented in memory is irrelevant, I just need something which has same order of magnitude accuracy as float/double".Then again... whenever this kind of thing matters, just use fixed point. Lot easier to understand and you can trivially do napkin (or Excel) calculations of ranges and resolutions. Much more difficult on the variable-resolution floating point, and even more difficult if the implementation introduces extra error.Therefore, I rarely use single-precision floats at all; double has enough range and resolution that in 100% of my use cases (some atom modeling scientific guy might disagree) I can treat it as an ideal real number storage, but float does not, it does that only in 99% of my use cases, which needs I need to turn my brain on. And if I do that, then why wouldn't I use fixed point which is easier to analyze and verify?And clearly the designers of C have had similar mindset, as you can see from the default promotions to double, and all library functions working on doubles (except specifically named f variants).

author: Nominal Animal
author_rank: Super Contributor
timestamp: June 15, 2024, 02:41:43 pm
content: Quote from: Siwastaja on June 15, 2024, 11:36:02 amFor all the ones I know –– including at least LAMMPS, Gromacs, VASP, Dalton, SIESTA, CHARRM ––doublesuffices.There isGNU Quad-Precision Math Libraryaka libquadmath, which provides 128-bit__float128 and __complex128 typesand a wide range ofmath functions. I use it sometimes to calculate coefficients for double precision approximations, and to compare my approximations to "best known values", but that's about it.When I suspect cancellation error may be an issue –– as in a sum with both large and tiny terms, with the large terms cancelling out ––, either sorting them in descending order of magnitude, orKahan summationwill deal with it.Quote from: Siwastaja on June 15, 2024, 11:36:02 amIt also reminds one to use the ..f() functions from<math.h>, for examplesinf(),fabsf(),expf(), and so on.Typical speed-enhanced non-IEEE 754 about-single-precision floating-point libraries use a 8-bit signed exponent, and a 24-bit signed mantissa, often in two's complement format, and all bits explicit. Multiplication yields a 48-bit temporary, where only the 25 most significant bits matter; 24 for the actual result, and one for rounding. Similarly, about-double-precision can use either a 8-bit or 16-bit signed exponent, and a 56-bit or 48-bit mantissa.It is also useful to know that you can represent any finite unsignedfloatvalue in magnitude (ignoring sign), including subnormals, using a 277-bit fixed point format Q128.149. Since its integer and fractional parts are converted to decimal separately, you only need a 160-bit unsigned integer bit scratch pad for the conversion; or about 20 bytes. We had a thread about the details some time ago.doublemay need up to 2098 bits, Q1024.1074, but that too is doable with a 1088-bit or 136-byte scratch pad. Fractional part happens to be easier to convert to decimal, via repeated multiplication by 10; that pushes the next fractional digit to the units place, and is why the scratch pad needs about 4 more bits than the full fractional range (i.e. Q4.1074). Because of rounding, the fractional part may round up to the next larger integer, and thus should be converted before the decimal part. The conversion of the integer part is easiest to implement via repeated division-by-10 with remainder, each consecutive remainder yielding the decimal digits from right to left."AI" calculations often use either integers or a half-precision floating point with 11-bit mantissa (implicit most significant bit, which is 1 for all normals and 0 for subnormals) and 5-bit exponent. It has the nice feature that all its finite values, including subnormals, can be represented exactly in magnitude (i.e. ignoring sign bit) using 40-bit Q16.Q24 fixed point format. Multiplication is only 11-bit by 11-bit with 22-bit result, of which 11 high bits remain in the result, and the highest dropped bit determines rounding; addition and subtraction requires a 21-bit scratchpad.

author: SiliconWizard
author_rank: Super Contributor
timestamp: June 15, 2024, 09:41:42 pm
content: One way of not having to bother with this in C (but make sure that's what you want of course) is to include tgmath.h instead of math.h. It provides 'generic' math functions, which will call the corresponding variant of the function depending on the type of the argument(s). Particularly handy if you want to make some piece of code agnostic to the FP type and reserve the possibility to change types with just a typedef or macro. Sure you can roll that yourself with a bunch of macros, but it can get ugly, while this is already made, and standard. Requires C11 minimum.

author: westfw
author_rank: Super Contributor
timestamp: June 16, 2024, 08:19:50 am
content: QuoteParticularly on the recent crop of microcontrollers with HW support for single precision floats, but not for doubles.A non-IEEE variation I'm wondering about would be something that produces near-double precision results in two single-precision variables, and thus uses single-precision hardware. (48bit mantissa, 8bit exponent, essentially. Pi = 3.14159e0 + 2.65359e-6, or binary equivalent thereof.)

author: coppice
author_rank: Super Contributor
timestamp: June 16, 2024, 03:47:44 pm
content: Quote from: Siwastaja on June 15, 2024, 11:36:02 amIf you want to turn the warning level to maximum with some C and most C++ compilers you need to add all those f suffices, or you get a lot of warnings,

author: Twoflower
author_rank: Frequent Contributor
timestamp: June 16, 2024, 04:04:38 pm
content: Quote from: coppice on June 16, 2024, 03:47:44 pmFor gccCode:-Wdouble-promotionwill be sufficient to inform you about that. As mentioned, very useful if your hardware supports SP in hardware.

author: eutectique
author_rank: Frequent Contributor
timestamp: June 16, 2024, 04:37:52 pm
content: Quote from: peter-h on June 14, 2024, 03:19:32 pmIn "Cross Reference Table" of your map file, search for functions__aeabi_f2d(float to double) and__aeabi_d2f(double to float). Also check for__adddf3(add two doubles),__divdf3(divide two doubles), and similar. This will get you to the compilation unit.To find the line number, disassemble the corresponding object file with suitable options:arm-none-eabi-objdump -Sl file-with-doubles.o

author: peter-h
author_rank: Super Contributor
timestamp: June 16, 2024, 05:28:57 pm
content: Right; __aeabi_f2d in the .map takes me to the .o files of the very .c modules where I was doing the dodgy doublesSame for __aeabi_d2f.None of them are performance related though. And almost all impossible to fix because the value eventually goes to a printf type function.

author: NorthGuy
author_rank: Super Contributor
timestamp: June 16, 2024, 05:48:54 pm
content: Floating point takes off some precision, because it needs space to store exponent. If you compare single precision floating point to 32-bit integer (both take the same space), integer values from 0 to about 16 million can be represented exactly by both, in range from 16 million to 4 billion 32-bit integer will be more accurate. Of course, 32-bit integers cannot store values about above 4 billion.But 4 billion is a huge range. Like if you measure voltage in uV, it will take you up to 4000 V. Even if you use things with big dynamic range, such as capacitors, it's still not that bad - measuring in pF you can represent any capacitance from 1 pF to 4 mF.Of course if you're doing something like inverting nearly singular matrices, you must use floats. But for simple operations, such as calculating averages, filters etc. using floats will only lose precision without any positive effect.

author: coppice
author_rank: Super Contributor
timestamp: June 16, 2024, 05:57:18 pm
content: Quote from: NorthGuy on June 16, 2024, 05:48:54 pmFloating point gives you a reasonably consistent level of precision over a wide dynamic. Integers gives you a highly variable level of precision, depending where you are in their number range. When the integer is 2, the options of 1 and 3 as the finest possible changes you can make represent bloody awful precision.

author: NorthGuy
author_rank: Super Contributor
timestamp: June 16, 2024, 06:37:00 pm
content: Quote from: coppice on June 16, 2024, 05:57:18 pmIf you measure voltage from 0 to 3 V and you decide to measure it in volts with choices of 0, 1, 2, or 3, this is of course terrible. However, if you switch to nV then it is excellent resolution despite that there's nothing between 2 and 3 nV because your ADC step is in hundreds of nV at best.

author: peter-h
author_rank: Super Contributor
timestamp: June 16, 2024, 06:50:12 pm
content: This has been a super bit of learning. In this project it has not mattered (due to eventual snprintf etc) but in others it will matter. There must be a 100x perf difference...Thank you all.Yes I know about integer mathsMany years ago, Z80, I did various products which all used 32 bit integer maths. It does work but you have to know what is going on in great detail.

author: coppice
author_rank: Super Contributor
timestamp: June 16, 2024, 06:57:18 pm
content: Quote from: NorthGuy on June 16, 2024, 06:37:00 pmIf you range switch you have a form of floating point. That's exactly what floating point does.

author: NorthGuy
author_rank: Super Contributor
timestamp: June 16, 2024, 07:42:08 pm
content: Quote from: coppice on June 16, 2024, 06:57:18 pmFloating point calculations switch ranges at runtime (hence processing overhead). With fixed point integers you set the range manually at design time (hence higher resolution and lower overhead).

author: peter-h
author_rank: Super Contributor
timestamp: June 16, 2024, 10:03:56 pm
content: That's a good way to put it.The problem with fixed point is that in many cases you need to put in a whole load of your time to make sure the required dynamic range of the data is maintained. If say filtering the output of an ADC (taking last 100 values, adding them up, and dividing by 100 - an FIR filter AFAIK) then this is trivial using uint32. The PID control loop of a temperature controller will be a lot less trivial (although PID controllers are an old hat now and everybody and their dog knows how to do it efficiently) and the control loop of an autopilot will be much less trivial still. In Apollo they did it all using basically int32 (I've read the books) but they had a bunch of super clever guys there who came in with algorithms from the Polaris missile etc project. In modern times, working to a commercial schedule, that would just be masochism. Single floats are a great solution, 24 bits is way more accuracy (or even real noise-free resolution) than any manufacturable analog system can achieve, and at 7ns for a multiply...

author: Nominal Animal
author_rank: Super Contributor
timestamp: June 16, 2024, 11:28:01 pm
content: I was thinking of a soft-fp format on 8-bit MCUs with 40-bit mantissa \$m\$, radix 256, and a 7-bit exponent \$p\$, as in \$m \cdot 256^{p - 67} = m \cdot 2^{8 p - 536}\$ with a separate sign bit.While it would mean the actual precision would vary in steps, cyclically, between 32 to 40 bits, it would eliminate bit shifts. It would seriously speed up additions and subtractions, but also multiplications. Calculatingc+=a×b, if we label the five bytes 0 (least significant) to 4 (most significant), thenc4:c3+=a4×b4c3:c2+=a4×b3+a3×b4c2:c1+=a4×b2+a3×b3+a2×b4c1:c0+=a4×b1+a3×b2+a2×b3+a1×b4c0:u4+=a4×b0+a3×b1+a2×b2+a1×b3+a0×b4u4:u3+=a3×b0+a2×b1+a1×b2+a0×b3u3:u2+=a2×b0+a1×b1+a0×b2u2:u1+=a1×b0+a0×b1u1:u0+=a0×b0Whilec3will be nonzero for nonzero products,c4may become zero (whenevera4×b4< 256). Then,u4:u3products may need to be calculated, to obtain the eight bits "shifted in" (while decrementing the exponent). Typically 15 multiplications and 30 byte-wise additions are needed (not counting carry rippling, which isn't that common).The problem isn'tspeed, but the amount of machine instructions this kind of code generates, in my opinion. Most 8-bitters don't have that much Flash. Even if you implement the above using loops, you end up generating many instructions to compute the addresses. One could write the multiply-add in assembly by hand, but even then it is quite a few instructions. It ends upbulky, not slow.On 32-bit MCUs with a fast 32×32-bit multiplication returning the high or low 32 bits of the product, software floating-point is usually not worth it, because you can do multi-word products like above. For example, if you have a fixed-point format with 32 fractional bits, 31 integral bits, and a sign bit, thenc=a×bisc3= HI(a1×b1)c2= HI(a1×b0) + HI(a0×b1)+ LO(a1×b1)c1= HI(a0×b0)+ LO(a0×b1) + LO(a1×b0)c0= LO(a0×b0)of which onlyc2:c1forms the resultc. We only need the high bit ofc0for half-ULP correct rounding, and that is the same as AND operation between the most significant bits ofa0andb0. (When that bit and the least significant bit ofc1are both set, incrementc1, and carry over toc2andc3if overflow. Otherwise it is ignored.) Ifc3is nonzero, the product overflows.Thus, for modular arithmetic, you only need 6 32-bit unsigned multiplications (three low, three high), some additions, and a couple of bit checks for half-ULP correct rounding. Addition and subtraction is just two 32-bit operations. Division can be implemented either bitwise, or via 32-bit/32-bit division-and-remainder.Extending to 96 bits,c5= HI(a2×b2)c4= HI(a2×b1) + HI(a1×b2)+ LO(a2×b2)c3= HI(a2×b0) + HI(a1×b1) + HI(a0×b2)+ LO(a2×b1) + LO(a1×b2)c2= HI(a1×b0) + HI(a0×b1)+ LO(a2×b0) + LO(a1×b1) + LO(a0×b2)c1= HI(a0×b0)+ LO(a0×b1) + LO(a1×b0)c0= LO(a0×b0)For 32 fractional bits, you usec1,c2, andc3. Again, only the most significant bit ofc0matters, and ifc4orc5are nonzero, the calculation overflows. For modular arithmetic, you only need 13 multiplications.For 64 fractional bits, you usec2,c3, andc4. For half-ULP correct rounding, you need to calculate the most significant bit ofc1, but it again only matters when the least significant bit ofc2is also set.For branchless ripple carry, after adding to e.g.c2, you need to add zero with carry toc3(and if using 64 fractional bits, toc4), so there are quite a few additions as well, but they tend to be very fast.13 multiplications can sound like a lot, but 96 bits is a crapton of precision, easily beating even IEEE 754 double-precision floating point, unless you for some strange reason need the range. I rarely do, even in atomic simulations.From above, we can easily extend intoarbitrary precisionnumbers, simply by making the number of integral and fractional words –"limbs"– variable, in both directions.Arbitrary precision numbers are not floating-point, because the decimal point is always where the two branches (integral and fractional) meet.Combining both, you can represent any real number as a continuous sequence of (say 32-bit unsigned) words, i.e.$$v = \left(-1\right)^S \, \sum_{k=0}^{N-1} m_k \, 2^{32 ( k + B) }$$where \$S\$ is the sign bit, \$B\$ is the position of the least significant bit (in units of 32 bits), \$N\$ is the number of words in the value, and \$m_0\$ through \$m_{N-1}\$ contain the bit representation of the value.Arbitrary precision fixed- and floating-point types are generally calledmultiprecision numbers.These are scary powerful: no limits on what you can do, really. For example, you can easily use these – say, as provided by theGNU MPFR(multiple-precision floating-point library) orGNU MPC(multi-precision library), orany of the many alternativesto calculate exactly-correct IEEE Binary32 (float), Binary64 (double), Binary128(quad), or whatever precision you need. In fact, most Linux/BSD/Unix machines havebcinstalled, which is a command-line arbitrary-precision calculator. For example, if you want the first 101 decimal digits of pi, you use the arctan(1) function:echo 'scale=100; 4*a(1)' | bc -l(Yes,bcshortens arctan() toa(), sin tos()cosine toc(), natural logarithm tol(), exponential toe(), when the math library is enabled via the-loption.)Do let me know if you see any bugs in above. I've been buggy lately!

author: NorthGuy
author_rank: Super Contributor
timestamp: June 16, 2024, 11:48:58 pm
content: Quote from: peter-h on June 16, 2024, 10:03:56 pmMany (if not all) manufacturers offer Q31 libraries which can be used for coordinate transforms in machines, robots, solar trackers etc. They will have sin, cos and other common functions. So it should not be too much harder than with floats.

author: iMo
author_rank: Super Contributor
timestamp: June 17, 2024, 05:32:52 am
content: Quote from: NorthGuy on June 16, 2024, 11:48:58 pmGuys, you do not know what thereal masochismactually means, indeed..