Обычная практика как бороться с целочисленными переполнениями?
какова обычная практика борьбы с целочисленными переполнениями, такими как 999999*999999 (результат > целое число.Массив) с Команда Разработчиков Приложений точки зрения?
можно сделать BigInt
обязательно и запретить использование Integer
, но это хорошая/плохая идея?
4 ответов
если чрезвычайно важно, чтобы целое число не переполнялось, вы можете определить свои собственные операции перелива, например:
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
}
вы можете использовать шаблон enrich-your-library, чтобы превратить его в операторы; если JVM удастся правильно выполнить анализ escape, вы не получите слишком много штрафа за это:
class SafePlusInt(i: Int) {
def +?+(j: Int) = { /* as before, except without i param */ }
}
implicit def int_can_be_safe(i: Int) = new SafePlusInt(i)
например:
scala> 1000000000 +?+ 1000000000
res0: Int = 2000000000
scala> 2000000000 +?+ 2000000000
java.lang.ArithmeticException: Int out of bounds
at SafePlusInt.$plus$qmark$plus(<console>:12)
...
если это не очень важно, затем стандартное модульное тестирование и обзоры кода и такие должны поймать проблему в подавляющем большинстве случаев. Используя BigInt
возможно, но замедлит вашу арифметику в 100 раз или около того, и не поможет вам, когда вам нужно использовать существующий метод, который принимает Int
.
если вы используете Scala (и на основе тега, который я предполагаю, что вы), одно очень общее решение-написать код библиотеки против scala.math.Integral
тип класса:
def naturals[A](implicit f: Integral[A]) =
Stream.iterate(f.one)(f.plus(_, f.one))
вы также можете использовать границы контекста и Integral.Implicits
для лучшего синтаксиса:
import scala.math.Integral.Implicits._
def squares[A: Integral] = naturals.map(n => n * n)
вы можете использовать эти методы с Int
или Long
или BigInt
по мере необходимости, так как экземпляры Integral
существуют для всех них:
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)
отсутствие потребности изменить код библиотеки: просто используйте Long
или BigInt
где переполнение является проблемой и Int
иначе.
вы заплатите некоторый штраф с точки зрения производительности, но универсальность и способность отложить Int
илиBigInt
решение может быть стоит.
наиболее общие практика в отношении целочисленных переполнений заключается в том, что программисты должны знать, что проблема существует, следить за случаями, когда они могут произойти, и делать соответствующие проверки или переставлять математику так, чтобы переполнения не происходили, такие вещи, как выполнение a * (b / c), а не (a * b) / c . Если проект использует модульный тест,они будут включать случаи, чтобы попытаться принудительно переполнить.
Я никогда не работал или не видел код из команды это потребовало больше, чем это, поэтому я собираюсь сказать, что этого достаточно для почти всего программного обеспечения.
одно встроенное приложение, которое я видел, что на самом деле, честный спагетти-монстр должен был предотвратить переполнения, они сделали это, доказав, что переполнения не были возможны в каждой строке, где это выглядело так, как будто они могут произойти.
в дополнение к простой внимательности, как отметил @mjfgates, есть несколько практик, которые я всегда использую при работе с масштабированными десятичными (без плавающей точки) величинами реального мира. Это может быть не в точку для вашего конкретного приложения-извинения заранее, если нет.
во-первых, если используется несколько единиц измерения, значения всегда должны четко определять, что они такое. Это может быть соглашение об именах или использование отдельного класса для каждой единицы измерения. Я всегда просто использовал имена-суффикс для каждого имени переменной. В дополнение к устранению ошибки из-за неясности блоки, это побуждает думать о переполнении, потому что меры с меньшей вероятностью будут рассматриваться как просто числа.
во - вторых, моим наиболее частым источником переполнения обычно является масштабирование - преобразование из одной меры в другую-когда для этого требуется много значащих цифр. Например, коэффициент пересчета см в дюймах 0.393700787402. Чтобы избежать как переполнения, так и потери значимых цифр, нужно быть внимательным к умножению и делению в правильном порядке. Я не делал этого в течение длительного времени, но я считаю, что вы хотите что-то вроде:
добавить рациональное.скала!--8--> из книги:
def rescale(i:Int) : Int = {
(i * (numer/denom)) + (i/denom * (numer % denom))
затем вы получаете результаты (сокращенные от теста specs2):
val InchesToCm = new Rational(1000000000,393700787)
InchesToCm.rescale(393700787) must_== 1000000000
InchesToCm.rescale(1) must_== 2
это не округляет или не имеет дело с отрицательными коэффициентами масштабирования.
Производство реализация может захотеть factor out numer/denom
и numer % denom
.