2012-03-26 12 views

Odpowiedz

13

Jeśli jest to niezwykle ważne, że całkowita nie przepełnić, można zdefiniować własne przepełnienia wzrok operacji, np:

def +?+(i: Int, j: Int) = { 
    val ans = i.toLong + j.toLong 
    if (ans < Int.MinValue || ans > Int.MaxValue) { 
    throw new ArithmeticException("Int out of bounds") 
    } 
    ans.toInt 
} 

Możesz być w stanie korzystać z wzbogacać-Your-biblioteki wzór, aby włączyć ten do operatorów; jeśli JVM udaje się zrobić analizę ucieczki prawidłowo, nie będzie się zbytnio od kary za to:

class SafePlusInt(i: Int) { 
    def +?+(j: Int) = { /* as before, except without i param */ } 
} 
implicit def int_can_be_safe(i: Int) = new SafePlusInt(i) 

Na przykład:

scala> 1000000000 +?+ 1000000000 
res0: Int = 2000000000 

scala> 2000000000 +?+ 2000000000 
java.lang.ArithmeticException: Int out of bounds 
    at SafePlusInt.$plus$qmark$plus(<console>:12) 
    ... 

Jeśli nie jest niezwykle ważne, a następnie standardowe testy jednostkowe i przeglądy kodu i takie powinny w większości przypadków wychwycić problem. Korzystanie z BigInt jest możliwe, ale spowolni twoją arytmetykę o współczynnik około 100 i tak nie pomoże, gdy będziesz musiał użyć istniejącej metody, która wymaga Int.

+0

Cześć Rex, dziękuję za szybką odpowiedź! Rozsądne rozwiązanie, chociaż może wymagać ponownego fakturowania. Jak trudno jest powiedzieć, że manipuluje klasą podstawową Integer dodając flagę kontroli przepełnienia do standardowych funkcji "operatora", biorąc pod uwagę złożony system typów? – IODEV

+0

Btw, w odniesieniu do ""? + "", Czy istnieje standardowa konwersja nazw na Scalę, której należy użyć?/Dzięki z góry – IODEV

+0

@IODEV - Możesz dodać klasę wrapper, ale nie możesz sensownie manipulować klasą podstawową, ponieważ faktycznie mapuje ona do prymitywu 'int' w JVM i dlatego ma dużo specjalnej magii kompilatora. Wybór "?" Był przeze mnie arbitralny; zaczynając od '+', utrzymasz operatora na pierwszym miejscu i lubię symetrię (a inni robią wystarczająco dużo, aby chociaż nie nazwałbym tego "konwencją" jest przynajmniej znajomy), więc dodałem kolejne '+' na końcu . '+ @' działałoby równie dobrze. –

4

Zdecydowanie najbardziej wspólny praktyka dotycząca całkowitej przepełnienia jest to, że oczekuje się, że programiści wiedzieć, że problem istnieje, aby oglądać do przypadków, w których mogą się wydarzyć, i dokonać odpowiednich badań lub zmienić matematyki tak, że przelewa wygrał tak się dzieje, takie rzeczy jak robienie * (b/c) zamiast (a * b)/c. Jeśli projekt korzysta z testów jednostkowych, będą uwzględniać przypadki, w których będą wymuszane przepełnienia.

Nigdy nie pracowałem ani nie widziałem kodu z zespołu, który wymagał więcej niż to, więc powiem, że jest wystarczająco dobre dla prawie wszystkich programów.

Jedna wbudowana aplikacja, którą widziałem, naprawdę, szczery do spaghetti-potwora, POTRZEBNY, aby zapobiec przepełnieniu, zrobili to, udowadniając, że przepełnienia nie były możliwe w każdym wierszu, gdzie wyglądało na to, że mogą się zdarzyć.

+3

Niedomiar może być równie zły, co może dać ci "a * (b/c)". Powinieneś ogólnie zrobić '((a * b) .toLong/c) .toInt' lub odpowiednik z' Double'. –

+0

Podobnie jak w przypadku przepełnień, sposobem radzenia sobie z niedomiarami jest, aby programista był świadomy możliwości i używał swojego WYROK. Poszerzanie argumentów, a następnie zawężanie wyniku za każdym razem, gdy robisz matematykę, powoduje, że Złe rzeczy wpływają na wydajność i czytelność kodu, i nie działają w przypadkach, w których końcowy wynik jest zawyżony lub niedostateczny. – mjfgates

+0

