Float not equal after division followed by multiplication

眉间皱痕 提交于 2020-01-06 08:18:12

问题


I'm testing a small and simple library I made in Ruby. The goal is to convert from EUR to CNY and vice versa. Simple.

I tested it to be sure everything works but I got an unexpected issue. When I use to_euro followed by to_yuan it should go back to the original amount ; it doesn't happen. I tried to .to_f or round(2) the amount variable which fix some tests, raise new ones, but it's never equal to what I expect globally ; I'm running out of idea to fix this :(

class Currency

  attr_reader :amount, :currency

  def initialize(amount, currency='EUR')
    @amount = amount
    @currency = currency
  end

  def to_yuan
    update_currency!('CNY', amount * Settings.instance.exchange_rate_to_yuan)
  end

  def to_euro
    update_currency!('EUR', amount / Settings.instance.exchange_rate_to_yuan)
  end

  def display
    "%.2f #{current_symbol}" % amount
  end

  private

  def current_symbol
    if currency == 'EUR'
      symbol = Settings.instance.supplier_currency.symbol
    elsif currency == 'CNY'
      symbol = Settings.instance.platform_currency.symbol
    end
  end

  def update_currency!(new_currency, new_amount)
    unless new_currency == currency
      @currency = new_currency
      @amount = new_amount
    end
    self
  end

end

Tests

describe Currency do

  let(:rate) { Settings.instance.exchange_rate_to_yuan.to_f }

  context "#to_yuan" do

    it "should return Currency object" do
      expect(Currency.new(20).to_yuan).to be_a(Currency)
    end

    it "should convert to yuan" do
      expect(Currency.new(20).to_yuan.amount).to eql(20.00 * rate)
    end

    it "should convert to euro and back to yuan" do
      # state data test
      currency = Currency.new(150, 'CNY')
      expect(currency.to_euro).to be_a(Currency)
      expect(currency.to_yuan).to be_a(Currency)
      expect(currency.amount).to eql(150.00)
    end

  end

  context "#to_euro" do

    it "should convert to euro" do
      expect(Currency.new(150, 'CNY').to_euro.amount).to eql(150 / rate)
    end

  end

  context "#display" do

    it "should display euros" do
      expect(Currency.new(10, 'EUR').display).to eql("10.00 €")
    end

    it "should display yuan" do
      expect(Currency.new(60.50, 'CNY').display).to eql("60.50 ¥")
    end

  end


end

And here's my RSpec result

I'm pretty sure this problem is very common, any idea how to solve it easily ?


回答1:


Float isn't an exact number representation, as stated in the ruby docs:

Float objects represent inexact real numbers using the native architecture's double-precision floating point representation.

This not ruby fault, as floats can only be represented by a fixed number of bytes and therefor cannot store decimal numbers correctly.

Alternatively, you can use ruby Rational or BigDecimal

Its is also fairly common to use the money gem when dealing with currency and money conversion.



来源:https://stackoverflow.com/questions/38702439/float-not-equal-after-division-followed-by-multiplication

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!