Working with Floats

Beginner or Intermediate ASM coders may have not done any work on Floating Points yet or very little. This thread will cover some basics of working with floats.

Chapter 1: Fundamentals

There are 32 floating point registers aka fpr's (f0 thru f31). Floating point values in the fpr's are displayed in their 64-bit (double-word) hexadecimal form. The float values can either used as single precision or double precision. Single precision floats will have less accuracy, but they only take up a word of space when residing in memory (in their 32 bit hexadecimal form). Double precision floats are for precise work, but at a cost with taking two words of space in memory.

When a fpr value is stored to memory as single precision, its hexadecimal 32 bit (word) converted value is what gets stored. When a fpr value is stored to memory as double precision, what you see in the fpr register is what gets exactly stored to memory (double word)

Example of a Single Precision float in an fpr

Code:

`40B3880000000000 #Converted to decimal = 5000`

If this fpr value was stored to memory (converted to its 32 bit form), it will show a word value of 0x459C4000.

Question:

How do I tell if a fpr value is in single precision?

Answer:

If the last 7 digits of the fpr is null while the first digit of the second 32 bit segment is an even hex number (0,2,4,6,8,a,c,e), then the value is in single precision. See the guide below...

Code:

`Guide:`

XXXXXXXXY0000000

XXXXXXXXY = The value in fpr while Y is an even hex digit

Lets look at an example of a Double Precision float in an fpr...

Code:

`40B3880B8723A502 #Converted to decimal = 5000.045030811105`

If you took this fpr value and stored it to memory as double precision, it store as a doubleword hexadecimal value of 0x40B3880B8723A502 (what you exactly see in the fpr).

Remember when I said double precision is used to maintain high accuracy? Well, if you were to take this double precision float value from its fpr and store it to memory as single precision, it would store as 0x459C405C.

Convert 0x459C405C to decimal and you get 5000.044921875. Accuracy is now off by almost 0.0001!

You will notice that there is a entirely separate second 64 bit segment in each fpr. For now we will not cover this second 64 bit segment as that deals with what's called 'Paired Singles'. Float instructions (that are NOT paired single instructions) only use the first 64 bit segment. Paired Singles are any instructions that have the first two letters of 'ps' it its instruction operand.

You will need a good 32 bit and 64 bit float converter. Here's a 64 bit converter (only allows one way converting. ie: 64bit float -> 32bit float & decimal)...

https://babbage.cs.qc.cuny.edu/IEEE-754.old/64bit.html

The converter is handy if you need to take the 64 bit value of a floating point register and find out what word value it would be if it was stored to memory as single precision.

Also here's a 32 bit float converter, obviously this can only be used for single precision. (Allows both ways of conversion for single precision 32-bit float to/from decimal)

https://www.h-schmidt.net/FloatConverter/IEEE754.html

Question:

How do I know which precision to use?

Answer:

Use the precision that corresponds to the default instruction of your code address. Both single precision and double precision instructions will be covered in the next chapters, showing you how to tell the difference between the two.

FYI:

Aldelaro's Dolphin-memory-engine comes with options to search for floats. You can only search using a decimal represent to look for single precision 32-bit float values in memory (word values).

Chapter 2: Storing/Loading

Here are the two basic instructions for loading and storing floating points.

stfs fD, VALUE (rA) #NOTE use stfd for double precision

This will store the float word value was single precision to the memory address calculated by VALUE+rA. VALUE (the offset) is signed.

lfs fD, VALUE (rA) #NOTE: use lfd for double precision

This will load the float word value as single precision from the memory address calculated by VALUE+rA and store it in fD. VALUE (the offset) is signed.

Let's look at example where you have a single precision loading float instruction 'lfs f2, 0x20 (r5)', and you want to manually modify the value that gets loaded into f2.

#rX = Whatever is a safe register for use in your code

Code:

`lis rX, 0x4201 #Single precision float value for decimal value of 32.3`

ori rX, rX, 0x3333

stfs rX, 0x20 (r5) #Write over what's going to be loaded from memory

lfs f2, 0x20 (r5) #Now load it; default instruction

If you are working with a double precision loading float instruction, it's gonna require a bit more work. Let's say our default instruction is lfd f3, 0x64 (r30).

WWWWXXXXYYYYZZZZ = Desired manually written 64 bit double precision float value

Code:

`lis rX, 0xWWWW #Set upper 32 bits`

ori rX, rX, 0xXXXX

stw rX, 0x64 (r30) #Store upper bits

lis rX, 0xYYYY #Set lower 32 bits

ori rX, rX, 0xZZZZ

stw rX, 0x68 (r30) #Store lower bits, Remember we have to increment the address by 0x4, due to double word value in memory

lfd f3, 0x64 (r30) #Default instruction

Chapter 3: Basic Math plus other instructions

There's a lot more math-based instructions for floats than compared to integers in Broadway PPC.

Important NOTE for the following 4 instructions: To have the instruction as double precision remove the final 's' from the instruction operand. Example: fadds -> fadd

fadds fD, fA, fB #fA is added with fB. Result in fD.

fmuls fD, fA, fB #fA is multiplied with fB. Result in fD.

fsubs fD, fA, fB #fA minus fB. Result in fD.

fdivs fD, fA, fB #fA divided by fB. Result in fD.

-----

Need to copy a value from floating point register to another? Simple.

fmr fD, fA #fA's value is copied to fD

Need to flip a positive value to be negative, or vice versa? Easy to do.

fneg fD, fA #fA's value is flipped and result is written to fD.

Need to make any possible negative results positive (their absolute value); easy peasy

fabs fD, fA #The absolute value of fA is placed in fD

Need to do the opposite of that (ngeative absolute), say no more

fnabs fD, fA #The negative absolute value of fA is placed in fD