Zależy od tego, jak ważny jest kod wydajności. Bardzo niska wydajność może zostać osiągnięta dzięki liczbom całkowitym o dowolnej dokładności. Przyzwoitej wydajności można uzyskać dzięki rozszerzeniu/zwężeniu. Wysoka wydajność wymaga od programisty zrozumienia, jakie dozwolone wartości są takie, że poszerzenie/zwężenie nie jest potrzebne. Ultra-wysoka wydajność byłaby najlepsza, gdybyś w jakiś sposób uniknął podziału. –

6

Jeśli używasz Scala (i na podstawie znacznika jestem zakładając, że jesteś), jedno rozwiązanie bardzo ogólne jest napisanie kodu biblioteki przeciwko klasie scala.math.Integral typu:

def naturals[A](implicit f: Integral[A]) = 
    Stream.iterate(f.one)(f.plus(_, f.one)) 

można również używać granic kontekstowych i Integral.Implicits dla ładniejszy składnią:

import scala.math.Integral.Implicits._ 

def squares[A: Integral] = naturals.map(n => n * n) 

teraz można korzystać z tych metod z obu Int lub Long lub BigInt co potrzebne, ponieważ wystąpień Integral istnieją dla nich wszystkich:

scala> squares[Int].take(10).toList 
res0: List[Int] = List(1, 4, 9, 16, 25, 36, 49, 64, 81, 100) 

scala> squares[Long].take(10).toList 
res0: List[Long] = List(1, 4, 9, 16, 25, 36, 49, 64, 81, 100) 

scala> squares[BigInt].take(10).toList 
res1: List[BigInt] = List(1, 4, 9, 16, 25, 36, 49, 64, 81, 100) 

Nie ma potrzeby, aby zmienić kod biblioteki: wystarczy użyć Long lub BigInt gdzie przelewowy jest problemem i Int inaczej.

Zostanie nałożona pewna kara pod względem wydajności, ale ogólna charakterystyka i możliwość odroczenia decyzji o wartości Int -lub- BigInt może być tego warta.

+0

używając 'Long' mamy problem z przepełnieniem. – Jus12

3

Oprócz zwykłej uważności, jak zauważyłem przez @mjfgates, istnieje kilka praktyk, z których zawsze korzystam, gdy mamy do czynienia ze skalowanymi dziesiętnymi (nie zmiennoprzecinkowymi) liczbami rzeczywistymi. Może to nie być punkt dla konkretnego zastosowania - przepraszam z góry, jeśli nie.

Po pierwsze, jeśli w użyciu jest wiele jednostek miar, wartości muszą zawsze jednoznacznie określać, czym one są. Może to być nazywanie konwencji lub użycie oddzielnej klasy dla każdej jednostki miary. Zawsze używałam nazw - sufiksu na każdej nazwie zmiennej. Oprócz eliminacji errors from confusion over the units, zachęca do myślenia o przepełnieniu, ponieważ środki są mniej prawdopodobne, aby traktować je jako liczby.

Po drugie, moim najczęstszym źródłem obaw związanych z przepełnieniem jest zwykle przeskalowanie - przejście z jednej miary na drugą - gdy wymaga dużej liczby cyfr. Na przykład współczynnik konwersji od cm do cali wynosi 0.393700787402. Aby uniknąć zarówno przepełnienia, jak i utraty znaczących cyfr, musisz uważać, aby pomnożyć i podzielić w odpowiedniej kolejności. Nie robiłem tego od dłuższego czasu, ale wierzę, że to, co chcesz, jest coś takiego:

Dodaj do Rational.scala, z książki:

def rescale(i:Int) : Int = { 
     (i * (numer/denom)) + (i/denom * (numer % denom)) 

Następnie pojawi się jako wyniki (skrócona z specs2 test):

val InchesToCm = new Rational(1000000000,393700787) 
    InchesToCm.rescale(393700787) must_== 1000000000 
    InchesToCm.rescale(1) must_== 2 

To nie zaokrągla ani nie ma ujemnych współczynników skalowania. Wdrożenie produkcji może chcieć uwzględnić wartości numer/denom i numer % denom.

+0

+1 za przywiązanie jednostki miary do zmiennej. Posiadanie kodu krzyczy na mnie, gdybym próbował połączyć "liczbę znaków" i "liczbę bajtów" uratowało mi bekon jednego roku, kiedy miałem do czynienia ze stertą przerażających konwersji DBCS-Unicode. – mjfgates

Powiązane problemy