Perilaku pembulatan Python 3.x

176

Saya baru saja membaca kembali What's New In Python 3.0 dan menyatakan:

Putaran () fungsi strategi pembulatan dan tipe pengembalian telah berubah. Kasus setengah jalan yang tepat sekarang dibulatkan ke hasil genap terdekat bukannya menjauh dari nol. (Misalnya, babak (2.5) sekarang mengembalikan 2 daripada 3.)

dan dokumentasi untuk putaran :

Untuk jenis bawaan yang mendukung putaran (), nilai dibulatkan ke kelipatan terdekat dari 10 dengan daya minus n; jika dua kelipatan sama-sama dekat, pembulatan dilakukan menuju pilihan genap

Jadi, di bawah v2.7.3 :

In [85]: round(2.5)
Out[85]: 3.0

In [86]: round(3.5)
Out[86]: 4.0

seperti yang saya harapkan. Namun, sekarang di bawah v3.2.3 :

In [32]: round(2.5)
Out[32]: 2

In [33]: round(3.5)
Out[33]: 4

Ini tampaknya kontra-intuitif dan bertentangan dengan apa yang saya pahami tentang pembulatan (dan pasti akan membuat orang tersandung). Bahasa Inggris bukan bahasa ibu saya tetapi sampai saya membaca ini saya pikir saya tahu apa arti pembulatan: - / Saya yakin pada saat v3 diperkenalkan pasti ada beberapa diskusi tentang ini, tetapi saya tidak dapat menemukan alasan yang baik dalam pencarian saya.

  1. Adakah yang tahu mengapa ini diubah?
  2. Apakah ada bahasa pemrograman utama lainnya (misalnya, C, C ++, Java, Perl, ..) yang melakukan pembulatan semacam ini (bagi saya tidak konsisten)?

Apa yang kulewatkan di sini?

UPDATE: @ Li-aungYip berkomentar tentang "Pembulatan Banker" memberi saya istilah pencarian / kata kunci yang tepat untuk mencari dan saya menemukan pertanyaan SO ini: Mengapa. NET menggunakan pembulatan bankir sebagai default? , jadi saya akan membacanya dengan cermat.

Levon
sumber
27
Saya tidak punya waktu untuk melihat ini, tetapi saya percaya ini disebut "pembulatan Bankir". Saya percaya itu biasa di industri keuangan.
Li-aung Yip
2
@sberry well, ya, perilakunya konsisten dengan deskripsi sendiri. Jadi jika akan mengatakan "pembulatan" adalah dua kali lipat nilainya dan melakukannya, ia juga akan konsisten :) .. tapi tampaknya bertentangan dengan apa pembulatan umum berarti . Jadi saya mencari pemahaman yang lebih baik.
Levon
1
@ Li-aungYip Terima kasih atas petunjuknya "Pembulatan Banker" .. Saya akan mencarinya.
Levon
3
Hanya sebuah catatan: Pembulatan bankir tidak umum hanya di bidang keuangan. Beginilah cara saya diajar di sekolah dasar di tahun 70-an :-)
Lennart Regebro

Jawaban:

160

Cara Python 3.0 dianggap sebagai metode pembulatan standar hari ini, meskipun beberapa implementasi bahasa belum di bus.

Teknik sederhana "selalu membulatkan 0,5" menghasilkan sedikit bias terhadap angka yang lebih tinggi. Dengan jumlah perhitungan yang besar, ini bisa menjadi signifikan. Pendekatan Python 3.0 menghilangkan masalah ini.

Ada lebih dari satu metode pembulatan yang umum digunakan. IEEE 754, standar internasional untuk matematika floating-point, mendefinisikan lima metode pembulatan yang berbeda (yang digunakan oleh Python 3.0 adalah default). Dan ada yang lainnya.

Perilaku ini tidak diketahui secara luas sebagaimana mestinya. AppleScript adalah, jika saya ingat dengan benar, pengguna awal metode pembulatan ini. The roundperintah dalam AppleScript sebenarnya memang menawarkan beberapa pilihan, tapi round-menuju-bahkan default seperti di IEEE 754. Rupanya insinyur yang menerapkan roundperintah begitu bosan dengan semua permintaan untuk "membuatnya bekerja seperti yang saya pelajari di sekolah "yang dia implementasikan hanya itu: round 2.5 rounding as taught in schooladalah perintah AppleScript yang valid. :-)

