내 경우에 반올림 오류가 발생하지 않더라도 float 숫자의 동등성을 비교하면 주니어 개발자가 오도합니까?


31

예를 들어 0,0.5, ... 5의 버튼 목록을 표시하고 각 0.5마다 점프합니다. for 루프를 사용하여 STANDARD_LINE 버튼에서 다른 색상을 사용합니다.

var MAX=5.0;
var DIFF=0.5
var STANDARD_LINE=1.5;

for(var i=0;i<=MAX;i=i+DIFF){
    button.text=i+'';
    if(i==STANDARD_LINE){
      button.color='red';
    }
}

이 경우 각 값이 IEEE 754에서 정확하므로 반올림 오류가 없어야하지만 부동 소수점 동등성 비교를 피하기 위해 값을 변경 해야하는 경우 어려움을 겪고 있습니다.

var MAX=10;
var STANDARD_LINE=3;

for(var i=0;i<=MAX;i++){
    button.text=i/2.0+'';
    if(i==STANDARD_LINE/2.0){
      button.color='red';
    }
}

한편으로, 원래 코드는 더 단순하고 나에게 전달됩니다. 그러나 내가 고려하고있는 한 가지가 있습니다 : i == STANDARD_LINE 주니어 팀원을 오도합니까? 부동 소수점 숫자에 반올림 오류가있을 수 있다는 사실을 숨기고 있습니까? 이 게시물에서 댓글을 읽은 후 :

https://stackoverflow.com/questions/33646148/is-hardcode-float-precise-if-it-can-be-represented-by-binary-format-in-ieee-754

부동 소수점 숫자가 정확한지 모르는 많은 개발자가있는 것 같습니다. 내 경우에 부동 소수점 수 평등 비교를 피해야합니까? 아니면 이것에 대해 너무 생각하고 있습니까?


23
이 두 코드 목록의 동작은 동일하지 않습니다. 3 / 2.0은 1.5이지만 i두 번째 목록에서는 정수일뿐입니다. 두 번째를 제거하십시오 /2.0.
candied_orange

27
두 개의 FP를 동등하게 비교 해야하는 경우 (정수와 함께 루프 카운터 비교를 사용할 수 있기 때문에 다른 사람들이 정밀한 답변에서 지적하지 않았으므로) 그렇지 않은 경우 주석으로 충분합니다. 개인적으로 나는 오랫동안 IEEE FP와 함께 일해 왔으며, 아무런 언급이나 언급없이 직접 SPFP 비교를 본다면 여전히 혼란 스럽습니다. 그것은 매우 섬세한 코드입니다-적어도 IMHO 할 때마다 주석을 쓸 가치가 있습니다.

14
어떤 것을 선택하든, 이것은 왜 그리고 왜 절대적으로 필수적인지를 설명하는 의견 중 하나입니다. 이후 개발자는 미묘한 점을 언급하지 않고 미묘한 점을 고려하지 않을 수도 있습니다. 또한 button루프의 어느 곳에서도 변경되지 않는 사실에 크게 산만 합니다. 버튼 목록에 어떻게 액세스합니까? 배열 또는 다른 메커니즘으로 색인을 통해? 배열에 대한 인덱스 액세스의 경우 정수로 전환하는 것을 선호하는 또 다른 인수입니다.
jpmc26

9
그 코드를 작성하십시오. 누군가 0.6이 더 나은 스텝 크기가 될 것이라고 생각할 때까지 단순히 그 상수를 변경합니다.
tofro

11
"... 미숙 한 주니어 개발자" 당신도 고위 개발자를 오도 할 것입니다. 당신이 이것에 넣은 생각의 양에도 불구하고, 그들은 당신이 무엇을하고 있는지 알지 못한다고 가정하고 어쨌든 정수 버전으로 바꿀 것입니다.
GrandOpener

답변:


116

계산중인 모델에서 요구하지 않는 한 항상 연속 부동 소수점 연산을 피합니다. 부동 소수점 산술은 대부분의 주요 오류 소스에 직관적이지 않습니다. 그리고 그것이 아닌 경우 오류가 발생하는 경우를 말하는 것은 훨씬 더 미묘한 차이입니다!

