현재 다음과 같은 코드를 작성하고 있습니다.
double a = SomeCalculation1();
double b = SomeCalculation2();
if (a < b)
DoSomething2();
else if (a > b)
DoSomething3();
그리고 다른 곳에서는 평등을해야 할 수도 있습니다.
double a = SomeCalculation3();
double b = SomeCalculation4();
if (a == 0.0)
DoSomethingUseful(1 / a);
if (b == 0.0)
return 0; // or something else here
요컨대, 많은 부동 소수점 수학이 진행되고 있으며 조건에 대한 다양한 비교를 수행해야합니다. 이 문맥에서 그런 것은 의미가 없기 때문에 정수 수학으로 변환 할 수 없습니다.
나는 다음과 같은 일이 계속 될 수 있기 때문에 부동 소수점 비교가 신뢰할 수 없다는 것을 전에 읽었습니다.
double a = 1.0 / 3.0;
double b = a + a + a;
if ((3 * a) != b)
Console.WriteLine("Oh no!");
간단히 말해서 알고 싶습니다. 부동 소수점 수 (보다 작음,보다 큼, 같음)를 어떻게 안정적으로 비교할 수 있습니까?
내가 사용하는 숫자 범위는 대략 10E-14에서 10E6까지이므로 큰 숫자뿐만 아니라 작은 숫자로도 작업해야합니다.
내가 사용하는 언어에 관계없이이를 수행 할 수있는 방법에 관심이 있기 때문에 이것을 언어 불가지론 자로 태그 지정했습니다.
답변
float / double precision 한계의 가장자리에서 바로 작업하지 않는 한 크거나 작은 비교는 실제로 문제가되지 않습니다.
“퍼지 같음”비교의 경우,이 (자바 코드는 쉽게 적용 할 수 있어야 함) 많은 작업과 많은 비판을 고려한 후 부동 소수점 가이드 를 위해 생각해 낸 것입니다.
public static boolean nearlyEqual(float a, float b, float epsilon) {
final float absA = Math.abs(a);
final float absB = Math.abs(b);
final float diff = Math.abs(a - b);
if (a == b) { // shortcut, handles infinities
return true;
} else if (a == 0 || b == 0 || diff < Float.MIN_NORMAL) {
// a or b is zero or both are extremely close to it
// relative error is less meaningful here
return diff < (epsilon * Float.MIN_NORMAL);
} else { // use relative error
return diff / (absA + absB) < epsilon;
}
}
테스트 스위트와 함께 제공됩니다. 하나의 값 0, 0에 반대되는 두 개의 매우 작은 값 또는 무한대를 갖는 것과 같은 일부 엣지 케이스에서 사실상 실패가 보장되기 때문에 그렇지 않은 솔루션은 즉시 무시해야합니다.
대안 (자세한 내용은 위의 링크 참조)은 float의 비트 패턴을 정수로 변환하고 고정 된 정수 거리 내의 모든 것을 받아들이는 것입니다.
어쨌든 모든 응용 프로그램에 완벽한 솔루션은 없을 것입니다. 이상적으로는 실제 사용 사례를 다루는 테스트 스위트를 사용하여 직접 개발 / 적응하는 것이 좋습니다.
답변
TL; DR
- 현재 허용되는 솔루션 대신 다음 기능을 사용하여 특정 제한 상황에서 바람직하지 않은 결과를 방지하면서 잠재적으로 더 효율적입니다.
- 숫자에 대해 예상되는 부정확성을 알고 그에 따라 비교 함수에 입력하십시오.
bool nearly_equal(
float a, float b,
float epsilon = 128 * FLT_EPSILON, float relth = FLT_MIN)
// those defaults are arbitrary and could be removed
{
assert(std::numeric_limits<float>::epsilon() <= epsilon);
assert(epsilon < 1.f);
if (a == b) return true;
auto diff = std::abs(a-b);
auto norm = std::min((std::abs(a) + std::abs(b)), std::numeric_limits<float>::max());
// or even faster: std::min(std::abs(a + b), std::numeric_limits<float>::max());
// keeping this commented out until I update figures below
return diff < std::max(relth, epsilon * norm);
}
그래픽, 제발?
부동 소수점 수를 비교할 때 두 가지 “모드”가 있습니다.
첫 번째는이다 상대적인 차이 모드 x
와는 y
그 진폭이 상대적으로 간주된다 |x| + |y|
. 2D로 플롯하면 다음 프로필이 제공됩니다. 여기서 녹색은 x
및 y
. (나는 epsilon
예시를 위해 0.5를 취했다 ).
상대 모드는 “일반”또는 “충분히 큰”부동 소수점 값에 사용되는 것입니다. (나중에 더 자세히 설명합니다).
두 번째는 절대 모드로, 단순히 그 차이를 고정 된 숫자와 비교할 때입니다. 다음 프로필을 제공합니다 (다시 설명을 위해 epsilon
0.5와 relth
1).
이 절대 비교 모드는 “작은”부동 소수점 값에 사용됩니다.
이제 문제는 우리가이 두 가지 응답 패턴을 어떻게 연결 하는가입니다.
Michael Borgwardt의 답변에서 스위치는의 값을 기반으로하며 ( 그의 답변에서) diff
아래에 있어야합니다 . 이 스위치 영역은 아래 그래프에서 빗금으로 표시됩니다.relth
Float.MIN_NORMAL
때문에 relth * epsilon
것이 작은 relth
녹색 패치가 차례로 솔루션을 나쁜 속성을 제공하는, 단결하지 않는다 : 우리는 숫자 같은 그 세 쌍둥이 찾을 수 있습니다 x < y_1 < y_2
아직 x == y2
하지만를 x != y1
.
이 놀라운 예를 들어보십시오.
x = 4.9303807e-32
y1 = 4.930381e-32
y2 = 4.9309825e-32
우리는 가지고 있으며 x < y1 < y2
, 실제로 y2 - x
는보다 2000 배 이상 큽니다 y1 - x
. 그러나 현재 솔루션을 사용하면
nearlyEqual(x, y1, 1e-4) == False
nearlyEqual(x, y2, 1e-4) == True
대조적으로, 위에서 제안 된 솔루션에서 스위치 영역은 값을 기반으로하며 |x| + |y|
아래에 빗금 친 사각형으로 표시됩니다. 두 영역이 모두 정상적으로 연결되도록합니다.
또한 위의 코드에는 분기가 없으므로 더 효율적일 수 있습니다. 그와 같은 작업을 고려 max
하고 abs
, 사전 종종 전용 조립 지침을 가지고, 필요 분기를. 이러한 이유로이 접근 방식은 nearlyEqual
스위치를에서 diff < relth
로 변경하여 Michael의 문제를 해결하는 다른 솔루션보다 우수하다고 생각합니다. diff < eps * relth
그러면 기본적으로 동일한 응답 패턴이 생성됩니다.
상대 비교와 절대 비교간에 전환 할 위치는 어디입니까?
이러한 모드 사이의 전환 은 허용되는 답변 relth
에서와 같이 사용 FLT_MIN
됩니다. 이 선택은의 표현이 float32
부동 소수점 숫자의 정밀도를 제한 한다는 것을 의미합니다 .
이것은 항상 의미가있는 것은 아닙니다. 예를 들어 비교 한 숫자가 빼기의 결과 인 경우 범위 내의 어떤 FLT_EPSILON
것이 더 의미가있을 수 있습니다. 뺀 숫자의 제곱근이면 수치 부정확성이 훨씬 더 높아질 수 있습니다.
부동 소수점을 0
. 여기서는 상대 비교가 실패 |x - 0| / (|x| + 0) = 1
합니다. 따라서 x
계산이 정확하지 않은 순서 일 때 비교는 절대 모드로 전환해야 하며 거의 FLT_MIN
.
이것이 relth
위 의 매개 변수를 도입 한 이유입니다 .
또한, 곱하지 않음으로써 relth
으로 epsilon
,이 매개 변수의 해석은 우리가 그 숫자에 기대하는 수치의 정밀도 수준으로 간단하고 대응이다.
수학적 울림
(대부분 내 즐거움을 위해 여기에 보관)
더 일반적으로 나는 잘 작동하는 부동 소수점 비교 연산자 =~
가 몇 가지 기본 속성을 가져야 한다고 가정합니다 .
다음은 다소 분명합니다.
- 자기 평등 :
a =~ a
- 대칭 :
a =~ b
의미b =~ a
- 반대에 의한 불변성 :
a =~ b
암시-a =~ -b
(우리는이없는 a =~ b
및 b =~ c
의미 a =~ c
, =~
등가 관계 없음).
부동 소수점 비교에 더 구체적인 다음 속성을 추가합니다.
- 만약
a < b < c
, 다음a =~ c
의미a =~ b
(가까운 값도 동일 함) - 만약
a, b, m >= 0
다음a =~ b
의미a + m =~ b + m
(동일한 값이 큰 차이는 동일해야한다) - 경우
0 <= λ < 1
다음a =~ b
을 의미한다λa =~ λb
(아마 덜 분명 인수에 대한).
이러한 속성은 이미 가능한 거의 같음 함수에 강력한 제약을줍니다. 위에서 제안한 기능이이를 검증합니다. 하나 또는 여러 개의 명백한 속성이 누락되었을 수 있습니다.
및로 매개 변수화 된 =~
평등 관계의 가족 이라고 생각할 때 다음 을 추가 할 수도 있습니다.=~[Ɛ,t]
Ɛ
relth
- 경우
Ɛ1 < Ɛ2
다음a =~[Ɛ1,t] b
을 의미한다a =~[Ɛ2,t] b
(주어진 허용 오차에 대한 평등은 더 높은 허용 오차에서 평등을 의미한다) - 경우
t1 < t2
다음a =~[Ɛ,t1] b
을 의미한다a =~[Ɛ,t2] b
(주어진 부정확 평등은 더 높은 부정확성에서 평등을 의미한다)
제안 된 솔루션은 또한이를 확인합니다.
답변
나는 부동 소수점 숫자를 비교하는 문제를 가지고 A < B
와 A > B
여기에 작동하는 것 같다 것입니다 :
if(A - B < Epsilon) && (fabs(A-B) > Epsilon)
{
printf("A is less than B");
}
if (A - B > Epsilon) && (fabs(A-B) > Epsilon)
{
printf("A is greater than B");
}
팹 (절대 가치)은 본질적으로 동일한 지 여부를 처리합니다.
답변
부동 숫자를 비교하려면 허용 오차 수준을 선택해야합니다. 예를 들면
final float TOLERANCE = 0.00001;
if (Math.abs(f1 - f2) < TOLERANCE)
Console.WriteLine("Oh yes!");
하나의 메모. 귀하의 예는 다소 재미 있습니다.
double a = 1.0 / 3.0;
double b = a + a + a;
if (a != b)
Console.WriteLine("Oh no!");
여기에 몇 가지 수학
a = 1/3
b = 1/3 + 1/3 + 1/3 = 1.
1/3 != 1
아, 네..
의미합니까
if (b != 1)
Console.WriteLine("Oh no!")
답변
신속한 부동 소수점 비교에 대한 아이디어
infix operator ~= {}
func ~= (a: Float, b: Float) -> Bool {
return fabsf(a - b) < Float(FLT_EPSILON)
}
func ~= (a: CGFloat, b: CGFloat) -> Bool {
return fabs(a - b) < CGFloat(FLT_EPSILON)
}
func ~= (a: Double, b: Double) -> Bool {
return fabs(a - b) < Double(FLT_EPSILON)
}
답변
Michael Borgwardt & bosonix의 답변에서 PHP에 대한 적응 :
class Comparison
{
const MIN_NORMAL = 1.17549435E-38; //from Java Specs
// from http://floating-point-gui.de/errors/comparison/
public function nearlyEqual($a, $b, $epsilon = 0.000001)
{
$absA = abs($a);
$absB = abs($b);
$diff = abs($a - $b);
if ($a == $b) {
return true;
} else {
if ($a == 0 || $b == 0 || $diff < self::MIN_NORMAL) {
return $diff < ($epsilon * self::MIN_NORMAL);
} else {
return $diff / ($absA + $absB) < $epsilon;
}
}
}
}
답변
왜 숫자를 비교하는지 스스로에게 물어봐야합니다. 비교의 목적을 알고 있다면 필요한 숫자의 정확성도 알아야합니다. 이는 각 상황과 각 애플리케이션 컨텍스트에서 다릅니다. 그러나 거의 모든 실제 경우에는 절대적인 정확성 이 필요 합니다. 상대적 정확도가 적용되는 경우는 거의 없습니다.
예를 들어, 목표가 화면에 그래프를 그리는 것이라면 부동 소수점 값이 화면의 동일한 픽셀에 매핑되면 동일하게 비교되기를 원할 것입니다. 화면 크기가 1000 픽셀이고 숫자가 1e6 범위에 있다면 100은 200과 동일하게 비교하기를 원할 것입니다.
필요한 절대 정확도가 주어지면 알고리즘은 다음과 같습니다.
public static ComparisonResult compare(float a, float b, float accuracy)
{
if (isnan(a) || isnan(b)) // if NaN needs to be supported
return UNORDERED;
if (a == b) // short-cut and takes care of infinities
return EQUAL;
if (abs(a-b) < accuracy) // comparison wrt. the accuracy
return EQUAL;
if (a < b) // larger / smaller
return SMALLER;
else
return LARGER;
}