Gotta round the float to its single precision value? Here ya go

frsp fD, fA #The rounded single precision value of fA is placed in fD

Chapter 4: Float Comparisons

What if we need to do some comparison work with floats? Its similar to integer or GPR comparisons, but requires some extra work.

Floating Compare Ordered

fcmpo crf, fD, fA

This will do a comparison of fD vs fA. crf = the Condition Field within the Condition Register that will be used. For more details of the Condition Register and CR fields view this thread HERE.

You need to specify the condition field. For basic Wii code usage, cr1 is what you want to use.

Examples:

Code:

`#Compare f1 vs f2, if equal then branch. The branch is less likely to occur`

fcmpo cr1, f1, f2

beq- cr1, some_label

Code:

`#Compare f0 vs f30, if f0 is greater than f30, branch. The branch is most likely to occur`

fcmpo cr1, f0, f30

bgt+ cr1, some_label

It's important to note that there's no such thing as logical vs signed when it comes to floating comparisons. Bit 0 of the floating point value will let you know if a value is positive or not. Here's a snippet of source that checks if a float is negative~

Code:

`#Example that assumes f13 is safe, and you want to check if f7 is negative, adjust which registers to use for your code`

fsub f13, f13, f13 #Subtract f13 by itself to make it zero

fcmpo cr1, f7, f13 #Now check f7's value against zero; place desired branch below

Note:

There's another different type of floating compare instruction, its is fcmpu. fcmpu = floating compared unordered. The difference between fcmpo and fcmpu is how the FPSCR (floating point status control register) is modified when a NaN (not a number) is present in the comparison. Thus, in simple terms, don't worry about fcmpu, just use fcmpo.

IMPORTANT NOTE regarding RC (.) shortcut for floating point instructions:

If a floating point instruction is used with the 'Record' shortcut/feature (as long as said instruction allows it), please note that the comparison will be done on cr1! Afaik, the comparison done from the RC shortcut is an ordered comparison, not that it really matters. This is different than integer instructions where the Record feature effects cr0.

Chapter 5: Conversions Pt 1: (floats to integers)

There are two different instructions you can choose from to convert a floating point value to an integer.

fctiw fD, fA #fA is converted into integer form, result is placed into fD; standard rounding is applied (i.e. decimal value 5.8 rounds to 6)

fctiwz fD, fA #Same as instruction above but the value is rounded towards zero (i.e. decimal value 5.8 rounds down to 5)

Fyi: The rounding examples are shown in decimal representation for ease of readability. Obviously the integer values will be in hex form.

NOTE: When values are converted, do NOT concern yourself with the upper 32 bits of the fpr. The lower 32 bits will hold the result.

Example: Converting f1's value of 0x403EDF5840000000 to an integer. Place result in f13, apply standard rounding

Code:

`fctiw f13, f1`

Once this instruction has executed, f13 is now 0xFFF800000000001F. The lower 32 bits (0x0000001F) is our result (31 in decimal). The exact decimal conversion of 0x403EDF5840000000 is 30.872440338134766. Since standard rounding was used, the result was rounded up to 31 (0x1F). If you instead used the fctiwz instruction, the result would have been rounded down to 30 (0x1E).

~How to store the result to memory:

There are two different ways to store the integer result.

Method #1: stfiwx

stfiwx fD, rA, rB

Values of rA + rB = effective address. Lower 32 bits of fD (the integer result) is stored to the effective address.

Method #2: stfd (instruction example plus syntax shown in Chapter 2: Storing & Loading.

Both examples storing the result of f13 to memory address 0x80001554. Assume f13, f11 and r12 are safe for use.

Using Method #1:

Code:

`lis r12, 0x8000`

li r11, 0x1554

stfiwx f13, r11, r12

Using Method #2:

Code:

`lis r12, 0x8000`

stfd f13, 0x1550 (r12) #Since the entire double-word is being stored, we need to store it at 0x80001500 instead of 0x80001554.

Pros and Cons of stfiwx Method vs stfd Method:

- stfiwx requires 2 GPR's to use but only uses one word of space in memory

- stfd only requires 1 GPR but at the expense of using a double-word of space in memory.

Chapter 6: Conversions Pt 2: (integers to floats)

Do you need to convert an integer value to it's fpr value? Here's a snippet of code to do that (thank you salmon01)~

Code:

`#r3 holds your integer value to convert, result will be placed into f1. `

#This code assumes r3, r4, f1, and f2 are all safe. Adjust choosing which registers to use accordingly for your code's safety requirements

lis r4, 0x4330

stw r4, -0x8 (r1)

lis r4, 0x8000

stw r4, -0x4 (r1)

lfd f2, -0x8 (r1) # load magic double into f2

xoris r3, r3, 0x8000 # flip sign bit

stw r3, -0x4 (r1) # store lower half (upper half already stored)

lfd f1, -0x8 (r1)

fsub f1, f1, f2 # complete conversion

^Due to integer values within GPRs only being whole numbers,the floating point value result in f1 will always be of single precision.

Chapter 7: Paired Singles

Remember that second 64 bit segment that was mentioned at the beginning of the thread? That deals with paired singles. Paired singles are just two single precision floats that can be manipulated simultaneously.

The first 64 bit segment of the fpr is the first paired single. And the second 64 bit segment is the second paired single. Paired singles are ALWAYS single precision!

If a paired single is stored to memory, both single precision floats are converted to their hexadecimal word value and stored to memory as a double word.

Example~

Code:

`C06C800000000000 4170000000000000 is the value in the fpr`

When written to memory, it will have a double word value of 0xC36400004B800000.

Here's a full tutorial on Paired Singles - https://mkwii.com/showthread.php?tid=1870

Happy coding!