Почему бы и нет, если заведомо известно, что аргумент неотрицателен и влезет в int, а нам нужно работать именно с int? В этом случае `int(x+0.5)` будет значительно эффективнее по производительности, чем `int(round(x))` (операция roundss имеет больший latency, чем простое сложение).
Как вариант, потому что вот это "заведомо известно" потом превращается в изменения исходных требований, и опа — вылезли за границы допустимых входных данных :( С другой стороны, производительность это тоже серьезный аргумент.
Первый и третий кейс понятны. Но почему округление -0.5 должно приводить к -1? Математически верно приводить к 0 (ну или к -0, если есть это спецзначение).
Или в фразе
Я буду рассматривать округление к ближайшему целому, причём 0,5 будет округляться в большую сторону.
По крайней мере у round() в C такое же правило — цитирую по линуксовому ману:
These functions round x to the nearest integer, but round halfway cases away from zero (regardless of the current rounding direction, see fenv(3)), instead of to the nearest even integer like rint(3). For example, round(0.5) is 1.0, and round(-0.5) is -1.0.
В доступных текстах стандартов то же самое по сути.
Так что если это решение не идеальное, то по крайней мере традиционное.
(В идеале, я бы предпочёл видеть или дополнительный параметр направления округления, или раздельные функции для каждого режима округления. Но сейчас и введение только одного варианта — уже успех.)
Вообще говоря, округление — это чуть более сложная и разнообразная операция с числами, чем это преподаётся в школе. И самый спорный фактор тут, как правило, куда сдвигать середину (0.5). Вот лишь возможные очевидные ответы: большему/меньшему целому, большему/меньшему целому по модулю, а также к ближайшему чётному/нечётному. У каждого из этих подходов есть плюсы и минусы.
Округление к меньшему по модулю имеет вот такое замечательное свойство: round(x) + round(-x) == 0
В большинстве случаев, встречается округление к меньшему как поведение по умолчанию. Статья на вики
Округление к меньшему по модулю имеет вот такое замечательное свойство: round(x) + round(-x) == 0
Округление к чётному, к нечётному, к нулю (к меньшему по модулю) и от нуля — все одновременно имеют это свойство.
В большинстве случаев, встречается округление к меньшему как поведение по умолчанию.
Хм… вообще-то в большинстве случаев или к ближайшему, а середину — от нуля (как в обсуждаемой реализации), или к ближайшему, а посредине — к чётному (банкирское, оно же гауссово округление). В школе нас учили единственному варианту, что округление — к ближайшему, а середину — от нуля.
В терминах IEEE754-2008 это соответственно roundToNearestTiesAway и roundToNearestTiesToEven.
Статья на вики
Статья хорошая как вводной обзор, но сильно неполная.
Нет, например, фоннеймановского округления, про которое см. документацию IBM:
Round to prepare for shorter precision: For a BFP or HFP permissible set (двоичная арифметика), the candidate selected is the one whose voting digit has an odd value. For a DFP permissible set (десятичная арифметика), the candidate that is smaller in magnitude is selected, unless its voting digit has a value of either 0 or 5; in that case, the candidate that is greater in magnitude is selected.
Часто или редко встречается округление к меньшему, это, конечно, открытый вопрос. решил проинспектировать свой инструментарий. Получилось примерно поровну
PHP round(-0.5) = -1
Exasol select round(-0.5) = -1
JS Math.round(-0.5) = 0
Java Math.round(-0.5) = 0
MySQL select round(-0.5) = -1
C round(-0.5) is -1.0
R round(-0.5) = 0
Erlang round(-0.5) = -1
Чтобы понять, какой именно вариант работает в каждом конкретном случае, надо сравнивать не один случай -0.5, а несколько: например: -1.5, -0.5, +0.5, +1.5.
Тогда получается соответственно для этих значений (в том же порядке, что у Вас; Exasol не знаю, пропускаю):
PHP round(): -2, -1, 1, 2 (середина — от нуля)
Javascript Math.round(): -1, 0, 1, 2 (середина — к +INF)
Java Math.round(): -1, 0, 1, 2 (середина — к +INF)
(но рядом есть rint(), который округляет середину к чётному: -2, 0, 0, 2)
MySQL select round(): -2, -1, 1, 2 (середина — от нуля)
C round(): -2, -1, 1, 2 (середина — от нуля)
R round(): -2, 0, 0, 2 (середина — к чётному)
Erlang round(): -2, -1, 1, 2 (середина — от нуля)
Кроме того:
Python3 round(): -2, 0, 0, 2 (середина — к чётному)
Perl POSIX::round: как в C (середина — от нуля)
как видите, до сих пор ни одного случая ни «середина к нулю», ни «середина к меньшему». Особый случай — Java и склонировавший её Javascript, где, наоборот, середина — к +INF (интересно, кто и зачем такое выбрал?)
Могу предположить следующее: $y равен 6999.999999… после умножения.
Функция которая печатает, округляет флоаты до определенного знака.
Приведение к инту до округления просто отбрасывает друбную часть (хоть она и почти единица), и на выходе имеем — то что имеем.
как-то так.
Меня несколько удивляют подобные реализации. Я так понимаю, весь сыр-бор из-за платформ, где нет соответствующих инструкций? Но ведь представление чисел с плавающей точкой стандартизировано, правильнее действительно будет работать напрямую с представлением.
«Но я надеюсь, что теперь вам стало понятно, как устроено округление в Go и как нужно тестировать реализации округления»
да использовать их надо, а не тестировать
программист он для заказчика программист, а для языка программирования пользователь
«корректно работающее округление появилось лишь в шестой мажорной версии Java (через 15 лет, прошедших с релиза Java 1.0 до выхода Java 7)»
15 лет… пендосиннопром жжет…
мне просто интересно, если бы такие базовые штуки, как округление, я выдавал бы своим пользователям через 15 лет,… да нет, я бы не дождался результатов теста, столько не проработал бы.
и ведь на этом языке до сих пор большая часть вакансий в нашем замкадье-захолустье
команда Go согласилась добавить math.Round в Go 1.10! И даже появилась работающая реализация
Подскажите, пожалуйста, как правильно на базе этой реализации округлять до двух знаков после запятой. Например, по коду получаю 63 и -63, а хочу получить 62,84 и -62,84:
Точное представление десятичных дробей в виде конечных двоичных дробей невозможно, поэтому ответ — никак. Можно умножить на 100, взять round, поделить на 100 и вывести число на экран со спецификатором "%.2f".
вот так: play.golang.org/p/d_OUvLXSWaJ
Вариант на базе math.Round, когда берём битовое представление числа, сдвигаем его и применяем маску…, — правильно подправить не получается.
больше никогда не буду писать от лени int(x+0.5)
Как вариант, потому что вот это "заведомо известно" потом превращается в изменения исходных требований, и опа — вылезли за границы допустимых входных данных :( С другой стороны, производительность это тоже серьезный аргумент.
А деление вычитанием в этом фантастическом языке делать не надо?
Именно так.
Или в фразе
имелось в виду в «большую по модулю»?
По крайней мере у round() в C такое же правило — цитирую по линуксовому ману:
These functions round x to the nearest integer, but round halfway cases away from zero (regardless of the current rounding direction, see fenv(3)), instead of to the nearest even integer like rint(3). For example, round(0.5) is 1.0, and round(-0.5) is -1.0.
В доступных текстах стандартов то же самое по сути.
Так что если это решение не идеальное, то по крайней мере традиционное.
(В идеале, я бы предпочёл видеть или дополнительный параметр направления округления, или раздельные функции для каждого режима округления. Но сейчас и введение только одного варианта — уже успех.)
Округление к меньшему по модулю имеет вот такое замечательное свойство: round(x) + round(-x) == 0
В большинстве случаев, встречается округление к меньшему как поведение по умолчанию.
Статья на вики
Округление к чётному, к нечётному, к нулю (к меньшему по модулю) и от нуля — все одновременно имеют это свойство.
Хм… вообще-то в большинстве случаев или к ближайшему, а середину — от нуля (как в обсуждаемой реализации), или к ближайшему, а посредине — к чётному (банкирское, оно же гауссово округление). В школе нас учили единственному варианту, что округление — к ближайшему, а середину — от нуля.
В терминах IEEE754-2008 это соответственно roundToNearestTiesAway и roundToNearestTiesToEven.
Статья хорошая как вводной обзор, но сильно неполная.
Нет, например, фоннеймановского округления, про которое см. документацию IBM:
Round to prepare for shorter precision: For a BFP or HFP permissible set (двоичная арифметика), the candidate selected is the one whose voting digit has an odd value. For a DFP permissible set (десятичная арифметика), the candidate that is smaller in magnitude is selected, unless its voting digit has a value of either 0 or 5; in that case, the candidate that is greater in magnitude is selected.
PHP round(-0.5) = -1
Exasol select round(-0.5) = -1
JS Math.round(-0.5) = 0
Java Math.round(-0.5) = 0
MySQL select round(-0.5) = -1
C round(-0.5) is -1.0
R round(-0.5) = 0
Erlang round(-0.5) = -1
Тогда получается соответственно для этих значений (в том же порядке, что у Вас; Exasol не знаю, пропускаю):
PHP round(): -2, -1, 1, 2 (середина — от нуля)
Javascript Math.round(): -1, 0, 1, 2 (середина — к +INF)
Java Math.round(): -1, 0, 1, 2 (середина — к +INF)
(но рядом есть rint(), который округляет середину к чётному: -2, 0, 0, 2)
MySQL select round(): -2, -1, 1, 2 (середина — от нуля)
C round(): -2, -1, 1, 2 (середина — от нуля)
R round(): -2, 0, 0, 2 (середина — к чётному)
Erlang round(): -2, -1, 1, 2 (середина — от нуля)
Кроме того:
Python3 round(): -2, 0, 0, 2 (середина — к чётному)
Perl POSIX::round: как в C (середина — от нуля)
как видите, до сих пор ни одного случая ни «середина к нулю», ни «середина к меньшему». Особый случай — Java и склонировавший её Javascript, где, наоборот, середина — к +INF (интересно, кто и зачем такое выбрал?)
Спасибо, вы правы, поправил текст.
… а вот здесь на выходе будет 6999?
Могу предположить следующее: $y равен 6999.999999… после умножения.
Функция которая печатает, округляет флоаты до определенного знака.
Приведение к инту до округления просто отбрасывает друбную часть (хоть она и почти единица), и на выходе имеем — то что имеем.
как-то так.
Так и есть (по крайней мере в double == float64).
Всё верно, а такое поведение связано с тем, что echo кастует все параметры к string следующим образом:
да использовать их надо, а не тестировать
программист он для заказчика программист, а для языка программирования пользователь
«корректно работающее округление появилось лишь в шестой мажорной версии Java (через 15 лет, прошедших с релиза Java 1.0 до выхода Java 7)»
15 лет… пендосиннопром жжет…
мне просто интересно, если бы такие базовые штуки, как округление, я выдавал бы своим пользователям через 15 лет,… да нет, я бы не дождался результатов теста, столько не проработал бы.
и ведь на этом языке до сих пор большая часть вакансий в нашем замкадье-захолустье
if math.IsNaN(x) {
return x
}
return math.Copysign(1.0, x) * math.Floor(math.Copysign(1.0, x) * x + 0.5)
Тесты я брал из статьи. В builtins_test.go теста для round не нашел.
Подскажите, пожалуйста, как правильно на базе этой реализации округлять до двух знаков после запятой. Например, по коду получаю 63 и -63, а хочу получить 62,84 и -62,84:
В других случаях, — умножал и делил на сто, пример здесь: play.golang.org/p/jE9Q8BSsHQt
Точное представление десятичных дробей в виде конечных двоичных дробей невозможно, поэтому ответ — никак. Можно умножить на 100, взять round, поделить на 100 и вывести число на экран со спецификатором "%.2f".
вот так: play.golang.org/p/d_OUvLXSWaJ
Вариант на базе math.Round, когда берём битовое представление числа, сдвигаем его и применяем маску…, — правильно подправить не получается.
Я всё-таки советую добавлять спецификатор точности при выводе, чтобы в какой-то момент не увидеть 62.840000152587890625 вместо 62.84.