Putaran python ke kekuatan tertinggi berikutnya 10

44

Bagaimana saya bisa melakukan math.ceilsehingga suatu nomor ditugaskan ke kekuatan tertinggi berikutnya 10?

# 0.04  ->  0.1
# 0.7   ->  1
# 1.1   ->  10  
# 90    ->  100  
# ...

Solusi saya saat ini adalah kamus yang memeriksa kisaran nomor input, tetapi hardcoded dan saya lebih suka solusi satu-liner. Mungkin saya kehilangan trik matematika sederhana atau fungsi numpy yang sesuai di sini?

offeltoffel
sumber
3
@Bold sepertinya solusi tersebut bekerja dari 10atas, ini akan membutuhkan sesuatu dengan misalnya log10.
jonrsharpe
3
Kata yang Anda inginkan adalah "kekuatan". Mungkin Anda salah terjemahan untuk kata bahasa asli Anda untuk itu.
user2357112 mendukung Monica
Terima kasih, Monica! @ tebal: Saya menemukan pertanyaan ini, tetapi ini masalah yang berbeda. Jonrsharpe telah memberikan jawaban yang sempurna
offeltoffel
2
Ini juga terkait dengan urutan besarnya . 1 adalah urutan ke-0, urutan ke-10, ke-urutan ke-2, dll.
wjandrea

Jawaban:

60

Anda dapat menggunakan math.ceildengan math.log10melakukan hal ini:

>>> 10 ** math.ceil(math.log10(0.04))
0.1
>>> 10 ** math.ceil(math.log10(0.7))
1
>>> 10 ** math.ceil(math.log10(1.1))
10
>>> 10 ** math.ceil(math.log10(90))
100

log10(n)memberi Anda solusi xyang memuaskan 10 ** x == n, jadi jika Anda mengumpulkannya xmemberi Anda eksponen untuk kekuatan tertinggi berikutnya 10.

Catatan bahwa untuk nilai ndi mana xsudah merupakan integer, "kekuasaan tertinggi berikutnya 10" akan n:

>>> 10 ** math.ceil(math.log10(0.1))
0.1
>>> 10 ** math.ceil(math.log10(1))
1
>>> 10 ** math.ceil(math.log10(10))
10
Jonrsharpe
sumber
1
Bekerja dengan fungsi log sepertinya hanya trik yang tidak bisa saya lakukan. Saya percaya ini persis apa yang saya harapkan! Terima kasih banyak
offeltoffel
2
NB: tergantung pada perilaku yang Anda inginkan, ini tidak bekerja untuk kekuatan 10, misalnya 10 ** math.ceil(math.log10(1)) == 1, yang bukan "kekuatan tertinggi berikutnya"
Cireo
5
Catatan: jawaban ini bergantung pada aritmatika floating point dan karena itu bisa gagal karena kesalahan pembulatan. Coba beri makan di 1000000000000001 misalnya.
plugwash
2
@plugwash belum tentu, fungsi matematika juga akan menerima mis desimal.
jonrsharpe
5
Ya, Anda dapat melewati tipe lain, tetapi mereka akan dikonversi ke angka floating point presisi ganda dan diteruskan ke fungsi C "log10". Ada kasus khusus untuk mencegah log jumlah besar meluap, tetapi tidak ada yang mencegah kesalahan pembulatan.
plugwash
21

Masalah Anda tidak ditentukan, Anda harus mundur dan mengajukan beberapa pertanyaan.

  • Apa jenis input Anda?
  • Jenis apa yang Anda inginkan untuk output Anda?
  • Untuk hasil yang kurang dari 1, apa yang ingin Anda lakukan? Apakah Anda ingin kekuatan aktual 10 atau aproksimasi kekuatan 10 poin mengambang? Anda sadar bahwa kekuatan negatif 10 tidak dapat diekspresikan dengan tepat di floating point, bukan? Mari kita asumsikan untuk sekarang bahwa Anda ingin perkiraan floating point dari kekuatan 10.
  • Jika inputnya tepat dengan daya 10 (atau perkiraan titik mengambang terdekat dengan daya 10), haruskah outputnya sama dengan inputnya? Atau haruskah itu kekuatan 10 up berikutnya? "10 -> 10" atau "10 -> 100"? Mari kita asumsikan yang pertama untuk saat ini.
  • Bisakah nilai input Anda menjadi nilai yang mungkin dari jenis yang dimaksud? atau mereka lebih dibatasi.

