4

How do I round an integer to the nearest whole in assembly? The use of branching is not allowed here.

For example, 195 * .85 = 165.75. Normally, I'd multiply 195 by a scale factor (100) and then multiply, then divide down the scale factor. This would give me 165. How do I get 166?

I'm sorry if this is a terrible question - I'm new to assembly! Thank you.

Alexey Frunze
  • 61,140
  • 12
  • 83
  • 180
FinalJon
  • 365
  • 2
  • 4
  • 13
  • blackbear -you wouldn't know to add 1 without first testing the decimal for being round-up or round-down. – corsiKa Oct 10 '12 at 20:57
  • @finaljon What type of rounding are you trying to do - schoolboy rounding? 45.4 = 45, 45.5 = 46? – corsiKa Oct 10 '12 at 20:57
  • Multiply by the scale factor 100.0 then add 0.5 when number is positive (otherwise add -0.5), multiply by 0.85 , divide by 100.0. – halex Oct 10 '12 at 20:58
  • @halex your use of when implies a branch which isn't allowed. – corsiKa Oct 10 '12 at 20:59
  • @corsiKa You are right, overlooked it. Maybe OP has constraint that he only wants to incorporate positive integers :) – halex Oct 10 '12 at 21:02
  • @FinalJon what do your decimals look like? Are they your standard IEEE 754? – corsiKa Oct 10 '12 at 21:02
  • I do only need to assess positive integers, but I'm curious about the negative now too... would I be able to grab a remainder and double it? That should work for positive and negative right? Or would the remainder return positive, regardless of whether the number was negative or positive? – FinalJon Oct 10 '12 at 21:04
  • @corsiKa Sorry, I'm actually not sure what IEEE 754 is. All I really know is that it's SPARC and RISC. – FinalJon Oct 10 '12 at 21:04
  • Most ISAs have FP->integer conversion with round to nearest. FP multiply and divide make no sense because you'd just get back the number you already had. Given the answers this question already has, IDK if it would be better to tag it [sparc] at this point, because none of the existing answers are specific to that, and only one of them is about asm at all (and that one mentions x86 frndint which does FP->FP rounding). The rest are about C. – Peter Cordes Mar 28 '21 at 18:24

5 Answers5

7

For example, 195 * .85 = 165.75. Normally, I'd multiply 195 by a scale factor (100) and then multiply, then divide down the scale factor. This would give me 165. How do I get 166?

Classically you'd use a power of two scale factor and shift rather than multiply and divide; I guess now that divides and multiplies cost the same as shifts on many architectures you may be more concerned with keeping a certain precision.

Anyway, as almost suggested by halex, you'd add 0.5 before dividing. The net effect will be that if the decimal part is already 0.5 or greater you'll get carry into the integer part. If not then there'll be no carry.

So:

195 * 100 = 19500
19500 * 0.85 = 16575
16575 + 50 (ie, 0.5) = 16625
16625 / 100 = 166

Tommy
  • 99,986
  • 12
  • 185
  • 204
  • I really like this. I'm partial to mine because it's clever (which isn't always a good thing!!), but I actually like this more because it's elegant and much more intuitive. – corsiKa Oct 10 '12 at 21:12
  • My only pitfall I would say is that this has a lower space than my answer :-) You will overflow 50 elements sooner with this method than with mine. But for its simplicity of concept, that's a pretty decent tradeoff. – corsiKa Oct 10 '12 at 21:13
  • I like it but it only gives the right answer for positive ints :). It would be interesting to find a method without branching that can handle the rounding of both positive and negative. – halex Oct 10 '12 at 21:22
  • @halex assuming two's comp and a desire for away-from-zero rounding, I guess you'd do a sign extension of the top bit (which, hopefully your CPU architecture can do in a single signed shift right), XOR 0011 0010 (ie, decimal 50) onto that, add a single instance of the sign bit (so, effectively, add 0 if positive, add 1 if negative) and then add all that to the original prior to your divide, since that'll give +50 for positive integers and -50 for negative integers. If you can do a signed shift into carry then you can get the 1-or-0 add via an add-with-carry of 0. Simpler solutions may exist... – Tommy Oct 10 '12 at 21:45
3

Assuming you were given only integers, and just told that one of the integers needs to be treated like a decimal using a scale factor... then you can do this by going using a scale factor that is twice as large as you need. So instead of 100, you use 200. This will cause the last bit of the result to be 1 or 0 depending on whether or not you will round up.

So in c style it looks like this

result = (195 * 85) / (100 / 2);
add = result & 1;
result = result / 2 + add;

If you weren't supposed to round up (i.e. round down) then the 'add bit' will be 0. Otherwise the 'add bit' will be 1 if you're supposed to round up.

I think that should give you the pseudocode you need to translate this into assembly properly.

corsiKa
  • 81,495
  • 25
  • 153
  • 204
3

In x86 assembly, there's the FRNDINT instruction.

Munchy G
  • 41
  • 1
-2

An integer doesn't have decimal places, therefore it is already a whole number.

Alan
  • 7,875
  • 1
  • 28
  • 48
-2

For positive and negative numbers, 6 strings in asm.

double round(double x) {
  return (long)(x + 0.5 + (*(long*)&x >> 63));
}
Ddystopia
  • 31
  • 4
  • 1
    `*(long*)&x` is undefined behaviour (violates the strict-aliasing rule). Also, `long` isn't guaranteed to be the same size as `double`. Maybe you want `memcpy` into an `int64_t` to type-pun safely? Or just use `-(x<0.0)` or `-signbit(x)` - implicit boolean to int and int->float conversion will produce a 0.0 or -1.0 for you. – Peter Cordes Mar 28 '21 at 18:27
  • Also, note that `double` has a wider value-range than `int64_t`, so rounding to integer this way will fail for numbers larger than LONG_MAX. If you're using C anyway, really just use `nearbyint` or `rint`, with compiler options that let it inline into a single instruction like `roundsd` on x86-64: [round() for float in C++](https://stackoverflow.com/a/47347224). Or `lrint(x)` and back compiles efficiently even without SSE4.1, if we're talking about x86-64. https://godbolt.org/z/T1WMzveW8. But for AArch64, `rint` or `nearbyint` inline to a single insn: https://godbolt.org/z/TMhsjb975 – Peter Cordes Mar 28 '21 at 18:37