0

I am very new to assembly language so bear with me...

I have a floating-point that I have rounded to the nearest .001, but still displays as something like +1.6670000E+000. I would like it to display simply as 1.667.

Here is my code for dividing and rounding the numbers:

fild    num_a
fidiv   num_b
fimul   thousand
frndint
fidiv   thousand
fst     a_div_b

And here is my code for displaying the float:

mov     eax, num_a
call    WriteDec
mov     edx, OFFSET divide
call    WriteString
mov     eax, num_b
call    WriteDec
mov     edx, OFFSET equals
call    WriteString
fld     a_div_b
call    WriteFloat
call    CrLf

I looked a lot online and there were many answers on how to round numbers, but they still all displayed in the extended format. I am using the irvine32 library.

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
xHoudek
  • 115
  • 1
  • 10
  • 5
    If you don't have access to a function that allows specifying the format (e.g. `printf`) then separate the integer and the fractional part yourself and print those with a dot between them according to however many digits you want. – Jester Apr 03 '20 at 21:42
  • That makes sense, thank you! I understand how to get the integer part, but I don't know how I would get the fractional part, let alone only printing 3 digits of it. – xHoudek Apr 03 '20 at 21:45
  • 2
    Once you got the integer part, subtract from the original number, then multiply by 1000 to get 3 digits. – Jester Apr 03 '20 at 21:49
  • 1
    You can isolate the fractional part with `x - (int)x` (using truncation towards 0, not the default round to nearest, e.g. with SSE3 `fisttp`. Otherwise you could get a negative fractional part, and an integer part that's 1 too high. Of course this is much easier with SSE2 where you can use `cvttsd2si` instead of using legacy x87 at all). To get the leading 3 digits of that, you're already using hacks like multiplying by 1000 to make some of the fractional part integer. – Peter Cordes Apr 03 '20 at 21:49
  • Oh man, it's so obvious once it's explained to me. Last question, how would I deal with with a leading zero? If I'm understanding correctly, 1.062 would be converted to .062 * 1000 = 62. Then concatenating the numbers would become 1.62. I could maybe use if statements to check how many digits are in the decimal number, but is there a simpler way than that? – xHoudek Apr 03 '20 at 21:54
  • 2
    You might as well do the number to string conversion yourself, just divide by 10 in a loop and take the remainder each time. That's the easiest for the leading zeroes. – Jester Apr 03 '20 at 22:08

1 Answers1

2

An even a simpler modification to the approach discussed in comments:

Multiply by 1000 and convert that to integer with fistp (with the default rounding to nearest), instead of just rounding to an integer-valued long double using frndint.

The low 3 decimal digits of that integer are the fractional part of your number. i.e. you now have decimal fixed-point. div by 1000 gives you quotient (integer part) and remainder (fractional part). Print both parts with a . between them.

You'll want to do manual int->string conversion (How do I print an integer in Assembly Level Programming without printf from the c library?) or otherwise print leading zeros in the fractional part. (So 2.062 doesn't turn into 2.62)


This is easier than separating into integer and fractional parts in FP, which would require rounding with truncation toward zero to make sure you got a non-negative fractional part. Integer division naturally truncates towards zero, but legacy x87 FP->int conversion can only use the default rounding mode. (Except with SSE3 fisttp.) SSE1/2 had XMM FP->int conversions with truncation or current rounding mode since they were introduced, like cvttsd2si vs. cvtsd2si

Downside: overflows a 32-bit integer for smaller float inputs, because a single 32-bit integer has to hold x * 1000.

The other way is to use x - (int)x to get the fractional part and only multiplying that fractional part by 1000.0. That leads to (int)x in a separate integer from the the fractional part, with x*1000 only existing as floating point, not int32_t.


Fun fact:

AVX512DQ has an instruction for getting the fractional part directly: VREDUCESD xmm1, xmm2, xmm3/m64, imm8 (and ss/ps/pd versions). It's the part that SSE4 roundsd / vrndscalesd would discard when keeping the integer part. Even more fun: can keep a specified number of fraction bits. But of course those are binary fraction bits, not decimal places.

Most x86 CPUs have SSE4.1 these days, but only Skylake-X high-end desktops and modern Xeons have AVX512DQ. :/ And Ice Lake laptops.

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847