따라서 플로트를 루프 카운터로 사용하는 것은 발생하는 결함이며 최소한 0.5를 사용하는 것이 좋은 이유를 설명하는 뚱뚱한 배경 설명이 필요하며 이는 특정 숫자 값에 따라 다릅니다. 이 시점에서 부동 카운터를 피하기 위해 코드를 다시 작성하는 것이 더 읽기 쉬운 옵션 일 것입니다. 그리고 가독성은 전문가 요구 사항의 계층 구조에서 정확성 옆에 있습니다.


48
나는 "결함이 발생하는 결함"을 좋아합니다. 물론 지금 작동 할 수도 있지만 지나가는 사람의 산들 바람이 부러 질 것입니다.
AakashM

10
예를 들어, 4 번째 버튼의 "표준 라인"을 사용하여 0에서 5까지 11 개의 동일 간격 버튼 대신 6 번째 "표준 라인"을 사용하여 0에서 5까지 16 개의 동일 간격 버튼을 갖도록 요구 사항이 변경되었다고 가정합니다. 단추. 따라서이 코드를 상속 한 사람은 0.5에서 1.0 / 3.0으로 변경하고 1.5에서 5.0 / 3.0으로 변경하십시오. 그러면 어떻게됩니까?
David K

8
그래, 내가 무엇을 변경한다는 생각과 불편 해요 보이는 것은 임의의 수를 될 또 다른 임의의 번호 (숫자가 될 수 있기 때문에 "정상"으로) (즉 보인다 실제로 결함을 소개 동등하게 "정상").
알렉산더-복원 모니카

7
@Alexander : 그렇습니다 DIFF must be an exactly-representable double that evenly divides STANDARD_LINE. 해당 주석을 작성하지 않으려면 (그리고 미래의 모든 개발자가 IEEE754 binary64 부동 소수점을 이해하기에 충분하다고 알고있는 경우)이 방법으로 코드를 작성하지 마십시오. 즉, 이런 식으로 코드를 작성하지 마십시오. 특히 더 효율적이지 않기 때문에 : FP 추가는 정수 추가보다 대기 시간이 높으며 루프로 수행되는 종속성입니다. 또한 컴파일러 (JIT 컴파일러조차도)는 정수 카운터로 루프를 만드는 데 더 좋습니다.
Peter Cordes

39

일반적으로 루프는 n 번 무언가를 생각하는 방식으로 작성되어야합니다 . 부동 소수점 인덱스를 사용하는 경우 더 이상 n 번 수행하는 것이 아니라 조건이 충족 될 때까지 실행 하는 문제입니다 . 이 조건이 i<n많은 프로그래머가 기대하는 것과 매우 유사 하면 코드가 실제로 다른 코드를 수행 할 때 코드가 한 가지 일을하는 것처럼 보입니다.이 코드는 프로그래머가 코드를 감추면 쉽게 잘못 해석 할 수 있습니다.

다소 주관적이지만 겸손한 견해로는 정수 색인을 사용하여 고정 된 횟수만큼 반복하기 위해 루프를 다시 작성할 수 있다면 그렇게해야합니다. 따라서 다음 대안을 고려하십시오.

var DIFF=0.5;                           // pixel increment
var MAX=Math.floor(5.0/DIFF);           // 5.0 is max pixel width
var STANDARD_LINE=Math.floor(1.5/DIFF); // 1.5 is pixel width

for(var i=0;i<=MAX;i++){
    button.text=(i*DIFF)+'';
    if(i==STANDARD_LINE){
      button.color='red';
    }
}

루프는 정수로 작동합니다. 이 경우 i정수이며 STANDARD_LINE정수로 강제됩니다. 물론 반올림이 발생한 경우에도 표준 선의 위치가 변경 MAX되므로 정확한 렌더링을 위해 반올림을 방지하기 위해 노력해야합니다. 그러나 부동 소수점 비교에 대해 걱정할 필요없이 정수가 아닌 픽셀 측면에서 매개 변수를 변경할 수 있다는 장점이 있습니다.


3
원하는 바에 따라 과제에서 마루 대신 반올림을 고려할 수도 있습니다. 나눗셈이 정수 결과를 제공한다고 가정하면, 나눗셈이 약간 벗어난 숫자를 우연히 발견하면 바닥이 놀라게 될 수 있습니다.
ilkkachu