Di jawaban lain diusulkan untuk mengambil logaritma, kemudian mengumpulkan (fungsi langit-langit), kemudian eksponensial.

def nextpow10(n):
    return 10 ** math.ceil(math.log10(n))

Sayangnya ini mengalami kesalahan pembulatan. Pertama-tama n dikonversi dari tipe data apa pun yang kebetulan terjadi menjadi angka floating point presisi ganda, yang berpotensi menimbulkan kesalahan pembulatan, kemudian logaritma dihitung berpotensi menimbulkan lebih banyak kesalahan pembulatan baik dalam perhitungan internal maupun dalam hasilnya.

Karena itu tidak butuh waktu lama bagi saya untuk menemukan contoh yang memberikan hasil yang salah.

>>> import math
>>> from numpy import nextafter
>>> n = 1
>>> while (10 ** math.ceil(math.log10(nextafter(n,math.inf)))) > n:
...     n *= 10
... 
>>> n
10
>>> nextafter(n,math.inf)
10.000000000000002
>>> 10 ** math.ceil(math.log10(10.000000000000002))
10

Secara teori juga mungkin untuk gagal ke arah lain, meskipun ini tampaknya jauh lebih sulit untuk diprovokasi.

Jadi untuk solusi yang kuat untuk float dan int kita perlu mengasumsikan bahwa nilai logaritma kita hanya perkiraan, dan karena itu kita harus menguji beberapa kemungkinan. Sesuatu di sepanjang garis

def nextpow10(n):
    p = round(math.log10(n))
    r = 10 ** p
    if r < n:
        r = 10 ** (p+1) 
    return r;

Saya percaya kode ini harus memberikan hasil yang benar untuk semua argumen dalam jangkauan dunia nyata yang masuk akal. Ini akan pecah untuk jumlah yang sangat kecil atau sangat besar dari tipe non integer dan non-floating point karena masalah mengubahnya menjadi floating point. Argumen integer kasus khusus Python untuk fungsi log10 dalam upaya untuk mencegah overflow, tetapi masih dengan integer yang cukup besar mungkin dapat memaksa hasil yang salah karena kesalahan pembulatan.

Untuk menguji dua implementasi saya menggunakan program tes berikut.

n = -323 # 10**-324 == 0
while n < 1000:
    v = 10 ** n
    if v != nextpow10(v): print(str(v)+" bad")
    try:
        v = min(nextafter(v,math.inf),v+1)
    except:
        v += 1
    if v > nextpow10(v): print(str(v)+" bad")
    n += 1

Ini menemukan banyak kegagalan dalam implementasi yang naif, tetapi tidak ada dalam implementasi yang ditingkatkan.

plugwash
sumber
Terima kasih atas upaya Anda untuk lebih mendetail di sini. Sementara jawaban jonrsharpe telah memecahkan masalah saya, jawaban ini mungkin berguna bagi orang lain dengan pertanyaan serupa tetapi lebih khusus.
offeltoffel
1
Mengapa Anda menggunakan roundbukan math.ceil? Ini akan memperkenalkan banyak kasus yang tidak perlu di tempat r < nyang benar sehingga perlu melakukan pekerjaan tambahan.
a_guest
1
Karena log bisa mati di kedua arah.
plugwash
1
menggunakan "membaik" kode tapi dengan putaran digantikan oleh hasil math.ceil kegagalan untuk 1e-317 pada akhir rendah dan 100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 pada high end.
plugwash
1
(dalam prakteknya itu mungkin baik-baik saja)
plugwash
3

Tampaknya Anda menginginkan kekuatan 10 terendah berikutnya ... Berikut adalah cara menggunakan matematika murni dan tidak ada log, tetapi rekursi.

