문제

나는 부동 소수점 번호 (Java Doubles)가 많으며 대부분은 1에 매우 가깝고 더 큰 계산의 일부로 함께 곱해야합니다. 나는 이것을해야한다 많이.

문제는 Java 복식이 다음과 같은 숫자에 아무런 문제가 없다는 것입니다.

0.0000000000000000000000000000000001 (1.0E-34)

그들은 다음과 같은 것을 대표 할 수 없습니다.

1.0000000000000000000000000000000001

결과적으로 나는 정밀도를 빠르게 잃게됩니다 (한도는 Java의 복식의 경우 약 1.000000000000001 인 것 같습니다).

나는 단지 1 차수로 숫자를 저장하는 것을 고려 했으므로 예를 들어 1.0001은 0.0001로 저장 될 것입니다. 그러나 문제는 다시 그것들을 다시 곱하면 1을 추가해야한다는 것입니다.

이 문제를 해결하기 위해 BigDecimals를 사용하여 계산을 수행 할 수 있습니다 (BigDecimal으로 변환하고 1.0을 추가 한 다음 곱한 다음 곱한 다음) 이후로 다시 변환 할 수 있지만, 이의 성능 영향에 대해 심각한 우려가 있습니다.

누구든지 bigdecimal을 사용하지 않는 방법을 볼 수 있습니까?

명확성을 위해 편집하십시오: 이것은 대규모 협업 필터를위한 것입니다.이 필터는 그라디언트 하강 최적화 알고리즘을 사용합니다. 공동 작업 필터가 매우 적은 숫자를 다루기 때문에 정확도는 문제입니다 (예 : 사람이 제품에 대한 광고를 클릭 할 확률, 1000에서 1 또는 10000에서 1 일 수 있음).

공동 작업 필터를 수천만 건의 데이터 포인트에 대해 훈련해야하기 때문에 속도는 문제입니다.

도움이 되었습니까?

해결책

예 : 왜냐하면

(1 + x) * (1 + y) = 1 + x + y + x*y

당신의 경우, x 그리고 y 매우 작습니다 x*y 될 것입니다 멀리 더 작게 - 계산 결과에 영향을 미치기에는 너무 작습니다. 당신이 염려하는 한

(1 + x) * (1 + y) = 1 + x + y

즉, 숫자를 1 차례로 저장하고 곱하는 대신 숫자를 추가 할 수 있습니다. 결과가 항상 1 미만인 한, 차이에 신경 쓰지 않는 수학적으로 정확한 결과에 충분히 가까워 질 것입니다.

편집하다: 방금 알아 차렸다 : 당신은 말한다 대부분 그중 1에 매우 가깝습니다. 분명히이 기술은 1에 가깝지 않은 숫자에 대해서는 효과가 없습니다. x 그리고 y 크다. 그러나 하나가 크고 작다면 여전히 효과가있을 수 있습니다. 당신은 제품의 크기에만 관심이 있습니다 x*y. (그리고 두 숫자가 모두 1에 가깝지 않으면 일반 Java 만 사용할 수 있습니다. double 곱셈...)

다른 팁

아마도 로그를 사용할 수 있습니까?

로그는 편리하게 곱셈을 추가로 줄입니다.

또한, 초기 정밀 손실을 처리하기 위해, 함수 log1p (적어도 C/C ++에 존재 함)가 있으며, 이는 정밀 손실없이 log (1+x)를 반환합니다. (예 : log1p (1e-30) 나를 위해 1e-30을 반환합니다)

그런 다음 ExpM1을 사용하여 실제 결과의 소수 부분을 얻을 수 있습니다.

이런 종류의 상황이 BigDecimal의 상황이 정확하지 않습니까?

추가하기 위해 편집 :

"두 번째 라이트 단락에 따라, 가능한 경우 가능한 경우 큰 비도분을 피하고 싶습니다." - 정신

"조기 최적화는 모든 악의 근원입니다"-Knuth

