0

Problem

I want to access the source code the built-in function round(), to allow me to create a very similar function. How can i access this source code and how easily will this be to edit / use?


The reason I am interested in doing this is that the built-in function round() converts an integer into float even when the number of digits is negative. For Example:

round(1234.5678,-2)

Returns

1200.0 

I want to create a function that returns an integer. I am sure there other methods of achieving the same result, but I want to see how the built in function achieves this task as I would expect this to be reasonably efficient.

CodeCupboard
  • 1,507
  • 3
  • 17
  • 26
  • 1
    don't know about `round`, but you can start your search [here](https://github.com/python/cpython) if we talking about cpython – Grigory Sep 06 '17 at 15:07
  • 2
    The source will be in C, see [Finding the source code for built-in Python functions?](https://stackoverflow.com/questions/8608587/finding-the-source-code-for-built-in-python-functions) .. whats wrong with `int(round(x))` ? – Alex K. Sep 06 '17 at 15:07
  • @AlexK. for very large numbers this will create an error, due to the size limit on floats. – CodeCupboard Sep 06 '17 at 15:13
  • @BryanOakley i did try that, cheers though... – CodeCupboard Sep 06 '17 at 15:13
  • If you tried it, you should include that information in the question, along with why that didn't work for you. There's no way for us to know what you've tried or not tried unless you tell us. – Bryan Oakley Sep 06 '17 at 15:25
  • @BryanOakley Would it not be healthy to assume that the questioner has already tried to solve the problem themselves? It takes more effort to create a question on here than to google it. I know there are lots of poor question on here where maybe a google could have yielded an answer easily, but chances are the questioner has failed this for whatever reason. If the purpose of this site to help people and also create an archive of questions with good answers, comments like "have you googled it" does not help anyone. – CodeCupboard Sep 06 '17 at 15:53
  • _"Would it not be healthy to assume that the questioner has already tried to solve the problem themselves"_ - I say that no, it is not. I see many, many, many, _many_ questions that could be solved with a simple search on google, or a reading of basic documentation. Knowing that an answer is easy to find, and yet the OP doesn't find it, makes me think they didn't try. Knowing what the OP has and hasn't researched helps us write better answers. – Bryan Oakley Sep 06 '17 at 15:59
  • _"It takes more effort to create a question on here than to google it"_ - That's the weird thing. It's quite often quicker to simply type some code in to see what happens, or to do a basic google search, or search on this site, yet I see time and again people who ask before doing any research at all. I don't want to post an answer that is essentially the same as the first google result, only to find out later that the OP tried the first google result and it didn't help for one reason or another. – Bryan Oakley Sep 06 '17 at 16:00
  • See [How much research effort is expected of Stack Overflow users?](http://meta.stackoverflow.com/q/261592/7432) – Bryan Oakley Sep 06 '17 at 16:03

2 Answers2

4

just expanding the comment from Mark Dickinson and to make sure I understand it myself, the CPython round function is spread over several parts of the code base.

round(number, ndigits) starts by looking up and invoking the __round__ method on the object. this is implemented by the C function builtin_round_impl in bltinmodule.c

for floats this invokes the float.__round__ method, which is implemented in float___round___impl in floatobject.c:1045 but there's a stub entry point in floatobject.c.h that I think is mostly maintained by Python's argument clinic tool. this header is also where its PyMethodDef is defined as FLOAT___ROUND___METHODDEF

the C function float___round___impl starts by checking if ndigits was not specified (i.e. nothing passed, or passed as None), in this case then it calls round from the C standard library (or the version from pymath.c as a fallback).

if ndigits is specified then it probably calls the version of double_round in floatobject.c:927. this works in 53bit precision, so adjusts floating point rounding modes and is generally pretty fiddly code, but basically it converts the double to a string with a given precision, and then converts back to a double

for a small number of platforms there's another version of double_round at floatobject.c:985 that does the obvious thing of basically round(x * 10**ndigits) / 10**ndigits, but these extra operations can reduce precision of the result

note that the higher precision version will give different answers to the version in NumPy and equivalent version in R, as commented on here. for example, round(0.075, 2) results in 0.07 with the builtin round, while numpy and R give 0.08. the easiest way I've found of seeing what's going on is by using the decimal module to see the full decimal expansion of the float:

from decimal import Decimal

print(Decimal(0.075))

gives: 0.0749999999999999972…, i.e. 0.075 can't be accurately represented by a (binary) floating point number and the closest number happens to be slightly smaller, and hence it rounds down to 0.07. while the implementation in numpy gives 0.08 because it effectively does round(0.075 * 100) / 100 and the intermediate value happens to round up, i.e:

print(Decimal(0.075 * 100))

giving exactly 7.5, which rounds exactly to 8.

Sam Mason
  • 15,216
  • 1
  • 41
  • 60
  • Very thorough! I'd suggest replacing some of the source links with permalinks, else they'll become invalid if/when `floatobject.c` changes in the 3.7 branch. As for the last paragraph, there's a single correctly-rounded result for the `round` operation for any given input. Correct rounding is incredibly useful, in that it ensures reproducible arithmetic - if everyone had correctly-rounded `round` operations, then all of R, NumPy, Python would be giving the same results. Python definitely shouldn't deliberately give the wrong result just to match R and NumPy. – Mark Dickinson Jul 11 '19 at 16:52
  • There's also the aspect that as soon as you deviate from correct rounding, it becomes very unclear just *what* the correct answer is. In this _particular_ case, NumPy gives the expected result for this operation, but there are cases where NumPy doesn't give the expected result for a round, too, and it's really hard to pin down exactly what the "expected result" means if you're not going to aim for correct rounding. If you _are_ going to aim for correct rounding, the expected result is clear in all cases. – Mark Dickinson Jul 11 '19 at 16:54
  • @MarkDickinson this answer was partly motivated by https://stackoverflow.com/a/56974893/1358308 where I tried to explain where rounding errors were coming from. when I later actually tried to replicate Python's result in any other language I started to doubt that "doing the correct thing" was actually desirable – Sam Mason Jul 11 '19 at 17:00
  • Right, but then what's the alternative? How do you come up with a specification for what _is_ desirable? Note that e.g., `np.round(0.545, 2)` gives `0.55` (at least on my machine, and probably on yours, too). Should that be considered a bug in NumPy? – Mark Dickinson Jul 11 '19 at 17:01
1

The source seems to be: https://github.com/python/cpython/blob/master/Python/pymath.c

double
round(double x)
{
    double absx, y;
    absx = fabs(x);
    y = floor(absx);
    if (absx - y >= 0.5)
        y += 1.0;
    return copysign(y, x);
}

where copysign is:

double
copysign(double x, double y)
{
    /* use atan2 to distinguish -0. from 0. */
    if (y > 0. || (y == 0. && atan2(y, -1.) > 0.)) {
        return fabs(x);
    } else {
        return -fabs(x);
    }
}
Simpom
  • 938
  • 1
  • 6
  • 23
  • 2
    Unfortunately, that's not the source for Python's `round` function; it's the source for the fallback C `round` function for platforms that don't supply the C99 `round` function already (most do). (And similarly for copysign.) For the `round` implementation for the `float` type, start [here](https://github.com/python/cpython/blob/v3.6.2/Objects/floatobject.c#L1019-L1066) – Mark Dickinson Sep 10 '17 at 12:10
  • @MarkDickinson I've added an answer expanding on your above comment, as you seem to have an interest in floating point I was wondering if I've got it about right – Sam Mason Jul 11 '19 at 10:53
  • @MarkDickinson looks like this one may be more revealing on its implementation https://github.com/python/cpython/blob/5fd33b5926eb8c9352bf5718369b4a8d72c4bb44/Objects/floatobject.c#L914 – nimig18 Jun 06 '21 at 04:47
  • @nimig18: Yep, that's the one. Note that the bulk of the work is in `_Py_dg_dtoa` and `_Py_dg_strtod`, and those are _not_ simple functions. – Mark Dickinson Jun 06 '21 at 07:16