def ceiling10(x):
    if (x > 10):
        return ceiling10(x / 10) * 10
    else:
        if (x <= 1):
            return ceiling10(10 * x) / 10
        else:
            return 10
for x in [1 / 1235, 0.5, 1, 3, 10, 125, 12345]:
    print(x, ceiling10(x))
Dupvuis Silvain
sumber
Baru saja menguji yang ini, saya memberikannya upvote karena tampaknya berfungsi dengan baik dalam kebanyakan kasus praktis, tetapi tampaknya menderita kesalahan pembulatan dengan input yang cukup kecil. ceiling10 (1e-6) memberikan 1,0000000000000002e-06
plugwash
0
y = math.ceil(x)
z = y + (10 - (y % 10))

Mungkin sesuatu seperti ini? Itu hanya dari atas kepala saya tetapi berhasil ketika saya mencoba beberapa angka di terminal.

Lugene
sumber
0

Lihat ini!

>>> i = 0.04123; print i, 10 ** len( str( int( i ) ) ) if int( i ) > 1  else 10 if i > 1.0 else 1 if i > 0.1 else  10 ** ( 1 - min( [ ("%.100f" % i ).replace('.','').index( k ) for k in [ str( j ) for j in xrange( 1, 10 ) if str( j ) in "%.100f" % i  ] ]  ) )               
0.04123 0.1
>>> i = 0.712; print i, 10 ** len( str( int( i ) ) ) if int( i ) > 1  else 10 if i > 1.0 else 1 if i > 0.1 else  10 ** ( 1 - min( [ ("%.100f" % i ).replace('.','').index( k ) for k in [ str( j ) for j in xrange( 1, 10 ) if str( j ) in "%.100f" % i  ] ]  ) )                 
0.712 1
>>> i = 1.1; print i, 10 ** len( str( int( i ) ) ) if int( i ) > 1  else 10 if i > 1.0 else 1 if i > 0.1 else  10 ** ( 1 - min( [ ("%.100f" % i ).replace('.','').index( k ) for k in [ str( j ) for j in xrange( 1, 10 ) if str( j ) in "%.100f" % i  ] ]  ) )                   
1.1 10
>>> i = 90; print i, 10 ** len( str( int( i ) ) ) if int( i ) > 1  else 10 if i > 1.0 else 1 if i > 0.1 else  10 ** ( 1 - min( [ ("%.100f" % i ).replace('.','').index( k ) for k in [ str( j ) for j in xrange( 1, 10 ) if str( j ) in "%.100f" % i  ] ]  ) )                    
90 100

Kode ini berdasarkan prinsip kekuatan sepuluh di len( str( int( float_number ) ) ).

Ada 4 kasus:

    1. int( i ) > 1.

    FloatNomor - dikonversi ke int, setelah tali str()dari itu, akan memberi kita stringdengan lengthyang kita cari persis. Jadi, bagian pertama, untuk input i > 1.0- itu adalah sepuluh 10kekuatan sepanjang ini.

    1. & 3. Sedikit bercabang: i > 1.0dan i > 0.1<=> itu 10dan 1masing - masing.
    1. Dan kasus terakhir, ketika i < 0.1: Di sini, sepuluh akan berada dalam kekuatan negatif. Untuk mendapatkan elemen bukan nol pertama setelah koma, saya telah menggunakan konstruksi seperti itu ("%.100f" % i ).replace('.','').index( k ), di mana k dijalankan dengan [1:10]interval. Setelah itu, ambil minimal daftar hasil. Dan berkurang satu, itu nol pertama, yang akan dihitung. Juga, di sini standar python ini index()mungkin macet, jika tidak akan menemukan setidaknya satu dari non zero-elemen dari [1:10]selang, itu sebabnya pada akhirnya saya harus "filter" daftar oleh terjadinya: if str( j ) in "%.100f" % i. Selain itu, untuk mendapatkan ketelitian yang lebih dalam - %.100fdapat dianggap berbeda.
Marshmello123123123
sumber
Tolong tambahkan beberapa penjelasan.
Mobin Ranjbar