JavaScript'te Hassas Finansal Hesaplama. Gotcha'lar Nelerdir?


125

Çapraz platform kodu oluşturmak adına, JavaScript'te basit bir finansal uygulama geliştirmek istiyorum. Gerekli hesaplamalar, bileşik faiz ve nispeten uzun ondalık sayıları içerir. Bu tür bir matematik yapmak için JavaScript kullanırken hangi hatalardan kaçınmam gerektiğini bilmek istiyorum - eğer mümkünse!

Yanıtlar:


107

Muhtemelen ondalık değerlerinizi 100 ile ölçeklendirmeli ve tüm parasal değerleri tam sent olarak göstermelisiniz. Bu, kayan nokta mantığı ve aritmetik ile ilgili sorunları önlemek içindir . JavaScript'te ondalık veri türü yoktur - tek sayısal veri türü kayan noktadır. Bu nedenle paranın dolar 2550yerine sent olarak kullanılması önerilir 25.50.

JavaScript'te şunu düşünün:

var result = 1.0 + 2.0;     // (result === 3.0) returns true

Fakat:

var result = 0.1 + 0.2;     // (result === 0.3) returns false

İfade 0.1 + 0.2 === 0.3geri döner false, ancak neyse ki kayan noktadaki tamsayı aritmetiği tamdır, bu nedenle 1'i ölçeklendirerek ondalık gösterim hataları önlenebilir .

Gerçek sayılar kümesi sonsuz olsa da, yalnızca sonlu sayılarının (tam olarak 18,437,736,874,454,810,627) JavaScript kayan nokta biçimi ile tam olarak temsil edilebileceğini unutmayın. Bu nedenle, diğer sayıların temsili, gerçek sayı 2'nin yaklaşık bir tahmini olacaktır .


1 Douglas Crockford: JavaScript: İyi Parçalar : Ek A - Korkunç Parçalar (sayfa 105) .
2 David Flanagan: JavaScript: The Definitive Guide, Dördüncü Baskı : 3.1.3 Floating-Point Literals (sayfa 31) .


3
Ve bir hatırlatma olarak, hesaplamaları her zaman sente yuvarlayın ve bunu tüketiciye en az fayda sağlayacak şekilde yapın, IE Vergiyi hesaplıyorsanız, yuvarlayın. Kazanılan faizi hesaplıyorsanız, kısaltın.
Josh

6
@Cirrostratus: stackoverflow.com/questions/744099 adresini kontrol etmek isteyebilirsiniz . Ölçekleme yöntemiyle devam ederseniz, genel olarak değerinizi, hassasiyeti korumak istediğiniz ondalık basamak sayısına göre ölçeklendirmek istersiniz. 2 ondalık basamağa ihtiyacınız varsa, 100 ile ölçeklendirin, 4'e ihtiyacınız varsa 10.000'e ölçekleyin.
Daniel Vassallo

2
... 3000.57 değeriyle ilgili olarak, evet, bu değeri JavaScript değişkenlerinde saklıyorsanız ve üzerinde aritmetik yapmak istiyorsanız, 300057'ye (sent sayısı) ölçeklenmiş olarak saklamak isteyebilirsiniz. Çünkü 3000.57 + 0.11 === 3000.68geri döner false.
Daniel Vassallo

7
Dolar yerine kuruş saymak yardımcı olmayacaktır. Kuruşları sayarken, yaklaşık 10 ^ 16'da bir tam sayıya 1 ekleme yeteneğini kaybedersiniz. Doları sayarken, 10 ^ 14'teki bir sayıya 0,01 ekleme yeteneğini kaybedersiniz. Her iki şekilde de aynı.
slashingweapon

1
Bu göreve yardımcı olacağını umduğum bir npm ve Bower modülü oluşturdum !
Pensierinmusica

18

