[Ruby 1.8.6] Точность вычислений float

korishsasa

Всем привет
Ruby, я, к сожалению, практически не знаю, но возникла необходимость быстро разобраться в нектором коде и внести небольшие изменения.
Столкнулся со следующими непонятными для меня результатами (код дан для примера):

puts 1.00 - 0.91
puts 98.00 - 97.91
puts 3398.00 - 3397.91
puts 3398.00 - 3397.91
puts 97.55 - 97.20
puts 1.55 - 0.20
puts 1.00 - 0.94
puts 1.00 - 0.95
puts 1.00 - 0.96
puts 1.00 - 0.97
puts 1.00 - 0.98
puts 1.00 - 0.99
puts 1.00 - 1.00

выдает

0.09
0.0900000000000034
0.0900000000001455
0.0900000000001455
0.349999999999994
1.35
0.0600000000000001
0.05
0.04
0.03
0.02
0.01
0.0

Как правильно выполнять арифметические операции с дробными числами, чтобы получать правильные ответы без хвостов из девяток? Достачно точности до двух знаков после запятой. Хочется обойтись без округлений, так как подобных простых вычислений очень много и округления существенно увеличат общее время выполнения

apl13

:lol:

kruzer25

Округление не нужно, тебе надо просто вывести числа в нужном формате, процессорное время в любом случае на это потратится, вне зависимости от того, какой формат.
Так что, тебе вместо puts надо писать что-то вроде printf или что у вас там в этом руби.

vall

>Достачно точности до двух знаков после запятой.
деньги считаешь? float для этого не годится — теперь их модно считать в complex double :umn:

sbs-66

Где-то недавно читал, что в каком-то языке добавили для денег специальный тип с фиксированной точностью после запятой, а не плавающей.

apl13

dBase называется? :ooo:

sbs-66

вот уж хз, вроде нет.
Там язык какой-то популярный был, у него новая версия то ли вышла, то ли анонсирована, то ли начата разработка, и одно из нововведений - тип данных для денег. Как бы не JavaScript 2.0 то был

apl13

Некоторые языки, к примеру dBase, содержат дополнительный тип данных для чисел с фиксированной запятой. Они предназначены для более точных расчётов, к примеру в бухгалтерии. По сути, это "обычные" целые числа, у которых несколько последних знаков определены, как знаки после запятой. Соответственно, они и ведут себя так же, как целые числа. Всё, что написано в этой статье — не о них.

klyv

Может, C# - там есть тип, для денег предназначенный, decimal :)

tipnote

А че, decimal - это уже новинка? :shocked:

kruzer25

для денег специальный тип с фиксированной точностью после запятой, а не плавающей
У денег есть не только точность, но и валюта ;)

nikita270601

А че, decimal - это уже новинка?
bleeding edge!

bleyman

А я, кстати, слышал, что в каком-то новом динамическом языке, может даже в лиспе, есть специальный тип для рациональных чисел, где они как дроби хранятся! СЕНСАЦИЯ!

karkar

В OCaml'e есть такой: num - дробь из двух bigint'ов. Т.е. как бы вычисления с абсолютной точностью.

pilot

новом динамическом языке, может даже в лиспе,
:grin: :grin: Это пять :grin:

Vladislav177Rus

Мб, Currency в VB? :)
Вещественные числа с гарантированной точностью были еще в PL/1

yolki

интересно, один я увидел иронию в подсте Фиджи?

slonishka

нет, конечно. я думаю, все увидели

kruzer25

Т.е. как бы вычисления с абсолютной точностью
И как же там записывается корень из жвух, не гвооря уж о, не к ночи будь сказано, пи?

karkar

А как посчитаешь. Ты же их будешь вычислять с помощью арифметических операций над целыми и дробями, на каждом шаге будешь иметь дробь. Но в отличие от floating point никаких проблем с точностью и сравнениями..

kruzer25

То есть, с такими числами можно производить только сложение-вычитание, умножение-деление и возведение в целую степень? ;)

karkar

Типа того. А какие есть операции, которые выполняются не посредством этих?

kruzer25

Никаки, но в случае со стандартными float-ами понятно, где остановиться.
Скажи мне ещё раз, как у тебя будет представляться пи? То, что в обычных float-ах представляется как 3.141592653589...

Dasar

например, как: 3141592653589/10000000000000

vall

скорее как рациональный диапазон, вычисленный с нужной точностью

kruzer25

"Например" - это ясно.
Но как именно?
Если я захочу извлечь квадратный корень из двойки - что выйдет? Сейчас, насколько я понимаю, считается ряд тейлора, и там в какой-то момент становится ясно, что дальше считать смысла нет, потому что уже дошли до предела точности float. А для таких чисел как будет считаться корень из двойки?

Dasar

А для таких чисел как будет считаться корень из двойки?
до какой нужно точности
допустим до момента, когда разница между итерациями станет меньше, чем 1е-80

kruzer25

Ну вот ты опиши процесс.
Потому что когда мы считаем ряд тейлора ос обычными числами, каждое слагаемое тоже округлится. А если мы будем работать с дробями - общий знаменатель будет расти как факториал, и улетит за облака задолго до 1e-80.

Dasar

> общий знаменатель будет расти как факториал
для этого есть длинные целые

kruzer25

Бесконечно длинные?
Или всё-таки у тебя не будет возможности перемножать/складывать два _любых_ числа?
С обычными float-ами всё понятно, сложили 9.123456789 и 9.123456780, цифр стало не 10, а 11 - можно очень легко округлить до 10 цифр. А с дробями как округлять, если у нас получилось несокращаемое очень_длинное_число/другое_очень_длинное_число, а тебе надо получить и в числителе, и в знаменателе просто длинные числа - как округлишь, как подберёшь эти "просто длинные числа"?

