[C#] C #에서 부동 소수점 수학이 일관됩니까? 할 수 있습니까?

아니요, 이것은 또 다른 “왜 (1 / 3.0) * 3! = 1” 질문 이 아닙니다 .

최근에 부동 소수점에 대해 많이 읽었습니다. 특히, 동일한 계산이 다른 아키텍처 또는 최적화 설정에서 다른 결과제공 할 수있는 방법 .

재생을 저장하거나 P2P 네트워크로 연결된 비디오 게임 (서버-클라이언트와 달리)의 문제로, 모든 클라이언트가 프로그램을 실행할 때마다 정확히 동일한 결과를 생성합니다-하나의 작은 불일치 부동 소수점 계산은 다른 컴퓨터에서 (또는 동일한 컴퓨터에서 ) 게임 상태가 크게 달라질 수 있습니다 !

이것은 일부 프로세서 (즉, x86)가 배 확장 정밀도를 사용하기 때문에 IEEE-754 를 “따르는”프로세서에서도 발생합니다 . 즉, 80 비트 레지스터를 사용하여 모든 계산을 수행 한 다음 64 비트 또는 32 비트로 잘라내어 계산에 64 비트 또는 32 비트를 사용하는 머신과 다른 반올림 결과를 가져옵니다.

온라인 에서이 문제에 대한 몇 가지 솔루션을 보았지만 C #이 아닌 C ++에 대한 모든 솔루션을 보았습니다.

  • (Windows), (Linux?) 또는 (BSD)를 double사용하여 배정 밀도 모드를 비활성화합니다 (모든 계산에서 IEEE-754 64 비트를 사용하도록 )._controlfp_s_FPU_SETCWfpsetprec
  • 항상 동일한 최적화 설정으로 동일한 컴파일러를 실행하고 모든 사용자에게 동일한 CPU 아키텍처가 있어야합니다 (플랫폼 간 재생 없음). 내 “컴파일러”는 실제로 JIT이므로 프로그램이 실행될 때마다 다르게 최적화 될 수 있기 때문에 이것이 가능하지 않다고 생각합니다.
  • 고정 소수점 연산, 그리고 피할 사용 floatdouble모두를. decimal이 목적으로 작동하지만 훨씬 느려질 것이며 System.Math그것을 지원 하는 라이브러리 함수는 없습니다.

그래서 이것은 C #에서도 문제가됩니까? 모노 만 아닌 Windows 만 지원하려면 어떻게해야합니까?

그렇다면 프로그램을 정상적인 배정도로 실행하도록 강제 할 수있는 방법이 있습니까?

그렇지 않은 경우 부동 소수점 계산을 일관성있게 유지 하는 데 도움이되는 라이브러리가 있습니까?



답변

.net에서 일반 부동 소수점을 결정적으로 만드는 방법은 없습니다. JITter는 다른 플랫폼 (또는 다른 버전의 .net)에서 다르게 동작하는 코드를 작성할 수 있습니다. 따라서 float결정적 .net 코드에서 normal s를 사용할 수 없습니다.

내가 고려한 해결 방법 :

  1. C #에서 FixedPoint32를 구현하십시오. 이것이 너무 어렵지는 않지만 (반쯤 완성 된 구현이 있습니다) 매우 작은 범위의 값은 사용하기가 성가 시게합니다. 항상주의를 기울여야하므로 오버 플로우 나 정밀도가 떨어질 수 있습니다. 결국 나는 정수를 직접 사용하는 것보다 쉽지 않다는 것을 알았습니다.
  2. C #에서 FixedPoint64를 구현하십시오. 나는 이것이 오히려 어렵다는 것을 알았다. 일부 연산의 경우 128 비트의 중간 정수가 유용합니다. 그러나 .net은 그러한 유형을 제공하지 않습니다.
  3. 사용자 정의 32 비트 부동 소수점을 구현하십시오. BitScanReverse 내장 함수가 없으면이를 구현할 때 몇 가지 문제가 발생합니다. 그러나 현재 이것이 가장 유망한 길이라고 생각합니다.
  4. 수학 연산에는 기본 코드를 사용하십시오. 모든 수학 연산에서 델리게이트 호출의 오버 헤드가 발생합니다.

방금 32 비트 부동 소수점 수학의 소프트웨어 구현을 시작했습니다. 내 2.66GHz i3에서 초당 약 7 천만 더하기 / 곱하기를 할 수 있습니다.
https://github.com/CodesInChaos/SoftFloat . 분명히 여전히 매우 불완전하고 버그가 있습니다.


답변

C # 사양 (§4.1.6 부동 소수점 유형)에서는 특히 결과보다 높은 정밀도를 사용하여 부동 소수점 계산을 수행 할 수 있습니다. 따라서 아니요, .Net에서 직접 계산을 결정할 수 있다고 생각하지 않습니다. 다른 사람들은 다양한 해결 방법을 제안 했으므로 시도해 볼 수 있습니다.


답변

다음 페이지는 그러한 작업의 절대 이식성이 필요한 경우에 유용 할 수 있습니다. 부동 소수점 연산을 에뮬레이트하는 소프트웨어를 포함하여 IEEE 754 표준의 구현을 테스트하기위한 소프트웨어에 대해 설명합니다. 그러나 대부분의 정보는 C 또는 C ++에만 해당됩니다.

http://www.math.utah.edu/~beebe/software/ieee/

고정 점에 대한 참고 사항

이진 고정 소수점 숫자는 다음 네 가지 기본 산술 연산에서 알 수 있듯이 부동 소수점 대신 사용할 수 있습니다.

  • 덧셈과 뺄셈은 간단합니다. 정수와 같은 방식으로 작동합니다. 그냥 더하거나 빼세요!
  • 두 개의 고정 소수점 수를 곱하려면 두 수를 곱한 다음 정의 된 소수 비트 수만큼 오른쪽으로 이동합니다.
  • 두 개의 고정 소수점 수를 나누려면 피제수를 정의 된 소수 비트 수만큼 왼쪽으로 이동 한 다음 제수로 나눕니다.
  • 이 백서의 4 장 에는 이진 고정 소수점 수 구현에 대한 추가 지침 있습니다.

이진 고정 소수점 숫자는 int, long 및 BigInteger와 같은 정수 데이터 유형과 비 CLS 호환 유형 uint 및 ulong에서 구현 될 수 있습니다.

다른 답변에서 제안한 것처럼 테이블의 각 요소가 이진 고정 소수점 수인 조회 테이블을 사용하면 사인, 코사인, 제곱근 등과 같은 복잡한 함수를 구현할 수 있습니다. 룩업 테이블이 고정 소수점 수보다 세분화되지 않은 경우 룩업 테이블 세분화의 절반을 입력에 추가하여 입력을 반올림하는 것이 좋습니다.

// Assume each number has a 12 bit fractional part. (1/4096)
// Each entry in the lookup table corresponds to a fixed point number
//  with an 8-bit fractional part (1/256)
input+=(1<<3); // Add 2^3 for rounding purposes
input>>=4; // Shift right by 4 (to get 8-bit fractional part)
// --- clamp or restrict input here --
// Look up value.
return lookupTable[input];


답변

이것이 C #의 문제입니까?

예. 다른 아키텍처로 인해 부동 소수점 표현의 부정확성 등의 프레임 속도 다른이 편차로 이어질 수 걱정거리를 – 그들은이 경우에도 동일한 (하나의 기계에 느린 GPU를 제외한 예를 들어 동일한 아키텍처) 부정확.

System.Decimal을 사용할 수 있습니까?

당신이 할 수없는 이유는 없지만 개가 느립니다.

프로그램을 배정도로 실행하는 방법이 있습니까?

예. CLR 런타임을 직접 호스팅하십시오 . CorBindToRuntimeEx를 호출하기 전에 모든 nessecary 호출 / 플래그 (부동 소수점 산술의 동작을 변경하는)를 C ​​++ 응용 프로그램으로 컴파일하십시오.

부동 소수점 계산을 일관성있게 유지하는 데 도움이되는 라이브러리가 있습니까?

내가 아는 한에서는 아니다.

이 문제를 해결하는 다른 방법이 있습니까?

전에이 문제를 해결했지만 아이디어는 QNumbers 를 사용하는 입니다. 그것들은 고정 소수점 형태의 실수입니다. 기수 -10 (10 진수)의 고정 소수점이 아닌 기수 -2 (이진수); 이 때문에 그것들의 수학적 프리미티브 (add, sub, mul, div)는 순진한 base-10 고정 점보다 훨씬 빠릅니다. 특히 n두 값이 같은 경우 (귀하의 경우). 또한 통합되어 있기 때문에 모든 플랫폼에서 명확한 결과를 얻을 수 있습니다.

프레임 속도는 여전히 이것에 영향을 미칠 수 있지만 동기화 지점을 사용하여 나쁘지 않고 쉽게 수정됩니다.

QNumber에 더 많은 수학적 함수를 사용할 수 있습니까?

예, 이것을하기 위해 십진수를 왕복합니다. 또한 trig (sin, cos) 함수에 룩업 테이블 을 사용해야 합니다. 그것들은 실제로 다른 플랫폼에서 다른 결과를 줄 수 있으므로 올바르게 코딩하면 QNumber를 직접 사용할 수 있습니다.


답변

이 약간 오래된 MSDN 블로그 항목에 따르면 JIT는 부동 소수점에 SSE / SSE2를 사용하지 않으며 x87입니다. 그 때문에 언급했듯이 모드와 플래그에 대해 걱정해야하며 C #에서는 제어 할 수 없습니다. 따라서 정상적인 부동 소수점 연산을 사용한다고해서 프로그램의 모든 기계에서 동일한 결과가 보장되는 것은 아닙니다.

배정 밀도의 정확한 재현성을 얻으려면 소프트웨어 부동 소수점 (또는 고정 소수점) 에뮬레이션을 수행해야합니다. 이 작업을 수행하는 C # 라이브러리를 모르겠습니다.

필요한 작업에 따라 단 정밀도로 벗어날 수 있습니다. 아이디어는 다음과 같습니다.

  • 관심있는 모든 값을 단 정밀도로 저장
  • 작업을 수행하려면
    • 입력을 배정도로 확장
    • 배정도로 조작하다
    • 결과를 단 정밀도로 다시 변환

x87의 큰 문제는 정밀도 플래그와 레지스터가 메모리에 유출되었는지 여부에 따라 53 비트 또는 64 비트 정확도로 계산이 수행 될 수 있다는 것입니다. 그러나 많은 작업에서 높은 정밀도로 작업을 수행하고 낮은 정밀도로 반올림하면 정답이 보장되므로 모든 시스템에서 정답이 동일하게 보장됩니다. 어느 쪽이든 정답을 보장하기에 충분한 정밀도를 가지므로 여분의 정밀도를 얻는 지 여부는 중요하지 않습니다.

이 체계에서 작동 해야하는 연산 : 더하기, 빼기, 곱하기, 나누기, sqrt. sin, exp 등과 같은 것은 작동하지 않습니다 (결과는 일반적으로 일치하지만 보장은 없습니다). “이중 반올림은 무해합니까?” ACM 참조 (유료 등록 요구)

도움이 되었기를 바랍니다!


답변

다른 답변에서 이미 언급했듯이 : 예, 이것은 순수한 Windows를 유지하는 경우에도 C #의 문제입니다.

해결책에 관해서 : 내장 BigInteger클래스를 사용하고 그러한 수의 계산 / 저장에 공통 분모를 사용하여 모든 계산을 정의 된 정밀도로 스케일링 하면 문제를 줄일 수 있습니다 (그리고 약간의 노력 / 성능 저하로) .

OP의 요청에 따라 성능과 관련하여 :

System.Decimal부호는 1 비트, 96 비트 정수 및 “스케일”(소수점이있는 위치)로 숫자를 나타냅니다. 모든 계산에서이 데이터 구조에서 작동해야하며 CPU에 내장 된 부동 소수점 명령어를 사용할 수 없습니다.

BigInteger“솔루션”비슷한 않습니다 – 당신은 아마 당신이 80 비트 또는 정밀도의 240 개 비트를 원하는 … 당신이 /가 필요합니다 얼마나 많은 숫자 정의 할 수 있습니다 만있다.

CPU / FPU 내장 명령을 사용하지 않고 정수 전용 명령을 통해이 숫자에 대한 모든 작업을 항상 시뮬레이션해야하기 때문에 속도가 느려져 수학 연산 당 훨씬 더 많은 명령이 발생합니다.

성능 저하를 줄이려면 QNumbers (Jonathan Dickinson의 답변 참조- 부동 소수점 수학이 C #에서 일관됩니까? 가능합니까? ) 및 / 또는 캐싱 (예 : 삼각 계산 등)과 같은 몇 가지 전략 이 있습니다 .


답변

글쎄, 이 작업을 수행하는 방법대한 나의 첫 번째 시도 는 다음과 같습니다.

  1. 중요한 부동 소수점 연산에 사용할 간단한 개체가있는 ATL.dll 프로젝트를 만듭니다. 부동 소수점을 수행하기 위해 xx87 이외의 하드웨어를 사용하지 않도록 설정하는 플래그로 컴파일해야합니다.
  2. 부동 소수점 연산을 호출하고 결과를 반환하는 함수를 만듭니다. 간단하게 시작한 다음 효과가있는 경우 나중에 필요한 경우 나중에 성능 요구를 충족시키기 위해 복잡성을 늘릴 수 있습니다.
  3. control_fp 호출을 실제 수학 주위에 두어 모든 머신에서 동일한 방식으로 수행되도록하십시오.
  4. 새 라이브러리를 참조하고 예상대로 작동하는지 테스트하십시오.

(32 비트 .dll로 컴파일 한 다음 x86 또는 AnyCpu (또는 64 비트 시스템에서 x86 만 타겟팅 할 수 있습니다. 아래 주석 참조)와 함께 사용할 수 있다고 생각합니다.)

그런 다음 작동한다고 가정하면 Mono를 사용하고 싶을 때 비슷한 방식으로 다른 x86 플랫폼에서 라이브러리를 복제 할 수 있다고 생각합니다 (물론 COM은 아니지만 와인을 사용합니까?) 우리는 거기에 간다 …).

작동한다고 가정하면 성능 문제를 해결하기 위해 한 번에 여러 작업을 수행 할 수있는 사용자 정의 기능을 설정할 수 있어야하며 플랫폼에서 최소한의 결과로 일관된 결과를 얻을 수있는 부동 소수점 연산이 있습니다. C ++로 작성된 코드의 나머지 부분을 C #으로 남겨 둡니다.