Обычная практика как бороться с целочисленными переполнениями?

какова обычная практика борьбы с целочисленными переполнениями, такими как 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.