Последовательное округление чисел с плавающей запятой в Ruby

Я понимаю, что из-за неточного представления чисел с плавающей запятой следующий код «кажется» непоследовательным.

"%.1f" % 1.14 # => 1.1
"%.1f" % 1.15 # => 1.1
"%.1f" % 1.16 # => 1.2
"%.0f" % 1.4 # => 1
"%.0f" % 1.5 # => 2
"%.0f" % 1.6 # => 2

Однако есть ли простой способ последовательного округления с плавающей запятой на 5? Одним из способов может быть явное манипулирование строками. Есть ли более простой способ или существующая библиотека?


person bryantsai    schedule 23.12.2009    source источник


Ответы (4)


Если вам нужна десятичная точность, используйте BigDecimal вместо чисел с плавающей запятой. .

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

"%.1f" % BigDecimal('1.15').round(1) => "1.2"
"%.0f" % BigDecimal('1.5').round(0) => "2"
person Tobias Cohen    schedule 23.12.2009
comment
дополнительный вопрос: всегда ли x.to_s для числа с плавающей запятой дает x (например, 1.15.to_s => '1.15')? или также повлияет на неточное представление? - person bryantsai; 23.12.2009
comment
Извините, я не уверен в этом. Судя по моим тестам, все проходит нормально, однако Float#to_s, кажется, усекает любое число, содержащее более 15 цифр. - person Tobias Cohen; 23.12.2009

Просто добавьте крошечное возмущение, чтобы гарантировать, что вещи, которые чуть меньше 0,5 в плавающей запятой, станут чуть выше.

Например,

x = 1.15
"%.1f" % (1.000001*x)  # include correction for imprecise floating-point.

этого будет достаточно для решения проблем с форматированием, хотя очень маловероятно, что это вызовет соответствующую ошибку.

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

person Peter    schedule 23.12.2009
comment
Разве это не повлияет на какое-то значение, просто слегка ударив 0,5? - person bryantsai; 23.12.2009
comment
Ну, значение (1/1.000001)*(1.1499999999) будет неправильно округлено, но, учитывая, что вы печатаете значения только для отображения, я думаю, что это нормально. Если это проблема, просто добавьте еще несколько нулей. - person Peter; 23.12.2009

Функция roundthis() в этом примере показывает, как округлять числа управляемым и согласованным способом. Обратите внимание на небольшое значение выдумки. Попробуйте запустить этот пример без выдумки, чтобы увидеть, что произойдет.

def roundthis(x, m)
    return (x/m+0.50001).floor*m
end

for x in [1.14, 1.15, 1.16]
    print "#{x}   #{roundthis(x, 0.1)}  \n"
end

for x in [1.4, 1.5, 1.6]
    print "#{x}   #{roundthis(x, 1.0)}  \n"
end

Это поместите в файл с именем roundtest.rb и выполните печать

bash> ruby roundtest.rb
1.14   1.1  
1.15   1.2  
1.16   1.2  
1.4   1.0  
1.5   2.0  
1.6   2.0  

Обратите внимание на простоту округления до ближайших 2, 15, 0,005 и т. д.

person DarenW    schedule 23.12.2009

Умножьте на 100, затем округлите, затем разделите на 100:

(1.15 * 100).round / 100.0 # => 1.15

Это не совсем элегантно, но позволяет избежать использования строк.

person Alex Reisner    schedule 23.12.2009
comment
это неправильно - проблема все еще остается, потому что 1.15 теперь представляется внутренне и неточно, с использованием чисел с плавающей запятой. Опять же, как только вы придете, чтобы показать это, вы получите ответы, которых не ожидаете. - person Peter; 23.12.2009