baik hati
sumber
4
Saya tidak mengetahui "metode pembulatan standar standar ini cukup universal saat ini", apakah Anda (atau orang lain) tahu jika C / C ++ / Java / Perl atau bahasa "arus utama" lainnya menerapkan pembulatan dengan cara yang sama?
Levon
3
Ruby yang melakukannya. Bahasa .NET Microsoft melakukannya. Java tidak muncul. Saya tidak dapat melacaknya untuk setiap bahasa yang mungkin, tetapi saya kira itu paling umum dalam bahasa yang baru dirancang. Saya membayangkan C dan C ++ sudah cukup tua sehingga tidak.
kindall
5
ruby kembali 3untuk2.5.round
jfs
14
Saya menambahkan sedikit tentang penanganan AppleScript ini karena saya suka cara sarkastik perilaku "lama" diimplementasikan.
Kindall
2
@kindall Metode ini telah menjadi mode pembulatan default IEEE sejak 1985 (ketika IEEE 754-1985 diterbitkan). Ini juga telah menjadi mode pembulatan default di C sejak setidaknya C89 (dan dengan demikian juga dalam C ++), namun , sejak C99 (dan C ++ 11 dengan dukungan sporadis sebelum itu) tersedia fungsi "round ()" yang menggunakan ikatan bulat dari nol sebagai gantinya. Pembulatan titik mengambang internal dan rangkaian fungsi rint () masih mematuhi pengaturan mode pembulatan, yang secara default adalah ikatan putaran hingga genap.
Wlerin
41

Anda bisa mengontrol pembulatan yang Anda dapatkan di Py3000 menggunakan modul Desimal :

>>> decimal.Decimal('3.5').quantize(decimal.Decimal('1'), 
    rounding=decimal.ROUND_HALF_UP)
>>> Decimal('4')

>>> decimal.Decimal('2.5').quantize(decimal.Decimal('1'),    
    rounding=decimal.ROUND_HALF_EVEN)
>>> Decimal('2')

>>> decimal.Decimal('3.5').quantize(decimal.Decimal('1'), 
    rounding=decimal.ROUND_HALF_DOWN)