vall

длинные сколько надо или сколько памяти есть
числа можно хранить и не в машинных типах

karkar

Короче, написал работающий пример.
Исходник на OCaml:
open Num;;

let rec f sum x n = if n>300 then sum else
let nx = x */ (num_of_int n) // (num_of_int (2*n+1 in f (sum +/ nx) nx (n+1);;

let p = (f (num_of_int 1) (num_of_int 1) 1) */ (num_of_int 2);;
print_endline( string_of_num p );;
print_endline( approx_num_fix 70 p );;

считает 300 итераций, выводит результат в виде обыкновенной дроби и в виде десятичной с точностью до 70 знаков (с какой точностью выводить задается при выводе). Результат работы:

3143948890252284171910047895193462472432227540361512494945598721505936502667835973439205345561363199906996017734139870652536798150284218174962017186312838837180963205619902903636996631357109054768960034556448743508950944516859950114935737185942259922641092608
/
1000750013423859563820178033935346647064503829511796580677054956997260837732792307343326317345949222660090872187560287545096899766581719139554528762116067391798485131051003599985219593074482918260000262818521318455672915323135522196723199336910693508934511625
+3.1415926535897932384626433832795028841971693993751058209749445923078164

serega1604

ты из рублей или из баксов что-ли собрался корень извлекать?

ppplva

Можно из квадратных рублей.

slonishka

money is the root of the evil! (c)

Dasar

> Бесконечно длинные?
смотря какая точность нужна, если точность нужна бесконечно длинная - то да, значит числа будут бесконечно длинные.
если точность нужна какая-то разумная, то дробь будет нормализоваться до этой заданной точности, и бесконечных чисел уже не будет

kruzer25

дробь будет нормализоваться до этой заданной точности
Можно поподробнее?

kruzer25

А деньги не имеет смысла умножать или корни извлекать.
Просто смотри, как бы у тебя не получилось, что 1 рубль + 1 фунт = 2.

mkrec

разве для сложения сумм, пусть даже в разных валютах, требуются более сложные операции, чем деление (а обычно даже умножение)?

Dasar

> Можно поподробнее?
если точность 1%, то дробь 101/10000 - можно нормализовать до 1/100

kruzer25

дробь 101/10000 - можно нормализовать до 1/100
Это очень хорошие дроби, те же float-ы.
А что будешь делать с дробью 1234/56789?

kruzer25

Для сложения денег требуется сложная операция.
А перемножать или делить деньги друг на друга никому не нужно, это бессмысленно.

mkrec

для сложения денег требуется простая операция. Если ты подразумеваешь учет сложных правил, по которым, скажем, 1USD/EUR*1EUR/RUR!=1USD/RUR, то эти тонкости не имеют никакого отношения к возможностям рациональных типов.
Умножать и делить деньги очень даже полезно. Только не друг на друга, а на другие величины.

Dasar

> А что будешь делать с дробью 1234/56789?
решение в лоб, нормализовать также как и в предыдущем примере, до десятичной дроби:
1234/56789 = 21/1000

kruzer25

1USD/EUR*1EUR/RUR
Чего-чего?
для сложения денег требуется простая операция
Чему равен 1USD + 1RUR? Если с деньгами работать как с числами, не глядя на валюту - ты получишь 2. 2 чего?
Только не друг на друга, а на другие величины
А я о чём?

kruzer25

Ну вот и получили мы говно. Что, если знаменатель оказывается слишком большим - это число у нас оказывается стандартным float-ом, причём, никто никогда не знает, когда это произойдёт.
Всякие там корни из двух и прочие пи - окажутся float-ами совершенно точно, а остальные?
Ничего, что 22/7 - более точное приближение пи, чем 3.14?

karkar

Да, если хочешь ограниченную точность, то и используй float. Хочешь абсолютную - используй дроби из сколь-угодно-больших-целых. Решение с дробями ограниченной точности действительно как минимум странное..

kruzer25

Решение с дробями ограниченной точности действительно как минимум странное..
+100.
Но в решении с дробями абсолютной точности не получится делать никаких не-рациональных операций. Даже корень извлечь.

karkar

Отлично получается - рациональное приближение (см. мой пример с Пи). Иррациональные же числа никак не представишь..

kruzer25

Отлично получается - рациональное приближение (см. мой пример с Пи).
Ты уверен, что в твоём примере с пи ты получишь самую близкую к пи дробь с знаменателем, не больше заданного? Я уверен, что нет.
А с float-ами всё понятно, пи больше 3.14, но меньше 3.15 - значит, они и будут его приближениями.

mkrec

> Если с деньгами работать как с числами, не глядя на валюту
так "с деньгами" или "не глядя на валюту"? Если ты будешь суммировать кроликов с клетками, ты получишь что? Или на основании этого примера ты будешь утверждать, будто в реальном мире невозможно пользоваться натуральными числами?
> А я о чём?
делить, кстати, рубли на доллары можно

kruzer25

Если ты будешь суммировать кроликов с клетками, ты получишь что?
Вот-вот.
Только в случае денег кролики с клетками немного связаны, и можно в каждый конкретный момент узнать, сколько будет 5 кроликов + 3 клетки в кроликах.
делить, кстати, рубли на доллары можно
Но не нужно.

karkar

>Ты уверен, что в твоём примере с пи ты получишь самую близкую к пи дробь с знаменателем, не больше заданного?
А откуда ограничение про знаменатель? Я получу в точности значение суммы ряда из заданного числа слагаемых. Насколько это число будет близко к пи - зависит от использованного метода вычисления..
Оставить комментарий
Имя или ник:
Комментарий: