Working with Floats
#1
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.0449. Accuracy is now off by more than 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 all instructions listed in this chapter EXCEPT for 'fmr', 'fneg', and 'frsp': to have the instruction as double precision remove the final 's' from the instruction operand. Example: fadds -> fadd

fadds fD, fA, fB #Adds single precision values of fA and fB, result is written to fD.

fmuls fD, fA, fB #Multiplies the single precision values of fA and fB, result written to fD.

fsubs fD, fA, fB #fA minus fb = fD. 

fdivs fD, fA, fB #fA / fB = 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

Another note: There's another different type of floating compare instruction, its is fcmpu. fcmpu = floating compared unordered. To my limited knowledge, this does a 'faster' comparison which can make it less accurate. For Wii codes, it is futile to be concerned with saving speed. Use the ordered comparison to always ensure accuracy.



Chapter 5: Conversions

Do you need a convert a value in an fpr to an integer value? There's an entire separate thread covering that - https://mkwii.com/showthread.php?tid=967

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 not being able to set fractional integer values in the GPRs, the result will always safe to use as single precision in memory if needed.



Chapter 6: Paired Singles

For advanced coders only, provided here for the rare case somebody needs it.

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.

WiiBrew has a great article covering Paired Singles along with a list of instructions - http://wiibrew.org/wiki/Paired_single

Regarding the W and I values from the article: Use what your default instruction uses.
Regarding ps0 and ps1: ps0 = first 64 bit segment of fpr. ps1 = second 64 bit segment

Happy coding!
Reply


Messages In This Thread
Working with Floats - by Vega - 01-30-2021, 10:17 PM

Forum Jump:


Users browsing this thread: 1 Guest(s)