>>> Decimal('3')
dawg
sumber
Terima kasih .. Saya tidak terbiasa dengan modul ini. Adakah yang tahu bagaimana saya akan mendapatkan perilaku Python v 2.x? Contoh yang Anda tunjukkan tampaknya tidak melakukan itu. Hanya ingin tahu apakah itu mungkin.
Levon
1
@Levon: Konstanta ROUND_HALF_UPsama dengan perilaku lama Python 2.X.
dawg
2
Anda juga dapat mengatur konteks untuk modul Desimal yang melakukan ini untuk Anda secara implisit. Lihat setcontext()fungsinya.
ramah
Inilah yang saya cari hari ini. Bekerja seperti yang diharapkan dalam Python 3.4.3. Juga diperhatikan, Anda dapat mengontrol berapa banyak putaran dengan mengubah quantize(decimal.Decimal('1')ke quantize(decimal.Decimal('0.00')jika Anda ingin putaran ke 100 terdekat seperti untuk uang.
Igor
Solusi ini bekerja sebagai pengganti round(number, ndigits)selama ndigitspositif, tapi mengganggu Anda tidak dapat menggunakannya untuk mengganti sesuatu seperti round(5, -1).
Pekka Klärck
15

Tambahkan saja catatan penting dari dokumentasi di sini:

https://docs.python.org/dev/library/functions.html#round

Catatan

Perilaku round () untuk floats bisa mengejutkan: misalnya, round (2.675, 2) memberi 2.67 bukannya 2.68 yang diharapkan. Ini bukan bug: ini adalah hasil dari fakta bahwa sebagian besar pecahan desimal tidak dapat diwakili persis seperti pelampung. Lihat Aritmatika Titik Apung: Masalah dan Batasan untuk informasi lebih lanjut.

Jadi jangan heran untuk mendapatkan hasil berikut di Python 3.2:

>>> round(0.25,1), round(0.35,1), round(0.45,1), round(0.55,1)
(0.2, 0.3, 0.5, 0.6)

>>> round(0.025,2), round(0.035,2), round(0.045,2), round(0.055,2)
(0.03, 0.04, 0.04, 0.06)
skif1979
sumber
Saya melihat bahwa. Dan reaksi pertama saya: Siapa yang menggunakan CPU 16-bit yang tidak mampu mewakili semua permutasi "2.67x"? Mengatakan bahwa pecahan tidak dapat diekspresikan dalam float tampaknya seperti kambing hitam di sini: tidak ada CPU modern yang tidak akurat, dalam bahasa apa pun (kecuali Python?)
Adam
9
@ Adam: Saya pikir Anda salah paham. Format biner (IEEE 754 binary64) yang digunakan untuk menyimpan float tidak dapat mewakili dengan 2.675tepat: yang paling dekat dengan komputer adalah 2.67499999999999982236431605997495353221893310546875. Itu cukup dekat, tetapi tidak persis sama dengan 2.675: itu sedikit lebih dekat 2.67daripada ke 2.68. Jadi roundfungsi melakukan hal yang benar, dan membulatkannya ke nilai 2-digit-setelah-titik yang lebih dekat, yaitu 2.67. Ini tidak ada hubungannya dengan Python, dan semuanya ada hubungannya dengan floating-point biner.
Mark Dickinson
3
Ini bukan "hal yang benar" karena itu diberikan kode sumber konstan :), tapi saya mengerti maksud Anda.
Adam
@ Adam: Saya mengalami quirkiness yang sama di JS sebelumnya jadi tidak spesifik bahasa.
Igor
5

Saya baru-baru punya masalah dengan ini juga. Oleh karena itu, saya telah mengembangkan modul python 3 yang memiliki 2 fungsi trueround () dan trueround_precision () yang membahas hal ini dan memberikan perilaku pembulatan yang sama dengan yang digunakan dari sekolah dasar (bukan pembulatan bankir). Inilah modulnya. Cukup simpan kode dan salin atau impor. Catatan: modul trueround_precision dapat mengubah perilaku pembulatan tergantung pada kebutuhan sesuai dengan ROUND_CEILING, ROUND_DOWN, ROUND_FLOOR, ROUND_HALF_DOWN, ROUND_HALF_EVEN, ROUND_HALF_UP, ROUND_UP, dan modul ROUND_05UP untuk informasi lebih lanjut tentang modul (lihat modul di modul desimal). Untuk fungsi-fungsi di bawah ini, lihat dokumen atau gunakan bantuan (trueround) dan bantuan (trueround_precision) jika disalin ke penerjemah untuk dokumentasi lebih lanjut.

#! /usr/bin/env python3
# -*- coding: utf-8 -*-

def trueround(number, places=0):
    '''
    trueround(number, places)

    example:

        >>> trueround(2.55, 1) == 2.6
        True

    uses standard functions with no import to give "normal" behavior to 
    rounding so that trueround(2.5) == 3, trueround(3.5) == 4, 
    trueround(4.5) == 5, etc. Use with caution, however. This still has 
    the same problem with floating point math. The return object will 
    be type int if places=0 or a float if places=>1.

    number is the floating point number needed rounding

    places is the number of decimal places to round to with '0' as the
        default which will actually return our interger. Otherwise, a
        floating point will be returned to the given decimal place.

    Note:   Use trueround_precision() if true precision with
            floats is needed

    GPL 2.0
    copywrite by Narnie Harshoe <[email protected]>
    '''
    place = 10**(places)
    rounded = (int(number*place + 0.5if number>=0 else -0.5))/place
    if rounded == int(rounded):
        rounded = int(rounded)
    return rounded

def trueround_precision(number, places=0, rounding=None):
    '''
    trueround_precision(number, places, rounding=ROUND_HALF_UP)

    Uses true precision for floating numbers using the 'decimal' module in
    python and assumes the module has already been imported before calling
    this function. The return object is of type Decimal.

    All rounding options are available from the decimal module including 
    ROUND_CEILING, ROUND_DOWN, ROUND_FLOOR, ROUND_HALF_DOWN, ROUND_HALF_EVEN, 
    ROUND_HALF_UP, ROUND_UP, and ROUND_05UP.

    examples:

        >>> trueround(2.5, 0) == Decimal('3')
        True
        >>> trueround(2.5, 0, ROUND_DOWN) == Decimal('2')
        True

    number is a floating point number or a string type containing a number on 
        on which to be acted.

    places is the number of decimal places to round to with '0' as the default.

    Note:   if type float is passed as the first argument to the function, it
            will first be converted to a str type for correct rounding.

    GPL 2.0
    copywrite by Narnie Harshoe <[email protected]>
    '''
    from decimal import Decimal as dec
    from decimal import ROUND_HALF_UP
    from decimal import ROUND_CEILING
    from decimal import ROUND_DOWN
    from decimal import ROUND_FLOOR
    from decimal import ROUND_HALF_DOWN
    from decimal import ROUND_HALF_EVEN
    from decimal import ROUND_UP
    from decimal import ROUND_05UP

    if type(number) == type(float()):
        number = str(number)
    if rounding == None:
        rounding = ROUND_HALF_UP
    place = '1.'
    for i in range(places):
        place = ''.join([place, '0'])
    return dec(number).quantize(dec(place), rounding=rounding)

Semoga ini membantu,

Narnie

narnie
sumber
5

Python 3.x rounds .5 nilai ke tetangga yang genap

assert round(0.5) == 0
assert round(1.5) == 2
assert round(2.5) == 2

import decimal

assert decimal.Decimal('0.5').to_integral_value() == 0
assert decimal.Decimal('1.5').to_integral_value() == 2
assert decimal.Decimal('2.5').to_integral_value() == 2

namun, seseorang dapat mengubah pembulatan desimal "kembali" untuk selalu membulatkan 0,5 ke atas, jika perlu:

decimal.getcontext().rounding = decimal.ROUND_HALF_UP

assert decimal.Decimal('0.5').to_integral_value() == 1
assert decimal.Decimal('1.5').to_integral_value() == 2
assert decimal.Decimal('2.5').to_integral_value() == 3

i = int(decimal.Decimal('2.5').to_integral_value()) # to get an int
assert i == 3
assert type(i) is int
kares
sumber
1

Perilaku pembulatan Python 2 dalam python 3.

Menambahkan 1 di tempat desimal ke-15. Akurasi hingga 15 digit.

round2=lambda x,y=None: round(x+1e-15,y)
SmartManoj
sumber
3
Bisakah Anda menjelaskan intuisi di balik formula ini?
Hadi
2
Dari apa yang saya mengerti, pecahan yang tidak dapat diwakili secara akurat akan memiliki hingga 15 9, kemudian ketidaktepatan. Sebagai contoh, 2.675adalah 2.67499999999999982236431605997495353221893310546875. Menambahkan 1e-15 akan memberi tip lebih dari 2,675 dan membulatkannya dengan benar. jika fraksi sudah melebihi konstanta kode, menambahkan 1e-15 tidak akan mengubah apa pun ke pembulatan.
Benoit Dufresne
1

Beberapa kasus:

in: Decimal(75.29 / 2).quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)
in: round(75.29 / 2, 2)
out: 37.65 GOOD

