이 사이트의 답변 중 일부를 읽었으며 printf
반올림이 이상적이라는 것을 알았습니다.
그러나 실제로 사용할 때 미묘한 버그로 인해 다음과 같은 동작이 발생합니다.
$ echo 197.5 | xargs printf '%.0f'
198
$ echo 196.5 | xargs printf '%.0f'
196
$ echo 195.5 | xargs printf '%.0f'
196
반올림은 196.5
가 됩니다 196
.
나는 이것이 아마도 미묘한 부동 소수점 오류일 것이라는 것을 알고 있습니다(그러나 그것은 큰 숫자는 아닙니다, 그렇죠?). 그래서 누구든지 이것에 대해 밝힐 수 있습니까?
이에 대한 해결 방법도 매우 환영합니다(지금은 이를 실행에 옮기려고 노력 중입니다).
답변1
예상대로 "반올림" 또는 "은행원의 반올림"입니다.
ㅏ관련 웹사이트 답변설명하다.
이 규칙이 해결하려는 문제는 (소수점 첫째 자리까지의 숫자에 대해),
- x.1에서 x.4로 내림합니다.
- x.6에서 x.9로 반올림합니다.
아래로 4개, 위로 4개입니다.
반올림의 균형을 유지하려면 x.5를 반올림해야 합니다.
- 위로한 번 그리고아래에다음.
이는 "가장 가까운 '짝수'로 반올림"이라는 규칙에 따라 수행됩니다.
코드에서:
쉿 LC_NUMERIC=C printf '%.0f ' "$value"
앗 echo "$value" | awk 'printf( "%s", $1)'
옵션:
숫자를 반올림하는 방법에는 총 4가지가 있습니다.
- 은행원의 법칙을 설명했습니다.
- +무한대 방향으로 반올림합니다. 반올림(양수의 경우)
- -무한대 방향으로 반올림합니다. 내림(양수의 경우)
- 0을 향해 반올림합니다. 소수(양수 또는 음수)를 제거합니다.
위로
정말로 "반올림 +infinite
"이 필요한 경우 awk를 사용할 수 있습니다.
value=195.5
앗 echo "$value" | awk '{ printf("%d", $1 + 0.5) }'
기원전 echo "scale=0; ($value+0.5)/1" | bc
아래에
정말로 "내림(방향으로 -infinite
)"이 필요한 경우 다음을 사용할 수 있습니다.
value=195.5
앗 echo "$value" | awk '{ printf("%d", $1 - 0.5) }'
기원전 echo "scale=0; ($value-0.5)/1" | bc
소수 자릿수를 자릅니다.
소수점(점 뒤의 모든 것)을 제거합니다.
쉘을 직접 사용할 수도 있습니다(대부분의 쉘에서 작동 - POSIX).
value="127.54" ### Works also for negative numbers.
껍데기 echo "${value%%.*}"
앗 echo "$value"| awk '{printf ("%d",$0)}'
기원전 echo "scale=0; ($value)/1" | bc
답변2
이는 실수가 아니라 의도적인 것입니다.
일종의 가장 가까운 반올림을 수행합니다(나중에 자세히 설명).
말 그대로 .5
우리는 어느 쪽이든 갈 수 있었습니다. 학교에서는 아마도 반올림하라는 말을 들었을 것입니다. 그런데 왜일까요? 3.51을 4로 반올림하거나 3.5를 반대 방향으로 계산할 수도 있지만 첫 번째 숫자만 보고 0.5를 반올림하면 항상 올바른 결과를 얻을 수 있습니다.
그러나 소수점 이하 2자리 집합(0.00 0.01, 0.02, 0.03 … 0.98, 0.99)을 보면 100개의 값이 있고 1은 정수이고 49는 반올림해야 하고 49는 반내림해야 하며, 1 ( 0.50 )은 에테르 경로를 택할 수 있습니다. 항상 반올림하면 0.01 더 큰 평균 숫자를 얻게 됩니다.
범위를 0 → 9.99로 확장하면 반올림하면 9개의 추가 값을 얻게 됩니다. 따라서 우리의 평균은 예상보다 약간 큽니다. 따라서 이 문제를 해결하기 위한 한 가지 시도는 다음과 같습니다. 0.5 라운드는 짝수가 되는 경향이 있습니다. 시간의 절반을 반올림하고 시간의 절반을 내림합니다.
그러면 편향이 위쪽에서 짝수로 변경됩니다. 대부분의 경우 이것이 더 좋습니다.
답변3
반올림 모드를 일시적으로 변경하는 것은 드문 일이 아닙니다 bin/printf
.그 자체소스를 변경해야 합니다.
coreutils의 소스 코드가 필요합니다. 저는 오늘 사용 가능한 최신 버전을 사용했습니다.http://ftp.gnu.org/gnu/coreutils/coreutils-8.24.tar.xz.
원하는 디렉토리에 압축을 푼다
tar xJfv coreutils-8.24.tar.xz
소스 디렉터리로 전환
cd coreutils-8.24
src/printf.c
선택한 편집기에 파일을 로드 하고 헤더 파일 을 포함하는 두 개의 전처리기 지시문 을 main
포함하여 전체 함수를 다음 함수로 바꾸고 파일 끝에 . 파일의 맨 끝math.h
fenv.h
int main...
}
#include <math.h>
#include <fenv.h>
int
main (int argc, char **argv)
{
char *format;
char *rounding_env;
int args_used;
int rounding_mode;
initialize_main (&argc, &argv);
set_program_name (argv[0]);
setlocale (LC_ALL, "");
bindtextdomain (PACKAGE, LOCALEDIR);
textdomain (PACKAGE);
atexit (close_stdout);
exit_status = EXIT_SUCCESS;
posixly_correct = (getenv ("POSIXLY_CORRECT") != NULL);
// accept rounding modes from an environment variable
if ((rounding_env = getenv ("BIN_PRINTF_ROUNDING_MODE")) != NULL)
{
rounding_mode = atoi(rounding_env);
switch (rounding_mode)
{
case 0:
if (fesetround(FE_TOWARDZERO) != 0)
{
error (0, 0, _("setting rounding mode to roundTowardZero failed"));
return EXIT_FAILURE;
}
break;
case 1:
if (fesetround(FE_TONEAREST) != 0)
{
error (0, 0, _("setting rounding mode to roundTiesToEven failed"));
return EXIT_FAILURE;
}
break;
case 2:
if (fesetround(FE_UPWARD) != 0)
{
error (0, 0, _("setting rounding mode to roundTowardPositive failed"));
return EXIT_FAILURE;
}
break;
case 3:
if (fesetround(FE_DOWNWARD) != 0)
{
error (0, 0, _("setting rounding mode to roundTowardNegative failed"));
return EXIT_FAILURE;
}
break;
default:
error (0, 0, _("setting rounding mode failed for unknown reason"));
return EXIT_FAILURE;
}
}
/* We directly parse options, rather than use parse_long_options, in
order to avoid accepting abbreviations. */
if (argc == 2)
{
if (STREQ (argv[1], "--help"))
usage (EXIT_SUCCESS);
if (STREQ (argv[1], "--version"))
{
version_etc (stdout, PROGRAM_NAME, PACKAGE_NAME, Version, AUTHORS,
(char *) NULL);
return EXIT_SUCCESS;
}
}
/* The above handles --help and --version.
Since there is no other invocation of getopt, handle '--' here. */
if (1 < argc && STREQ (argv[1], "--"))
{
--argc;
++argv;
}
if (argc <= 1)
{
error (0, 0, _("missing operand"));
usage (EXIT_FAILURE);
}
format = argv[1];
argc -= 2;
argv += 2;
do
{
args_used = print_formatted (format, argc, argv);
argc -= args_used;
argv += args_used;
}
while (args_used > 0 && argc > 0);
if (argc > 0)
error (0, 0,
_("warning: ignoring excess arguments, starting with %s"),
quote (argv[0]));
return exit_status;
}
./configure
다음과 같이 실행
LIBS=-lm ./configure --program-suffix=-own
-own
모든 서브루틴을 설치하고 시스템의 나머지 부분에 맞는지 확신할 수 없는 경우를 대비하여 각 서브루틴(많은 서브루틴)에 접미사를 추가합니다 . coreutils의 이름이 지정되지 않았습니다.핵무기사용할 이유가 없습니다!
하지만 가장 중요한 것은 LIBS=-lm
맨 앞에 서는 것입니다. 명령에서 ./configure
필수 라이브러리 목록에 추가하라고 지시하는 수학 라이브러리가 필요합니다.
Make를 실행
make
멀티코어/멀티프로세서 시스템을 갖고 있다면 시도해 보세요.
make -j4
여기서 숫자(여기서는 "4")는 해당 작업을 위해 기꺼이 절약할 코어 수를 나타내야 합니다.
printf
모든 것이 잘 진행되면 새로운 int 를 얻게 됩니다 src/printf
. 시도 해봐:
BIN_PRINTF_ROUNDING_MODE=1 ./src/printf '%.0f\n' 196.5
BIN_PRINTF_ROUNDING_MODE=2 ./src/printf '%.0f\n' 196.5
두 명령의 출력은 달라야 합니다. 다음 숫자는 다음을 IN_PRINTF_ROUNDING_MODE
의미합니다.
- 00을 향해 반올림
- 1가장 가까운 숫자로 반올림(기본값)
- 2양의 무한대로 반올림
- 삼음의 무한대로 반올림
전체 파일을 설치하거나(권장하지 않음) 파일(강력히 권장되기 전에 이름을 바꾸었음)을 src/printf
디렉터리에 복사 PATH
하고 위와 같이 사용할 수 있습니다.
답변4
실제로 원하는 것이 x.1을 x.4로 줄이고 x.5를 x.9로 반올림하는 것이라면 다음과 같은 짧은 한 줄 작업을 수행할 수 있습니다.
if [[ ${a#*.} -ge "5" ]]; then a=$((${a%.*}+1)); else a=${a%.*}; fi
또는 "5"를 "6"과 같이 원하는 대로 변경하세요.
PS: 소수 구분 기호로 사용되는 "." 문제에 대한 간단한 일반적인 해결책은 다음과 같습니다.
if [[ ${a##*[.,]} -ge "5" ]]; then a=$((${a%[.,]*}+1)); else a=${a%[.,]*}; fi