Çözüm, her değeri 100 ölçeklendirmektir. Bunu elle yapmak muhtemelen işe yaramaz çünkü bunu sizin için yapan kütüphaneler bulabilirsiniz. ES6 uygulamaları için çok uygun işlevsel bir API sunan moneysafe'i öneririm:

const { in$, $ } = require('moneysafe');
console.log(in$($(10.5) + $(.3)); // 10.8

https://github.com/ericelliott/moneysafe

Hem Node.js'de hem de tarayıcıda çalışır.


2
Upvoted. "100'e göre ölçekleme" puanı zaten kabul edilen yanıtta kapsanmıştır, ancak modern JavaScript sözdizimine sahip bir yazılım paketi seçeneği eklemeniz iyi olur. FWIW in$, $ değer adları, paketi daha önce kullanmamış biri için belirsizdir. Nesneleri bu şekilde adlandırmanın Eric'in seçimi olduğunu biliyorum, ancak yine de bunları içe aktar / yok edilmiş gerekli ifadesinde yeniden adlandırmamın yeterli bir hata olduğunu düşünüyorum.
james_womack

4
Yüzde hesaplama gibi bir şey yapmak isteyene kadar (esasen bölme gerçekleştirin) sadece 100 ölçeklendirme yardımcı olur.
Pointy

2
Keşke bir yorumu birden çok kez yükseltebilseydim. 100 ölçeklendirmek yeterli değildir. JavaScript'teki tek sayısal veri türü hala bir kayan nokta veri türüdür ve yine de önemli yuvarlama hatalarıyla sonuçlanacaksınız.
Craig

1
Ve Benioku'da kadar başka kafalar: Money$afe has not yet been tested in production at scale.. Herkesin kendi kullanım durumu için uygun olup olmadığını düşünebilmesi için bunu belirtmek yeterli
Nobita

7

Sadece iki ondalık kesir rakamı nedeniyle "kesin" finansal hesaplama diye bir şey yoktur, ancak bu daha genel bir problemdir.

JavaScript'te, her değeri 100'e ölçekleyebilir ve Math.round()her kesir oluştuğunda kullanabilirsiniz.

Numaraları depolamak ve yuvarlamayı prototip valueOf()yöntemine dahil etmek için bir nesne kullanabilirsiniz . Bunun gibi:

sys = require('sys');

var Money = function(amount) {
        this.amount = amount;
    }
Money.prototype.valueOf = function() {
    return Math.round(this.amount*100)/100;
}

var m = new Money(50.42355446);
var n = new Money(30.342141);

sys.puts(m.amount + n.amount); //80.76569546
sys.puts(m+n); //80.76

Bu şekilde, bir Money nesnesini her kullandığınızda, iki ondalık sayıya yuvarlanmış olarak temsil edilecektir. Yuvarlanmamış değere, üzerinden hala erişilebilir m.amount.

İsterseniz kendi yuvarlama algoritmanızı içine inşa edebilirsiniz Money.prototype.valueOf().


Bu nesne yönelimli yaklaşımı seviyorum, Money nesnesinin her iki değeri de tutması çok kullanışlı. Özel Objective-C sınıflarımda oluşturmak istediğim tam işlevsellik türü.
james_womack

4
Yuvarlanacak kadar doğru değil.
Henry Tseng

3
Sys.puts (m + n) olmamalı; //80.76 aslında sys.puts'u okur (m + n); //80.77? 5'i yuvarlamayı unuttuğuna inanıyorum.
Dave L

2
Bu tür bir yaklaşım, ortaya çıkabilecek bir dizi ince meseleye sahiptir. Örneğin, güvenli toplama, çıkarma, çarpma vb. Yöntemleri uygulamadınız, bu nedenle para tutarlarını birleştirirken büyük olasılıkla yuvarlama hatalarıyla karşılaşacaksınız
1800 BİLGİ

2
Buradaki sorun, örneğin Money(0.1), JavaScript sözlüğünün kaynaktan "0.1" dizesini okuduktan sonra onu bir ikili kayan noktaya dönüştürdüğü ve sizin zaten istenmeyen bir yuvarlama yaptığınız anlamına gelir. Sorun, temsil (ikili ve ondalık) ile ilgilidir, hassasiyetle ilgili değil .
mgd


2

Probleminiz kayan nokta hesaplamalarındaki yanlışlıktan kaynaklanıyor. Bunu çözmek için sadece yuvarlamayı kullanıyorsanız, çarparken ve bölerken daha büyük hatalarla karşılaşırsınız.

Çözüm aşağıda, bir açıklama aşağıdaki gibidir:

Bunu anlamak için bunun arkasındaki matematiği düşünmeniz gerekecek. 1/3 gibi gerçek sayılar, sonsuz oldukları için matematikte ondalık değerlerle temsil edilemez (örn. - .333333333333333 ...). Ondalık sayıdaki bazı sayılar ikili olarak doğru şekilde temsil edilemez. Örneğin 0,1, sınırlı sayıda basamakla ikili olarak doğru şekilde temsil edilemez.

Daha ayrıntılı açıklama için buraya bakın: http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html

Çözüm uygulamasına bir göz atın: http://floating-point-gui.de/languages/javascript/


1

Kodlamalarının ikili yapısı nedeniyle, bazı ondalık sayılar mükemmel bir doğrulukla gösterilemez. Örneğin

var money = 600.90;
var price = 200.30;
var total = price * 3;

// Outputs: false
console.log(money >= total);

// Outputs: 600.9000000000001
console.log(total);

Saf javascript kullanmanız gerekiyorsa, her hesaplama için çözüm düşünmeniz gerekir. Yukarıdaki kod için ondalık sayıları tam sayılara dönüştürebiliriz.

var money = 60090;
var price = 20030;
var total = price * 3;

// Outputs: true
console.log(money >= total);

// Outputs: 60090
console.log(total);

JavaScript'te Ondalık Matematik Sorunlarından Kaçınma

Harika belgelere sahip finansal hesaplamalar için özel bir kütüphane var. Finance.js


Finance.js'nin de örnek uygulamaları olmasını
seviyorum

0

Maalesef şimdiye kadarki tüm cevaplar, tüm para birimlerinin 100 alt birimi olmadığı gerçeğini göz ardı ediyor (örneğin, yüzde ABD dolarının (USD) alt birimi). Irak Dinarı (IQD) gibi para birimlerinin 1000 alt birimi vardır: bir Irak Dinarı 1000 fileye sahiptir. Japon Yeni'nin (JPY) alt birimleri yoktur. Bu nedenle, "tamsayı aritmetiği yapmak için 100 ile çarpın" her zaman doğru cevap değildir.

Ek olarak parasal hesaplamalar için para birimini de takip etmeniz gerekir. Bir Hint Rupisine (INR) ABD Doları (USD) ekleyemezsiniz (önce birini diğerine dönüştürmeden).

JavaScript'in tamsayı veri türü tarafından temsil edilebilecek maksimum miktar konusunda da sınırlamalar vardır.

Parasal hesaplamalarda, paranın sonlu kesinliğe (tipik olarak 0-3 ondalık nokta) sahip olduğunu ve yuvarlamanın belirli şekillerde yapılması gerektiğini (örneğin, "normal" yuvarlama ile bankacı yuvarlaması) gerektiğini de unutmamalısınız. Gerçekleştirilecek yuvarlama türü de yargı bölgesine / para birimine göre değişebilir.

Javascript'te paranın nasıl ele alınacağı, ilgili noktaların çok iyi bir tartışmasına sahiptir.

Aramalarımda parasal hesaplamalarla ilgili birçok konuyu ele alan dinero.js kitaplığını buldum . Henüz bir üretim sisteminde kullanmadım, bu yüzden bu konuda bilinçli bir fikir veremezsiniz.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.