in: Decimal(85.55 / 2).quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)
in: round(85.55 / 2, 2)
out: 42.77 BAD

Untuk memperbaiki:

in: round(75.29 / 2 + 0.00001, 2)
out: 37.65 GOOD
in: round(85.55 / 2 + 0.00001, 2)
out: 42.78 GOOD

Jika Anda ingin lebih banyak desimal, misalnya 4, Anda harus menambahkan (+ 0,0000001).

Bekerja untukku.

Virako
sumber
Ini adalah satu-satunya solusi yang bekerja untuk saya, terima kasih telah memposting. Semua orang tampaknya bertekad untuk membulatkan ke atas / bawah, jadi saya tidak bisa mengelola masalah pembulatan multi desimal.
Gayathri
-1

Reproduksi sampel:

['{} => {}'.format(x+0.5, round(x+0.5)) for x in range(10)]

['0.5 => 0', '1.5 => 2', '2.5 => 2', '3.5 => 4', '4.5 => 4', '5.5 => 6', '6.5 => 6', '7.5 => 8', '8.5 => 8', '9.5 => 10']

API: https://docs.python.org/3/library/functions.html#round

Menyatakan:

Kembalikan angka yang dibulatkan ke presisi angka setelah titik desimal. Jika ndigits dihilangkan atau Tidak, itu mengembalikan integer terdekat ke inputnya.