1
@ilkkachu 맞습니다. 내 생각은 5.0을 최대 픽셀 양으로 설정하면 반올림을 통해 바람직하게 약간 5.0보다 아래쪽에있는 것이 좋습니다. 5.0은 효과적으로 최대 값입니다. 당신이해야 할 일에 따라 반올림이 바람직 할 수 있지만. 어쨌든 부서가 어쨌든 정수를 만들면 별 차이가 없습니다.
Neil

4
나는 매우 동의하지 않습니다. 루프를 멈추는 가장 좋은 방법은 비즈니스 로직을 가장 자연스럽게 표현하는 조건입니다. 비즈니스 로직에 11 개의 버튼이 필요한 경우 루프는 반복 11에서 정지해야합니다. 비즈니스 로직에 따라 라인이 가득 찰 때까지 버튼이 0.5 떨어져 있으면 라인이 가득 찼을 때 루프가 중지됩니다. 하나의 메커니즘 또는 다른 메커니즘으로 선택을 푸시 할 수있는 다른 고려 사항이 있지만 이러한 고려 사항이 없으면 비즈니스 요구 사항에 가장 가까운 메커니즘을 선택하십시오.
복구 상태 Monica

귀하의 설명은 자바에 대한 완전히 올바른 것입니다 / C ++ / 루비 / 파이썬 / ...하지만 자바 스크립트가 정수를 가지고 있지 않기 때문에, i그리고 STANDARD_LINE오직 정수처럼 보인다. 아무 강제는 전혀 없습니다, 그리고 DIFF, MAX그리고 STANDARD_LINE모든 단지입니다 Number이야. Number정수로 사용되는은 아래 2**53에서 안전해야 하지만 여전히 부동 소수점 숫자입니다.
Eric Duminil 8

@EricDuminil 그렇습니다. 그러나 이것의 절반입니다. 나머지 절반은 가독성입니다. 나는 이것을 최적화가 아닌 이런 식으로하는 주요 이유라고 언급합니다.
Neil

20

정수가 아닌 루프 변수를 사용하는 것이 올바르게 작동하는 것과 같은 경우에도 일반적으로 나쁜 스타일이라는 다른 모든 대답에 동의합니다. 그러나 여기 스타일이 나쁜 또 다른 이유가있는 것 같습니다.

코드는 사용 가능한 선 너비가 정확히 0에서 5.0까지의 0.5의 배수임을 알고 있습니다. 그래야합니까? 그것은 쉽게 변경 될 수있는 사용자 인터페이스 결정처럼 보입니다 (예를 들어, 너비가 커짐에 따라 사용 가능한 너비 사이의 간격이 더 커지기를 원할 수도 있습니다. 0.25, 0.5, 0.75, 1.0, 1.5, 2.0, 2.5, 3.0, 4.0, 5.0 또는 무엇인가).

코드는 사용 가능한 선 너비가 모두 부동 소수점 숫자와 10 진수로 "좋은"표현을 가지고 있음을 "알고"있습니다. 그것은 또한 변할 수도있는 것 같습니다. (어느 시점에서 0.1, 0.2, 0.3, ...을 원할 수 있습니다.)

여러분의 코드는 버튼에 넣을 텍스트가 자바 스크립트가 부동 소수점 값으로 바뀌는 것임을 "알고"있습니다. 그것은 또한 변할 수도있는 것 같습니다. (예를 들어 언젠가는 1/3과 같은 너비를 원할 것입니다.이 너비는 0.33333333333333 또는 다른 것으로 표시하지 않을 것입니다. 또는 "1.5"와 일관성을 유지하기 위해 "1"대신 "1.0"을보고 싶을 수도 있습니다. .)

이것들은 모두 하나의 약점의 표현처럼 느껴집니다. 이것은 일종의 레이어 혼합입니다. 이러한 부동 소수점 숫자는 소프트웨어의 내부 논리의 일부입니다. 버튼에 표시되는 텍스트는 사용자 인터페이스의 일부입니다. 여기 코드에서보다 분리되어 있어야합니다. "이 중 어느 것이 강조 표시되어야합니까?"와 같은 개념 사용자 인터페이스 문제이며 아마도 부동 소수점 값에 묶여서는 안됩니다. 그리고 여기서 루프는 실제로 선 너비가 아닌 버튼 위의 루프 입니다. 그렇게하면 정수가 아닌 값을 갖는 루프 변수를 사용하려는 유혹이 사라집니다. 연속적인 정수 또는 for ... in / for ... of 루프를 사용하는 것입니다.

