CodeCupboard
CodeCupboard

Reputation: 1585

Python built-in function source code - round()

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.

Upvotes: 0

Views: 5808

Answers (2)

Sam Mason
Sam Mason

Reputation: 16164

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.

Upvotes: 5

Simpom
Simpom

Reputation: 987

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);
    }
}

Upvotes: 1

Related Questions