Untuk tipe bawaan yang mendukung round (), nilai dibulatkan ke kelipatan terdekat dari 10 ke power dikurangi ndigits; jika dua kelipatan sama-sama dekat, pembulatan dilakukan menuju pilihan genap (jadi, misalnya, putaran (0,5) dan putaran (-0,5) adalah 0, dan putaran (1,5) adalah 2). Nilai integer apa pun valid untuk ndigits (positif, nol, atau negatif). Nilai kembali adalah bilangan bulat jika ndigits dihilangkan atau Tidak ada. Kalau tidak, nilai pengembalian memiliki jenis yang sama dengan angka.

Untuk nomor objek Python umum, bulatkan delegasi ke angka. bulat .

Catatan Perilaku round () untuk floats bisa mengejutkan: misalnya, round (2.675, 2) memberi 2.67 bukannya 2.68 yang diharapkan. Ini bukan bug: ini adalah hasil dari fakta bahwa sebagian besar pecahan desimal tidak dapat diwakili persis seperti pelampung. Lihat Aritmatika Titik Apung: Masalah dan Batasan untuk informasi lebih lanjut.

Dengan wawasan ini, Anda dapat menggunakan beberapa matematika untuk menyelesaikannya

import math
def my_round(i):
  f = math.floor(i)
  return f if i - f < 0.5 else f+1

sekarang Anda dapat menjalankan tes yang sama dengan my_round, bukan ronde.

['{} => {}'.format(x + 0.5, my_round(x+0.5)) for x in range(10)]
['0.5 => 1', '1.5 => 2', '2.5 => 3', '3.5 => 4', '4.5 => 5', '5.5 => 6', '6.5 => 7', '7.5 => 8', '8.5 => 9', '9.5 => 10']
Fallenreaper
sumber
-2

Cara termudah untuk membulatkan dalam Python 3.x seperti yang diajarkan di sekolah adalah menggunakan variabel bantu:

n = 0.1 
round(2.5 + n)

Dan ini akan menjadi hasil dari seri 2.0 hingga 3.0 (dalam 0,1 langkah):

>>> round(2 + n)
>>> 2

>>> round(2.1 + n)
>>> 2

>>> round(2.2 + n)
>>> 2

>>> round(2.3 + n)
>>> 2

>>> round(2.4 + n)
>>> 2

>>> round(2.5 + n)
>>> 3

>>> round(2.6 + n)
>>> 3

>>> round(2.7 + n)
>>> 3

>>> round(2.8 + n)
>>> 3

>>> round(2.9 + n)
>>> 3

>>> round(3 + n)
>>> 3
baermathias
sumber
-2

Anda dapat mengontrol pembulatan Anda menggunakan modul math.ceil:

import math
print(math.ceil(2.5))
> 3
Eds_k
sumber
Itu akan selalu mengembalikan nomor tanpa bagian desimalnya, ini bukan pembulatan. ceil (2.5) = 2, ceil (2.99) = 2
krafter
1
di python3 +, Jika argumen bilangan adalah bilangan positif atau negatif, fungsi ceil mengembalikan nilai plafon.
Eds_k
Dalam [14]: math.ceil (2.99) Keluar [14]: 3
Eds_k
Ya, maaf saya salah. Ceil () mengembalikan nilai plafon sedangkan floor () mengembalikan nilai yang saya bicarakan. Tapi tetap saja, menurut saya ini bukan perilaku pembulatan (kedua fungsi ini)
krafter
-4

Coba kode ini:

def roundup(input):   
   demo = input  if str(input)[-1] != "5" else str(input).replace("5","6")
   place = len(demo.split(".")[1])-1
   return(round(float(demo),place))

Hasilnya adalah:

>>> x = roundup(2.5)
>>> x
3.0  
>>> x = roundup(2.05)
>>> x
2.1 
>>> x = roundup(2.005)
>>> x
2.01 

Ooutput Anda dapat memeriksa di sini: https://i.stack.imgur.com/QQUkS.png

Anil Kumar Sahoo
sumber