제 생각에는 정수가 아닌 숫자를 반복하려고 하는 대부분의 경우는 다음과 같습니다. 숫자 문제와 전혀 관련이없는 다른 이유가 있으며 코드가 다르게 구성되어야하는 이유가 있습니다. ( 모든 경우가 아니라 ; 일부 수학적 알고리즘이 정수가 아닌 값에 대한 루프로 가장 깔끔하게 표현 될 수 있다고 상상할 수 있습니다.)


8

하나의 코드 냄새가 그런 식으로 루프에서 수레를 사용하고 있습니다.

루핑은 여러 가지 방법으로 수행 할 수 있지만 99.9 %의 경우 1 씩 증가해야합니다. 그렇지 않으면 주니어 개발자 만이 아니라 혼란이있을 것입니다.


나는 1의 정수 배수가 for 루프에서 혼란스럽지 않다고 생각한다. 코드 냄새가 나지 않을 것입니다. 분수 만.
CodeMonkey

3

예, 이것을 피하고 싶습니다.

부동 소수점 숫자는 의심없는 프로그래머에게 가장 큰 함정 중 하나입니다. 부동 소수점 평등 테스트에 따라 돈을 부동 소수점으로 표시하는 것까지 모두 큰 문제입니다. 하나의 플로트를 다른 플로트에 추가하는 것은 가장 큰 범죄자 중 하나입니다. 이와 같은 것에 관한 많은 과학 문헌이 있습니다.

예를 들어 삼각법, 플로팅 함수 그래프 등과 같이 실제 수학 계산을 수행 할 때와 같이 적절한 위치에 부동 소수점 숫자를 정확하게 사용하고 연속 연산을 수행 할 때는 매우주의해야합니다. 평등은 바로입니다. IEEE 표준에 의해 어떤 특정 숫자 집합이 정확한지에 대한 지식은 매우 희귀하며 절대 그것에 의존하지 않습니다.

귀하의 경우, Murphys Law에 따르면 경영진이 0.0, 0.5, 1.0 ...이 아니라 0.0, 0.4, 0.8 ... 또는 그 밖의 것을 갖기를 원하는 지점 이 생길 것입니다 . 당신은 즉시 질식 할 것이고, 당신의 하급 프로그래머 (또는 당신 자신)는 문제를 찾을 때까지 길고 열심히 디버깅 할 것입니다.

특정 코드에서는 실제로 정수 루프 변수가 있습니다. i숫자가 아닌 th 버튼을 나타냅니다 .

그리고 아마도, 명확성을 기하기 위해 글을 쓰지 i/2않고 i*0.5무슨 일이 일어나고 있는지 분명히 알 수있을 것입니다.

var BUTTONS=11;
var STANDARD_LINE=3;

for(var i=0; i<BUTTONS; i++) {
    button.text = (i*0.5)+'';
    if (i==STANDARD_LINE) {
      button.color='red';
    }
}

