![]() |
|
||||||||
경고! 게시물 작성자의 사전 허락없는 메일주소 추출행위 절대 금지 |
|
http://www.delmadang.com/community/bbs_view.asp?bbsNo=3&bbsCat=43&indx=195044&keyword1=double&keyword2=반올림
.. .. Round(2.5); // 2를 돌려줌. Rnd(2.5); // 3을 돌려줌. 한석희 님이 올리신 글----------------------- > 가끔 round()를 사용해 실수를 정수로 바꾸려는 경우에 > 이상을 느끼는 분들의 글이 많이 올라옵니다... > > 반올림에는 무조건 4사5입하는 방법과 (보통 생각하는 방법) > 유효숫자의 다음 자리가 5인 경우 마지막 유효숫자의 홀수/짝수를 > 구별해서 4사5입하는 두 가지 방법이 있습니다. (Banker's round) > > 델파이의 round()는 정수 변환을 위해 두번째 방법을 쓰는데, > 이것은 반올림으로 인한 오차를 줄이기 위한 벙법입니다.. > 마지막 숫자가 5로 끝나는 숫자가 무작위로 입력될 경우, > 그 앞의 숫자가 홀수 또는 짝수일 경우는 확률적으로 반씩일 것이므로, > 무조건 4사5입하면 정답보다 큰 답이 나오고(올림에 해당하므로), > 홀수/짝수를 구별하여 4사5입하면 정답에 보다 가까운 해를 구할수 있다 > 는 것입니다... 위의 것은 TEST해 보지 않음... 뭐 함수로 그렇게 만들었다면 납득할 수 있음... 그 나라에서 아니면... 수학의 통계에 의해... 그렇지만... 위의 경우와는 다르다고 생각됩니다... 아시는 분 더 많은 정보 부탁드립니다. 가끔씩 저런 문제에 부딛히고는 하는데
원래 실수 값을 double, float 에 저장할때는 실제는 3.175가 저장되는게 아니라, 3.174999999999999 가 저장됩니다. 이는 DEC 즉 십진수의 내부 저장 방법에 기인한 문제로 예전 8비트 시절부터 오랫동안 애용되어 오던 방법입니다. 다시 말하자면 컴퓨터가 10진수를 16진수로 기억되는데 따르는 문제입니다. 값에 따라서 기억되는 16진수의 값과 다시 10진수로 되돌렸을때의 값이 약간의 오차를 보이는 수치가 생기가 된 것이죠. 예의 3.175처럼. 그래서 이런 변환시에 생기는 오차를 줄이기 위해 여러가지 방법이 동원 되었습니다. 3.174999999999999 에 1000을 곱하면 3174.999999999999 가 되고 int 로 변환시는 .99999999999를 제거해버리니 저런 현상이 생깁니다. 이는 과거의 FPU(연산을 담당하는 CPU)에서도 그렇게 처리?한다고 기억이 드는군요. (자꾸 희미해지는 기억력 --;) 그래서 컴파일러를 위해서는 double, float의 계산은 double, float 형에서 모든 계산을 마치고, int로는 최종 계산 값만 옮기면 문제가 해결됩니다. 이렇게 하면 컴파일러 최종 대입전에 스스로 오차를 바로 잡기 때문에 문제가 해결되나, int로 바로 대입하면 최종 오차를 바로 잡는대신 int 값으로의 보정이 되기 때문에 .99999999999999999999가 잘려 버리게 됩니다. 언제인지는 정확히 기억이 나진 않지만 VC도 마찬가지 문제를 안고 있었는데 언제인가 부터 int로의 대입시에도 문제가 안되게 교정을 했다고 합니다. 이는 컴파일러 마다 다를 수 있는 사항이기 때문에 항상 double 값은 double 에서 모든 계산을 마치게 하고 int result = result_double; 식으로 값만 옴기는 식으로 하면 오차를 없애게 됩니다. void __fastcall TForm1::Button5Click(TObject *Sender) { SetPrecisionMode(pmDouble); //3174, 3174 ==> 3174, 3175이것을 넣어니... //오 값이 바로 나왔네요.... 김도완님 감사... float fData = 3.175; double dlData = 3.175; int valueF = fData *1000; int valueD = dlData*1000; char str[100]; wsprintf( str, "%d, %d", valueF, valueD ); ::MessageBox( this->Handle, str, "MSG", MB_OK ); //결과: 3174, 3175 } //--------------------------------------------------------------------------- void __fastcall TForm1::Button3Click(TObject *Sender) { // Edit의 값 1.175를 1.15가 되게 하려고 한다. 0.05로 떨어지는 값이어야 함으로 0.05로 나누고 // 소수점 아래를 버리고 곱하기 0.05 이렇게 하면 되잖아요 // 그런데 Edit에 항상 1.175만 아니고 1.15(0.05로 떨어지는 값)도 사용자가 입력할 수 있지요 //SetPrecisionMode(pmDouble); //여기에 어떤 값을 넣어도 제대로 안나오네요 double Value = 1.175; int n = Value / 0.05; Value = n * 0.05; ShowMessage( Value ); //1.15 정상 짝짝... Value = 1.15; n = Value / 0.05; Value = n * 0.05; ShowMessage( Value ); //1.1 <== 돌겠다.... Error 여기도 1.15가 되어야 하는 데... Value = 1.15; // 그래서 중간값을 보기로 함... Value = Value / 0.05; ShowMessage( Value ); //여기까지 23 짝짝... n = (int)Value; ShowMessage( n ); //여기오면 22 엥... 경악!!!... Value = n * 0.05; // 22 * 0.05 ShowMessage( Value ); // 1.1 Value = 1.15; // 그러면 이렇게 해야 겠다. double을 바로 계산 Value = Value / 0.05; Value = Value * 0.05; ShowMessage( Value ); // 1.15 짝짝.... Value = 1.175; // 그러면 1.175도 이렇게 하여 1.15가 나온다면 웃기는 일이지만 나오면 좋겠다. Value = Value / 0.05; Value = Value * 0.05; ShowMessage( Value ); // 1.175 가 나온다... 이 정도라면 이것 좀 심각한 것 아닌가? 돌겠다... //--------------------------------------------------------------------------- //이런 짓을 하도 하다 보니... 이제는 빌더의 마음을 알게 되더군요.... + 0.00001 을 더해줌 Value = 1.15; Value = Value / 0.05; ShowMessage( Value ); //여기까지 23 짝짝... n = (int)(Value + 0.00001); //그래서 이 부분에 0.1을 아니 혹 또 오차가 발생할까 싶어 0.00001을 더해 주었습니다. 어째도 int가 될 것이기 때문에... ShowMessage( n ); //여기도 23 짝짝... Value = n * 0.05; // 23 * 0.05 ShowMessage( Value ); // 1.15 Value = 1.175; // 이것도 잘 되네요... Value = Value / 0.05; //23.5 ShowMessage( Value ); n = (int)(Value + 0.00001); //그렇다면 뒤에 점이 없다면... 1을 빼버리나..???? ShowMessage( n ); //23 Value = n * 0.05; ShowMessage( Value ); //1.15 짝짝,.... -.- 그런데 이렇게 쓰야 하나... } //위의 논리를 적용시켜서... 아래를 이렇게 바꾸어 보았습니다. //0.00001 이것보다 더 작아도 됩니다. 그러나 아주 작으면... 안됩니다. //그러니까... double의 허용범위라고 해야 하나... 여하튼 실험해 보세요... //SetPrecisionMode(pmDouble); 산술프로세서... 어떤 보정 하는 것 같은 데... 정확하게 어떻게 언제.. 해야할지..??? //뭔가 잘못되었다.... void __fastcall TForm1::Button6Click(TObject *Sender) { float fData = 3.175; double dlData = 3.175; int valueF = fData *1000 + 0.00001; int valueD = dlData*1000 + 0.00001; char str[100]; wsprintf( str, "%d, %d", valueF, valueD ); ::MessageBox( this->Handle, str, "MSG", MB_OK ); //결과: 3174, 3175 } //--------------------------------------------------------------------------- double Value = 1.15; // 그래서 중간값을 보기로 함...
Value = Value / 0.05; ShowMessage( Value ); //여기까지 23 짝짝... int n = (int)Value; ShowMessage( n ); //여기오면 22 엥... 경악!!!... Value = n * 0.05; // 22 * 0.05 ShowMessage( Value ); // 1.1 //--------------------------------------------------- 올만에 목욕을 했습니다... 개운합니다. 목욕을 하고 또 누가 답변을 주셨나... 하고 보았습니다.. 다시 첨부터 한번 더 읽었습니다. 그런데 위에 제가 적은 것인데... 가만히 보니... 첫번째 ShowMessage에서 23을 보여줍니다. 음밀히 말해서... 22.99999999999*** 뭐뭐 이렇게 보여 준는 것이 정상인 데... ANSI의 기준을 따른다면... ShowMessage에서는 VC++처럼 보정을 하여 보여주는 군요.... ShowMessage( FloatToStr(Value) ); 이것도 마찬가지군요... 보정하는 방법도 다 알고 있으면서... int로 넘어 갈때도 해주었다면 통일성도 있고 좋았을 것인 데... 아깝다.... 도대체 보정한 값을 얼마로 했을까? 요것 함수로 하나 만들어 두고 사용하면 괜찮을 것 같은 데... DoubleToInt(); 혹 다음말에 있을까 찾아보았습니다. 없군요... 그래서 고민하다가 23 - Value를 해보았습니다. 보정한 값을 알기 위해... 3.5527136788005E-15 E-15.... 헉... 지수표현은 알겠는 데... 정확하게... 찾아봐서 해야 할 것 .. 그래서 혹 보정값이 있을까? 찾아보았습니다. 하나 찾았습니다. 이 분도 개인 같은 데... 보정값이... 맞는지는 모르겠습니다. 부동 소숫점 실수 사용시 유의 사항 C/C++ 2007/01/17 13:47 http://blog.naver.com/elky84/10013104493 부동 소수점 실수란, float이나 double형을 말하는데 C언어 등 주요 언어에서 사용되는 소수점의 형태이다. 부동 소수점은 표현하는 수의 범위를 크게 만들기 위해 정밀함을 포기 했는데, 때문에 릴리즈 빌드에서 부동 소수점 실수는 값이 0.000001f(EPSILON)만큼 소실된다. 이것은 소수점끼리의 연산에서는 큰 지장이 없을 때가 많지만, 정수와 곱해줄 때 1.0f에서, 0.000001f 만큼 소실되어 버리면, 0.999999f 가 되어, 0과 곱하는 것이 되어버려 큰 계산 착오를 일으키게 됩니다. 이 문제를 알고 있다고 해도, 소수점을 사용하는 코드 어느 한곳에서만 빼먹어도 곤란한 상황이 나올 수가 있기 때문에, 정밀한 소수점 연산 클래스를 만들어, 부동 소수점 실수를 아예 사용하지 않는다고도 하는데, 내장 타입 (Built In Type)만큼 빠른 건 없기 때문에, 부동 소수점을 사용하되 부동 소수점 실수 class를 만들어두고, 필요할 때 마다 EPSILON만큼 더 해주어 이 문제를 해결하기도 하는 등 소실되는 값을 보정해주는 방법이 더 선호되는 편입니다. const float EPSILON = 0.000001f; class CSafeFloat { private: float m_fData; public: float GetData() { return m_fData; } void operator=(const float &pfData) { m_fData = pfData + EPSILON; } bool operator==(const float &pfData) { If(abs(m_fData, pfData) <= EPSILON){ return true; } return false; } CSafeFloat(float fData) { m_fData = fData; } float operator+() { return m_fData + EPSILON; } ~CSafeFloat() { } }; 1. float(단정도) 형식과 double(배정도) 형식의 변수끼리는 비교하면 안된다. 같은 수를 가지더라도 틀릴 수 있다. 부동소수점 방식의 변수일 경우 허용 오차를 생각한 후 비교한다. (EPSILON 정도의 값) 2. 2진수를 사용하는 컴퓨터에서는 0.1 과 같이 2진수로 변환했을 때 무한소수로 바뀌는 수의 경우 정확히 표현하지 못한다. 3. 연산 결과가 마지막 소수 자리까지 정확하지 않을 수 있다. 유효 자리 수를 벗어나면 그 이후로는 정확한 연산이 되지 않는다. 4. 오차를 줄이기 위해 정수 연산을 먼저 한 후 부동 소수점 연산을 한다. 5. 최대한 비슷한 크기의 부동 소수점 연산을 한다. [출처] 부동 소숫점 실수 사용시 유의 사항|작성자 엘키 잘못된 EPSILON 사용예입니다. 참고하시기 바랍니다.
http://www.borlandforum.com/impboard/impboard.dll?action=read&db=bcb_tip&no=979 이경문님 감사요...^^ 충분히 이해했습니다... 사용예를 보지 않고 아까 경문님이 댓글 다신 2번째 글을 보고 naver에서 가져온 class 보고... 또 보고하여... 그리고 올려 주신 예제도 잘 보았습니다. 현재까지 제가 적용시킬 것은 사용자가 입력이 소수점 3짜리를 넘지 않기 때문에... + 000001 만해도 보정이 충분할 것이라 생각합니다. 그리고 그 다음 계산 부터는 정수화, 아까 까막님 처럼 1000을 곱해서 ms 단위로 처리를 할 것이기 때문에... 그렇게 염려하지 않습니다. 이유도 알았고... 앞으로 바램은 누가... 논리적으로 날카롭고 똑똑한 분들이... 보정 함수를 하나 만들어 주시는 것입니다. 제가 만들려고 해도... 이론적인 머리가 딸립니다... 만들어 내는 사람과 보고 아... 맞다는 하는 사람이 있듯이 저는 보고 아~ 이것다... 정도 구별할 정도의 수준 밖에 되지를 못합니다. 이런 것은 또 혼자 하면 마음에 부담도 클 것이고... 이곳에 똑똑한 분들이 고민하여 서로 의논해서... 하나 만들어 주시면... 감사하겠습니다. 물론 시일이 걸리고... 보정 추가 해야겠지요... 물론 이런 경우 만들고 나면 몇 줄 되지는 않겠지만,... 생각보다 난이도가 있을 것 같습니다. 답변을 주신 모든 분들께 감사를 드립니다 ^^
실수가 bit로 어떻게 표현되는지를 이해하시면 각각의 어플리케이션의 경우마다 충분히 대처하실 수 있을 것입니다.
http://www.borlandforum.com/impboard/impboard.dll?action=read&db=bcb_tip&no=980 정확히 말씀을 드리자면 "10진수에 익숙한 입장에서 소수점 몇자리까지만 정확하면 된다"라는 생각에 의해 프로그래밍을 하였을 경우 예상치 않은 결과를 나을 수도 있습니다. 이는 실수를 요즘에는 2진수로 처리를 하기 때문에 그렇습니다. 예를 들면 1.1은 십진수로 표현을 하면 소수점 한자리에서 딱 끝이 나지만 float로 표현을 하면 significand가 0x8ccccccccccc.... 가 되어 버립니다. 정확히 표현을 할 수가 없다는 거죠. 그래서 결국 0x8ccccd의 값으로 반올림을 해 버리는 거구요. 시간이 걸리더라도 실수를 컴퓨터에서 어떻게 표시를 하는지, 그리고 예전에는 10진수 기반으로 표시를 했다가 왜 2진수 기반으로 넘어 갔는지를 곰곰히 생각해 보시면 해답을 스스로 찾으실 수 있을 것입니다. 넵.... 알겠습니다.... 참 어렵군요.... 오류가 일어날 가능성이 있다... 현재까지는 시각을 입력하면... 0.05의 배수가 아니다 박스 띄우고 나가게 해두었습니다... 그런데 사용자 입장에서 이게 입력하는 데... 제법 귀찮다는 생각이 들었습니다. 그래서... 보정을 해서... 제 프로그램에서... 원하는 가까운 값으로 자동으로 바꾸어 주는 부분을 넣을 생각이었는 데... 그대로 두어야 할 것 같군요.... 넣어도 별 이상은 없을 것 같은 데... -.- 이번에는 다관절이라... 시각 입력이 몇개 더 추가되어서... 입력하는 사람이 짜쯩을 낼 것 같아... 이런 계산을 해보았는 데... 갈수록 태산이군요.... 여하튼... 위의 글들을 참고해서... 해결하도록 하겠습니다... 섬세하게... 신경써주셔서.. 너무나 감사합니다... ^^
쉽게 말씀을 드리자면
1/3은 10진수로 나타내면 0.3333333.... 끝이 없죠. 하지만 만약 사람의 손가락이 왼손, 오른손 합쳐서 10개가 아니고 3개라면, 그래서 전 세계적으로 3진수가 사용이 되었다면 1/3(10진수) 은 0.1(3진수)로 표시되었을 것입니다. 컴퓨터는 bit가 연산 단위의 가장 기본단위이기 때문에 사람 손가락 수에 상관 없이 컴퓨터는 2진수 기반으로 발전할 수 밖에 없습니다. 그러기 때문에 반드시 2진수의 이해가 필요합니다. 결론을 지어서 10진수로는 쉽게 표현이 가능한 소수가 2진수로 표현하기 힘든 경우가 생길 수도 있다는 정도를 염두에 두시면 될 것 같습니다. 역시 이경문님 똑똑하십니다. 제가 나대로 실수 + 0.000001 이렇게 보정을 하여 실험을 하니
문제가 발생하는 군요... -.- 그러다가... 갑자기 생각났습니다. 보정 방법.... 저의 7번째 댓글 내용이 머리를 스치고 지나 갔습니다. "..... ANSI의 기준을 따른다면... ShowMessage에서는 VC++처럼 보정을 하여 보여주는 군요.... ShowMessage( FloatToStr(Value) ); 이것도 마찬가지군요... 보정하는 방법도 다 알고 있으면서... int로 넘어 갈때도 해주었다면 통일성도 있고 좋았을 것인 데... 아깝다.... " 어악~~~~~ 답이 여기에 있네요... ㅋㅋㅋㅋㅋ 천재들이 만든 C++Builder의 함수를 그대로 사용 하면 뭐 효율은 그렇게 빠르지 않겠지만... 나름대로 훌륭한 실수 보증 함수를 만들 수 있겠구나!!!! 어느 정도 검정도 되었고... ㅋㅋㅋ 그래서 만들었습니다. 풀그림 오래 하다보니 하였튼 잔꾀만 많이 늘어서... ㅋㅋㅋ 이론, 논리 이런 것 더 발달해야 하는 데... 늘 이렇게 잔꾀만 부리는... 한심한 월천 double Value = 1.15; int n = Value / 0.05; Value = n * 0.05; ShowMessage( Value ); //1.1 Error... 1.15가 나와야 하는 데.... //--------------------------------------------------- Value = 1.15; Value /= 0.05; Value = StrToFloat( FloatToStr( Value ) ); // 이 과정을 거치면 실수가 보정되어... n = Value; // 22.9999 아니라 23.0 이 들어가게 됩니다. Value = n * 0.05; ShowMessage( Value ); //1.1 이 아닌 1.15 정확히 나오는 군요. 정상 짝짝, 1.175를 대입해도 에고~~~~~ 지금 나대로 보정해서 실험하니 안돼서... 눈만 껌벅이다가... 이 간단한 것을.... 알고 적용하니 간단히 해결되었습니다. ^^ O Happy Day~~~ 실수 보정하실 분들... 위의 것을 사용하십시오... 감사합니다 ^^ 다시 한번 댓글에 동참해 주신 모든 분들께 감사를 드립니다. 이렇게... 계속... 댓글을 달면서... 생각하지 않았다면... 발견하지 못했을... 것을..... 아~~ 흑~~~ 울고 싶다... 정말 감사합니다 ^^ 관련 글 리스트
|
Copyright © 1999-2015, borlandforum.com. All right reserved. |
외국에서는 반올림 기법(?)이 우리랑 좀 개념이 다르다고 알고 있습니다.
무슨무슨법이라고 했었는데, 기억이... ㅠ.ㅠ
홀수인가 짝수인가로 올린다고...
실제 전체값에서는 우리 처럼 반올림하는 것보다 정확도가 높다고 하더군요.
이에 대해서 몇번 글이 올라왔었고, 그 답변도 나왔었던 것으로 기억합니다.
델마당에서였나... ^^
혼이 살아 있을까.... 대한민국.