문제를 주문하기 위해 실제로 만들어진 간단한 솔루션이 있습니다. 당신은 그것이 충분히 빠르지 않을 것이라고 걱정하고 있기 때문에 당신은 당신이 복잡한 일을하고 싶습니다. 생각한다 더 빨라질 것입니다. Knuth 인용문은 때때로 과도하게 사용되지만 이것은 그가 경고 한 상황입니다. 간단한 방법으로 작성하십시오. 테스트하십시오. 프로파일. 너무 느립니다. 그렇다면 그 다음에 더 빨리 만드는 방법에 대해 생각하기 시작하십시오. 필요하다는 것을 알 때 까지이 추가 복잡한 버그가 발생한 코드를 추가하지 마십시오.

숫자의 출처와 사용 방법에 따라 플로트 대신 합리를 사용하고 싶을 수도 있습니다. 모든 경우에 대한 정답은 아니지만 ~이다 정답은 실제로 다른 사람이 없습니다.

합리가 맞지 않으면 로그 대답을 보증합니다.

편집에 대한 응답으로 편집 :

낮은 응답률을 나타내는 숫자를 다루고 있다면 과학자들이하는 일을하십시오.

  • 초과 / 부족으로 표현하십시오 (1.0 부분을 정규화)
  • 그들을 확장하십시오. "백만당 부품"또는 적절한 것이 무엇이든 생각하십시오.

이렇게하면 계산을 위해 합리적인 숫자를 다루게됩니다.

Java가 아닌 하드웨어의 한계를 테스트하고 있다는 점은 주목할 가치가 있습니다. Java는 CPU에서 64 비트 부동 소수점을 사용합니다.

나는 당신이 충분히 빠르지 않다고 가정하기 전에 Bigdecimal의 성능을 테스트하는 것이 좋습니다. BigDecimal으로 초당 수만 건의 계산을 계속할 수 있습니다.

David가 지적했듯이 오프셋을 추가 할 수 있습니다.

(1 + x) * (1 + y) = 1 + x + y + x * y

그러나 마지막 학기를 중퇴하기로 선택하는 것은 위험한 것 같습니다. 하지 않다. 예를 들어, 이것을 시도하십시오.

x = 1e-8 y = 2e-6 z = 3e-7 w = 4e-5

뭐야 (1+x)(1+y)(1+z)*(1+W)? 이중 정밀하게는 다음과 같습니다.

(1+x)(1+y)(1+z)*(1+W)

ans =

      1.00004231009302

그러나 간단한 부가 근사치를 수행하면 어떻게되는지 확인하십시오.

1+(x+y+z+w)

ans =

            1.00004231

우리는 중요했던 저 순서 비트를 잃었습니다. 제품의 1 차이 중 일부가 최소한 SQRT (EPS) 인 경우 EPS가 작업하는 정밀도 인 경우에만 문제가됩니다.

대신 시도해보십시오.

f = @(u, v) u + v + u*v;

결과 = f (x, y);

결과 = f (결과, z);

결과 = f (결과, w);

1+결과

ans =

      1.00004231009302

보시다시피, 이것은 우리를 이중 정밀 결과로 되돌립니다. 실제로 결과의 내부 값이 4.23100930230249E-05이기 때문에 조금 더 정확합니다.

정밀도가 실제로 필요한 경우 Bigdecimal과 같은 것을 사용해야합니다.

정밀도가 필요하지 않으면 다윗의 대답을 가지고 갈 수 있습니다. 그러나 곱셈을 많이 사용하더라도 조기 최적화 일 수 있으므로 Bigdecimal은 어쨌든 갈 수있는 방법 일 수 있습니다.

"대부분은 1에 매우 가깝습니다"라고 말할 때 정확히 몇 명입니까?

어쩌면 당신은 모든 숫자에서 1의 암시 적 오프셋을 가질 수 있고 분수와 함께 일할 수 있습니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top