참고 : 주석에서 지적했듯이 JavaScript에는 실제로 별도의 정수 유형이 없습니다. 그러나 최대 15 자리의 정수는 정확하고 안전합니다 ( https://www.ecma-international.org/ecma-262/6.0/#sec-number.max_safe_integer 참조 ). 정수 또는 정수가 아닌 정수에서 작동하기가 더 혼동스럽고 오류가 많음 ")"정신 "에 별도의 유형을 갖는 것이 적절합니다. 매일 사용하는 (루프, 화면 좌표, 배열 인덱스 등) NumberJavaScript로 표시되는 정수는 놀라운 일이 아닙니다 .


BUTTONS라는 이름을 다른 것으로 바꿨습니다. 10 개가 아닌 11 개의 버튼이 있습니다. 아마도 FIRST_BUTTON = 0, LAST_BUTTON = 10, STANDARD_LINE_BUTTON = 3입니다. 그 외에도 네, 그렇게해야합니다.
gnasher729

사실 @EricDuminil이며 이에 대한 답변을 조금 추가했습니다. 고맙습니다!
AnoE

1

나는 당신의 제안 중 하나가 좋다고 생각하지 않습니다. 대신 최대 값과 간격을 기반으로 버튼 수에 대한 변수를 소개합니다. 그런 다음 버튼 자체의 인덱스를 반복하는 것은 간단합니다.

function precisionRound(number, precision) {
  let factor = Math.pow(10, precision);
  return Math.round(number * factor) / factor;
}

var maxButtonValue = 5.0;
var buttonSpacing = 0.5;

let countEstimate = precisionRound(maxButtonValue / buttonSpacing, 5);
var buttonCount = Math.floor(countEstimate) + 1;

var highlightPosition = 3;
var highlightColor = 'red';

for (let i=0; i < buttonCount; i++) {
    let buttonValue = i / buttonSpacing;
    button.text = buttonValue.toString();
    if (i == highlightPosition) {
        button.color = highlightColor;
    }
}

더 많은 코드 일 수 있지만 더 읽기 쉽고 강력합니다.


0

루프 카운터를 값으로 사용하는 대신 표시하고있는 값을 계산하여 모든 것을 피할 수 있습니다.

var MAX=5.0;
var DIFF=0.5
var STANDARD_LINE=1.5;

for(var i=0; (i*DIFF) < MAX ; i=i+1){
    var val = i * DIFF

    button.text=val+'';

    if(val==STANDARD_LINE){
      button.color='red';
    }
}

-1

부동 소수점 산술이 느리고 정수 산술이 빠르므로 부동 소수점을 사용할 때 정수를 사용할 수있는 곳에서 불필요하게 사용하지 않습니다. 부동 소수점 숫자, 상수조차도 약간의 오차가있는 근사치로 생각하는 것이 유용합니다. 디버깅하는 동안 네이티브 부동 소수점 숫자를 각 숫자를 포인트 대신 범위로 취급하는 플러스 / 마이너스 부동 소수점 객체로 바꾸는 것이 매우 유용합니다. 이렇게하면 각 산술 연산 후 점차 증가하는 부정확성을 발견 할 수 있습니다. 따라서 "1.5"는 "1.45와 1.55 사이의 일부 숫자"로, "1.50"은 "1.495와 1.505 사이의 일부 숫자"로 간주해야합니다.


5
작은 마이크로 프로세서를 위해 C 코드를 작성할 때 정수와 부동 소수점의 성능 차이가 중요하지만 최신 x86 파생 CPU는 부동 소수점을 너무 빨리 처리하므로 동적 언어를 사용하는 오버 헤드로 인해 패널티를 쉽게 극복 할 수 있습니다. 특히 Javascript는 실제로 필요할 때 NaN 페이로드를 사용하여 실제로 모든 숫자를 부동 소수점으로 나타내지 않습니까?
leftaroundabout

1
"부동 소수점 산술은 느리고 정수 산술은 빠릅니다"는 복음의 발전으로 유지해서는 안되는 역사적 사실입니다. @leftaroundabout이 말한 것에 덧붙여, 패널티가 거의 관련이 없다는 것은 사실이 아니며, 자동 벡터화 컴파일러 및 크런치 할 수있는 명령어 세트의 마술 덕분에 부동 소수점 연산이 동등한 정수 연산보다 빠르다 는 것을 알 수 있습니다 한 번의 주기로 많은 양의 부유물. 이 질문에 대해서는 관련이 없지만 기본 "정수가 부동 소수점보다 빠릅니다"라는 가정은 오랫동안 사실이 아닙니다.
Jeroen Mostert

1
@JeroenMostert SSE / AVX는 정수와 부동 소수점 모두에 대해 벡터화 된 연산을 가지고 있으며 더 작은 정수를 사용할 수 있습니다 (지수에 비트가 낭비되지 않기 때문에) 원칙적 으로 여전히 최적화 된 정수 코드에서 더 많은 성능을 낼 수 있습니다 수레보다. 그러나 이것은 대부분의 응용 프로그램과 관련이 없으며 분명히이 질문과 관련이 없습니다.
leftaroundabout

1
@leftaroundabout : 물론입니다. 내 요점은 어떤 상황에서 어느 것이 더 빠른지 에 관한 것이 아니라 "FP가 느리고 정수가 빠르다는 것을 알고 있기 때문에 가능하면 정수를 사용할 것입니다."라는 말은 당신이하고있는 일에 최적화가 필요한지에 대한 질문.
Jeroen Mostert
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.