วิธีจัดการกับความแม่นยำเลขทศนิยมใน JavaScript?


617

ฉันมีสคริปต์ทดสอบหุ่นจำลองต่อไปนี้:

function test() {
  var x = 0.1 * 0.2;
  document.write(x);
}
test();

สิ่งนี้จะพิมพ์ผลลัพธ์0.020000000000000004ขณะที่ควรพิมพ์0.02(ถ้าคุณใช้เครื่องคิดเลข) เท่าที่ฉันเข้าใจนี่เป็นเพราะข้อผิดพลาดในความแม่นยำคูณจุดลอย

ใครบ้างมีทางออกที่ดีดังนั้นในกรณีเช่นนี้ฉันได้ผลลัพธ์ที่ถูกต้อง0.02? ฉันรู้ว่ามีฟังก์ชั่นเช่นtoFixedหรือการปัดเศษเป็นความเป็นไปได้อีกอย่างหนึ่ง แต่ฉันต้องการให้พิมพ์จำนวนทั้งหมดโดยไม่ต้องตัดและปัดเศษ แค่อยากรู้ว่าคุณมีวิธีแก้ปัญหาที่ดีและสง่างามหรือไม่

แน่นอนมิฉะนั้นฉันจะปัดเป็นตัวเลข 10 หลัก


118
ที่จริงแล้วข้อผิดพลาดเกิดขึ้นเนื่องจากไม่มีวิธีการแมป0.1กับจำนวนจุดลอยตัวไบนารี จำกัด
Aaron Digulla

10
เศษส่วนส่วนใหญ่ไม่สามารถแปลงเป็นทศนิยมได้อย่างแม่นยำ คำอธิบายที่ดีอยู่ที่นี่: docs.python.org/release/2.5.1/tut/node16.html
Nate Zaugg

7
มีความเป็นไปได้ที่ซ้ำกันของคณิตศาสตร์ของ JavaScript เสียหรือไม่
epascarello

53
@SalmanA: ว่า JavaScript ของคุณซ่อนปัญหานี้จากคุณไม่ได้หมายความว่าฉันผิด
Aaron Digulla

5
ไม่เห็นด้วยกับแอรอนมีวิธีในการรหัส 0.1 อย่างสมบูรณ์และสมบูรณ์ในไบนารี แต่ IEEE 754 ไม่จำเป็นต้องกำหนดสิ่งนี้ ลองนึกภาพการแทนค่าที่คุณจะเขียนรหัสจำนวนเต็มในเลขฐานสองในมือหนึ่งส่วนทศนิยมในอีกด้านหนึ่งถึง n ทศนิยมในไบนารีเกินไปเช่นจำนวนเต็มปกติ> 0 และในที่สุดตำแหน่งของจุดทศนิยม . ทีนี้คุณก็จะได้ 0.1 อย่างสมบูรณ์แบบโดยไม่มีข้อผิดพลาด Btw เนื่องจาก JS ใช้จำนวนทศนิยมภายในขอบเขตพวกเขา devs อาจเข้ารหัสความกล้าที่จะไม่ทำผิดพลาดกับทศนิยมที่ผ่านมา
Fabien Haddadi

คำตอบ:


469

จากคู่มือจุดลอยตัว :

ฉันจะทำอย่างไรเพื่อหลีกเลี่ยงปัญหานี้

ขึ้นอยู่กับประเภทของการคำนวณที่คุณทำ

  • หากคุณต้องการให้ผลลัพธ์ของคุณเพิ่มขึ้นอย่างแน่นอนโดยเฉพาะเมื่อคุณทำงานกับเงินให้ใช้ประเภทข้อมูลทศนิยมพิเศษ
  • หากคุณไม่ต้องการเห็นตำแหน่งทศนิยมพิเศษทั้งหมด: เพียงแค่จัดรูปแบบผลลัพธ์ของคุณให้ปัดเศษเป็นทศนิยมจำนวนคงที่เมื่อแสดงผล
  • หากคุณไม่มีประเภทข้อมูลทศนิยมให้ใช้อีกทางเลือกหนึ่งคือทำงานกับจำนวนเต็มเช่นคำนวณเงินทั้งหมดเป็นเซ็นต์ แต่นี่เป็นงานที่มากกว่าและมีข้อเสียอยู่บ้าง

โปรดทราบว่าจุดแรกจะใช้เฉพาะถ้าคุณต้องการพฤติกรรมทศนิยมที่แม่นยำโดยเฉพาะ คนส่วนใหญ่ไม่ต้องการสิ่งนั้นพวกเขาแค่หงุดหงิดที่โปรแกรมของพวกเขาทำงานไม่ถูกต้องกับตัวเลขเช่น 1/10 โดยไม่ทราบว่าพวกเขาจะไม่กระพริบตาที่ข้อผิดพลาดเดียวกันหากเกิดขึ้นกับ 1/3

หากจุดแรกนำไปใช้กับคุณจริงๆให้ใช้BigDecimal สำหรับ JavaScriptซึ่งไม่หรูหราเลย แต่จริง ๆ แล้วแก้ปัญหาแทนที่จะให้วิธีแก้ปัญหาที่ไม่สมบูรณ์


11
ฉันสังเกตเห็นลิงค์ตายของคุณสำหรับ BigDecimal และในขณะที่มองหากระจกฉันพบทางเลือกที่เรียกว่า BigNumber: jsfromhell.com/classes/bignumber
Jacksonkr

4
@ bass-t: ใช่ แต่การลอยสามารถแสดงจำนวนเต็มจนถึงความยาวของซิกนิฟิแคนด์และตามมาตรฐาน ECMA มันเป็น 64 บิต ดังนั้นมันจึงสามารถแทนจำนวนเต็มได้ถึง 2 ^ 52
Michael Borgwardt

5
@Karl: เศษส่วนทศนิยม 1/10 ไม่สามารถแสดงเป็นเศษส่วนไบนารีที่แน่นอนในฐาน 2 และนั่นคือหมายเลข Javascript ดังนั้นจึงเป็นปัญหาเดียวกัน
Michael Borgwardt

12
ฉันเรียนรู้วันนี้ว่าจำนวนเต็มมีปัญหาความแม่นยำในจาวาสคริปต์ พิจารณาว่าconsole.log(9332654729891549)จริง ๆ แล้วพิมพ์9332654729891548(เช่นออกโดยหนึ่ง!)
mlathe

12
@mlathe: Doh .. ;P... ระหว่าง2⁵²= 4,503,599,627,370,496และ2⁵³= 9,007,199,254,740,992ตัวเลขซึ่งแสดงอยู่ตรงจำนวนเต็ม สำหรับช่วงถัดไปจาก2⁵³ไป2⁵⁴ทุกอย่างจะคูณด้วย2ดังนั้นตัวเลขซึ่งแสดงเป็นคนที่แม้ , ฯลฯตรงกันข้ามสำหรับช่วงก่อนหน้านี้จาก2⁵¹การ2⁵²เว้นวรรคคือ0.5, ฯลฯนี่คือสาเหตุที่เพิ่มขึ้นเพียง | ลดลงฐาน | radix 2 | เลขชี้กำลังเลขฐานสองใน / ของค่าทศนิยม 64 บิต (ซึ่งจะอธิบายถึงพฤติกรรมที่ไม่คาดคิดของเอกสาร 'ที่ไม่คาดคิด' toPrecision()สำหรับค่าระหว่าง0และ1)
GitaarLAB

126

ฉันชอบโซลูชันของ Pedro Ladaria และใช้สิ่งที่คล้ายกัน

function strip(number) {
    return (parseFloat(number).toPrecision(12));
}

ซึ่งแตกต่างจากวิธีการแก้ปัญหา Pedros นี้จะรอบ 0.999 ... การทำซ้ำและมีความแม่นยำในการบวก / ลบหนึ่งในหลักสำคัญน้อยที่สุด

หมายเหตุ: เมื่อจัดการกับ 32 หรือ 64 บิตลอยคุณควรใช้ toPrecision (7) และ toPrecision (15) เพื่อผลลัพธ์ที่ดีที่สุด ดูคำถามนี้สำหรับข้อมูลว่าทำไม


21
เหตุผลใดที่คุณเลือก 12
qwertymk

18
toPrecisionส่งคืนสตริงแทนตัวเลข สิ่งนี้อาจไม่เป็นที่ต้องการเสมอไป
SStanley

7
parseFloat (1.005) .toPrecision (3) => 1.00
ปีเตอร์

5
@ user2428118 ฉันรู้ว่าฉันหมายถึงแสดงข้อผิดพลาดในการปัดเศษผลลัพธ์คือ 1.00 แทน 1.01
Peter

9
สิ่งที่ @ user2428118 พูดว่าอาจไม่ชัดเจนพอ: (9.99*5).toPrecision(2)= 50แทน49.95เนื่องจาก toPrecision นับจำนวนทั้งหมดไม่ใช่แค่ทศนิยม จากนั้นคุณสามารถใช้toPrecision(4)แต่ถ้าผลลัพธ์ของคุณคือ> 100 แสดงว่าคุณโชคไม่ดีอีกเพราะจะอนุญาตให้มีตัวเลขสามตัวแรกและทศนิยมหนึ่งตำแหน่งวิธีนี้จะเลื่อนจุดและทำให้ใช้งานได้ไม่มากก็น้อย ฉันใช้toFixed(2)แทน
aexl

79

สำหรับความโน้มเอียงทางคณิตศาสตร์: http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html

วิธีที่แนะนำคือการใช้ปัจจัยการแก้ไข (คูณด้วยกำลังที่เหมาะสมของ 10 เพื่อให้การคำนวณทางคณิตศาสตร์เกิดขึ้นระหว่างจำนวนเต็ม) ตัวอย่างเช่นในกรณีของ0.1 * 0.2ปัจจัยการแก้ไขคือ10และคุณกำลังทำการคำนวณ:

> var x = 0.1
> var y = 0.2
> var cf = 10
> x * y
0.020000000000000004
> (x * cf) * (y * cf) / (cf * cf)
0.02

โซลูชัน (เร็วมาก) ดูเหมือนว่า:

var _cf = (function() {
  function _shift(x) {
    var parts = x.toString().split('.');
    return (parts.length < 2) ? 1 : Math.pow(10, parts[1].length);
  }
  return function() { 
    return Array.prototype.reduce.call(arguments, function (prev, next) { return prev === undefined || next === undefined ? undefined : Math.max(prev, _shift (next)); }, -Infinity);
  };
})();

Math.a = function () {
  var f = _cf.apply(null, arguments); if(f === undefined) return undefined;
  function cb(x, y, i, o) { return x + f * y; }
  return Array.prototype.reduce.call(arguments, cb, 0) / f;
};

Math.s = function (l,r) { var f = _cf(l,r); return (l * f - r * f) / f; };

Math.m = function () {
  var f = _cf.apply(null, arguments);
  function cb(x, y, i, o) { return (x*f) * (y*f) / (f * f); }
  return Array.prototype.reduce.call(arguments, cb, 1);
};

Math.d = function (l,r) { var f = _cf(l,r); return (l * f) / (r * f); };

ในกรณีนี้:

> Math.m(0.1, 0.2)
0.02

ฉันแนะนำให้ใช้ห้องสมุดทดสอบอย่างSinfulJS


1
ฉันรักวิธีแก้ปัญหาที่สง่างามนี้ แต่ดูเหมือนจะไม่สมบูรณ์แบบ: jsfiddle.net/Dm6F5/1 Math.a (76.65, 38.45) ส่งคืน 115.10000000000002
nicolallias

3
Math.m (10,2332226616) กำลังให้ฉัน "-19627406800" ซึ่งเป็นค่าลบ ... ฉันหวังว่าจะต้องมีขีด จำกัด สูงสุด - อาจเป็นสาเหตุของปัญหานี้ กรุณาแนะนำ
Shiva Komuravelly

1
ทั้งหมดนี้ดูดี แต่ดูเหมือนจะมีข้อผิดพลาดหรือสองอย่างในนั้น
MrYellow

5
วิธีแก้ปัญหาที่รวดเร็วมากเขาบอกว่า ... แก้ไขไม่ได้ไม่มีใครเคยพูด
Cozzbie

2
อย่าใช้รหัสด้านบน ไม่ใช่วิธีแก้ปัญหาอย่างรวดเร็วหากไม่ได้ผล นี่เป็นคำถามที่เกี่ยวข้องกับคณิตศาสตร์ดังนั้นจึงจำเป็นต้องมีความแม่นยำ
Drenai

49

คุณกำลังทำการคูณเท่านั้นหรือไม่ ถ้าเป็นเช่นนั้นคุณสามารถใช้เพื่อประโยชน์ของคุณเป็นความลับเรียบร้อยเกี่ยวกับเลขทศนิยม NumberOfDecimals(X) + NumberOfDecimals(Y) = ExpectedNumberOfDecimalsนั่นคือการที่ กล่าวคือถ้าเรามี0.123 * 0.12แล้วเรารู้ว่าจะมีทศนิยม 5 ตำแหน่งเพราะ0.123มีทศนิยม 3 ตำแหน่งและ0.12มีสอง ดังนั้นถ้าจาวาสคริปต์ให้ตัวเลขเหมือนกับ0.014760000002เราสามารถปัดเศษทศนิยมที่ 5 ได้อย่างปลอดภัยโดยไม่ต้องกลัวว่าจะเสีย


6
... และวิธีการที่จะได้รับแน่นอนปริมาณของตำแหน่งทศนิยม
line-o

7
0.5 * 0.2 = 0.10; คุณยังสามารถตัดที่ทศนิยม 2 ตำแหน่ง (หรือน้อยกว่า) แต่จะไม่มีตัวเลขใดที่มีนัยสำคัญทางคณิตศาสตร์ใด ๆ นอกเหนือจากกฎนี้
Nate Zaugg

3
คุณมีหนังเรื่องนี้ไหม? โปรดทราบด้วยว่าการแบ่งนั้นไม่เป็นความจริง
กริฟฟิน

3
@NateZaugg คุณไม่สามารถตัดทอนทศนิยมที่ล้นออกมาได้คุณต้องปัดเศษจำนวนเพราะ 2090.5 * 8.61 คือ 17999.205 แต่ในการลอยตัวมันเป็น 17999.204999999998
Lostfields

3
@Lostfields - คุณถูกต้อง! ฉันได้อัพเดตคำตอบแล้ว
Nate Zaugg

29

คุณกำลังมองหาsprintfการนำไปใช้สำหรับ JavaScript เพื่อให้คุณสามารถเขียนข้อผิดพลาดเล็ก ๆ ในลอยได้ (เนื่องจากมันถูกจัดเก็บในรูปแบบไบนารี) ในรูปแบบที่คุณคาดหวัง

ลองjavascript-sprintfคุณจะเรียกมันว่าสิ่งนี้:

var yourString = sprintf("%.2f", yourNumber);

เพื่อพิมพ์หมายเลขของคุณเป็นทศนิยมด้วยทศนิยมสองตำแหน่ง

นอกจากนี้คุณยังสามารถใช้ Number.toFixed () เพื่อจุดประสงค์ในการแสดงผลหากคุณไม่ต้องการรวมไฟล์มากขึ้นเพื่อใช้ในการปัดเศษทศนิยมเพื่อความแม่นยำ


4
ฉันคิดว่านี่เป็นทางออกที่สะอาดที่สุด หากคุณไม่ต้องการผลลัพธ์ที่แท้จริงคือ 0.02 ข้อผิดพลาดเล็ก ๆ น้อยมาก ดูเหมือนว่าสิ่งสำคัญคือตัวเลขของคุณจะปรากฏขึ้นอย่างชัดเจนไม่ใช่ว่าคุณมีความแม่นยำโดยพลการ
Long Ouyang

2
สำหรับการแสดงผลนี้เป็นตัวเลือกที่ดีที่สุดสำหรับการคำนวณที่ซับซ้อนตรวจสอบคำตอบของ Borgwardt
ไม่มีให้บริการใน

4
แต่แล้วอีกครั้งนี้จะส่งกลับสตริงเดียวกันเป็น yourNumber.toFixed (2)
Robert

27

ฉันพบว่าBigNumber.jsตรงตามความต้องการของฉัน

ไลบรารี JavaScript สำหรับเลขทศนิยมและเลขฐานสิบที่มีความแม่นยำโดยพลการ

มันมีเอกสารที่ดีและผู้เขียนขยันมากตอบสนองต่อข้อเสนอแนะ

ผู้เขียนคนเดียวกันมีห้องสมุดที่คล้ายกัน 2 แห่ง:

Big.js

ไลบรารี JavaScript ขนาดเล็กที่รวดเร็วสำหรับการคำนวณทศนิยมแบบทศนิยมโดยพลการ น้องสาวคนเล็กไป bignumber.js

และDecimal.js

ทศนิยมชนิดความแม่นยำโดยพลการสำหรับ JavaScript

นี่คือโค้ดบางส่วนที่ใช้ BigNumber:

$(function(){

  
  var product = BigNumber(.1).times(.2);  
  $('#product').text(product);

  var sum = BigNumber(.1).plus(.2);  
  $('#sum').text(sum);


});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>

<!-- 1.4.1 is not the current version, but works for this example. -->
<script src="http://cdn.bootcss.com/bignumber.js/1.4.1/bignumber.min.js"></script>

.1 &times; .2 = <span id="product"></span><br>
.1 &plus; .2 = <span id="sum"></span><br>


3
การใช้ห้องสมุดเป็นทางเลือกที่ดีที่สุดในความคิดของฉัน
แอนโธนี

1
จากลิงค์นี้github.com/MikeMcl/big.js/issues/45 bignumber.js -> ทศนิยมทางการเงิน -> วิทยาศาสตร์ big.js -> ???
ve

20
var times = function (a, b) {
    return Math.round((a * b) * 100)/100;
};

---หรือ---

var fpFix = function (n) {
    return Math.round(n * 100)/100;
};

fpFix(0.1*0.2); // -> 0.02

--- ยัง ---

var fpArithmetic = function (op, x, y) {
    var n = {
            '*': x * y,
            '-': x - y,
            '+': x + y,
            '/': x / y
        }[op];        

    return Math.round(n * 100)/100;
};

--- ดังที่ ---

fpArithmetic('*', 0.1, 0.2);
// 0.02

fpArithmetic('+', 0.1, 0.2);
// 0.3

fpArithmetic('-', 0.1, 0.2);
// -0.1

fpArithmetic('/', 0.2, 0.1);
// 2

4
ฉันคิดว่ามันจะให้ปัญหาเดียวกันกับผลลัพธ์ คุณส่งคืนจุดลอยตัวดังนั้นโอกาสที่ค่าส่งคืนจะเป็น "ไม่ถูกต้อง"
Gertjan

1
+1 ที่ฉลาดและมีประโยชน์มาก
Jonatas Walker

18

ฟังก์ชั่นนี้จะกำหนดความแม่นยำที่ต้องการจากการคูณจำนวนจุดลอยตัวสองจำนวนและส่งคืนผลลัพธ์ด้วยความแม่นยำที่เหมาะสม สง่างามแม้ว่ามันจะไม่

function multFloats(a,b){
  var atens = Math.pow(10,String(a).length - String(a).indexOf('.') - 1), 
      btens = Math.pow(10,String(b).length - String(b).indexOf('.') - 1); 
  return (a * atens) * (b * btens) / (atens * btens); 
}

Ew ใช่เรามาแปลงตัวเลขเป็นสตริงสำหรับคณิตศาสตร์จุดลอยตัวและลองเสนอว่าเป็นคำตอบ
Andrew

16

น่าแปลกที่ฟังก์ชั่นนี้ยังไม่ได้โพสต์ถึงแม้ว่าคนอื่น ๆ จะมีรูปแบบที่คล้ายคลึงกัน มันมาจากเอกสารเว็บ MDN สำหรับ Math.round () มันกระชับและช่วยให้แม่นยำแตกต่างกัน

function precisionRound(number, precision) {
  var factor = Math.pow(10, precision);
  return Math.round(number * factor) / factor;
}

console.log (precisionRound (1234.5678, 1)); // ผลลัพธ์ที่คาดหวัง: 1234.6

console.log (precisionRound (1234.5678, -1)); // ผลลัพธ์ที่คาดหวัง: 1230

var inp = document.querySelectorAll('input');
var btn = document.querySelector('button');

btn.onclick = function(){
  inp[2].value = precisionRound( parseFloat(inp[0].value) * parseFloat(inp[1].value) , 5 );
};

//MDN function
function precisionRound(number, precision) {
  var factor = Math.pow(10, precision);
  return Math.round(number * factor) / factor;
}
button{
display: block;
}
<input type='text' value='0.1'>
<input type='text' value='0.2'>
<button>Get Product</button>
<input type='text'>

ปรับปรุง: ส.ค. / 20/2019 เพิ่งสังเกตเห็นข้อผิดพลาดนี้ ฉันเชื่อว่าเกิดจากข้อผิดพลาดความแม่นยำของทศนิยมกับ Math.round ()

precisionRound(1.005, 2) // produces 1, incorrect, should be 1.01

เงื่อนไขเหล่านี้ทำงานอย่างถูกต้อง:

precisionRound(0.005, 2) // produces 0.01
precisionRound(1.0005, 3) // produces 1.001
precisionRound(1234.5, 0) // produces 1235
precisionRound(1234.5, -1) // produces 1230

การแก้ไข:

function precisionRoundMod(number, precision) {
  var factor = Math.pow(10, precision);
  var n = precision < 0 ? number : 0.01 / factor + number;
  return Math.round( n * factor) / factor;
}

นี่จะเพิ่มตัวเลขไปทางขวาเมื่อปัดเศษทศนิยม MDN ได้อัปเดตหน้า Math.round ดังนั้นอาจมีบางคนที่สามารถให้ทางออกที่ดีกว่า


คำตอบที่ไม่ถูกต้อง. 10.2 จะส่งคืน 10.19 เสมอ jsbin.com/tozogiwide/edit?html,js,console,output
Žilvinas

@ vilvinas ลิงก์ JSBin ที่คุณโพสต์ไม่ได้ใช้ฟังก์ชัน MDN ตามรายการด้านบน ฉันคิดว่าความคิดเห็นของคุณมุ่งไปที่คนผิด
HelloWorldPeace

13

คุณแค่ต้องคิดเลขทศนิยมที่คุณต้องการ - ไม่มีเค้กและกินมันเกินไป :-)

ข้อผิดพลาดเชิงตัวเลขสะสมกับการดำเนินการเพิ่มเติมทุกครั้งและหากคุณไม่ได้ตัดออกไปก่อนกำหนดก็จะเพิ่มขึ้น ไลบรารีตัวเลขที่แสดงผลลัพธ์ที่ดูสะอาดตาเพียงตัด 2 หลักสุดท้ายในทุกขั้นตอนตัวประมวลผลเชิงตัวเลขยังมีความยาวปกติและธรรมดาเต็มด้วยเหตุผลเดียวกัน Cuf-offs ราคาถูกสำหรับโปรเซสเซอร์ แต่แพงมากสำหรับคุณในสคริปต์ (การคูณและการหารและการใช้ pov (... )) lib คณิตศาสตร์ที่ดีจะให้พื้นที่ (x, n) เพื่อทำการตัดให้คุณ

อย่างน้อยที่สุดคุณควรสร้าง var / constant ทั่วโลกด้วย pov (10, n) - หมายความว่าคุณตัดสินใจด้วยความแม่นยำที่คุณต้องการ :-) จากนั้นทำดังนี้:

Math.floor(x*PREC_LIM)/PREC_LIM  // floor - you are cutting off, not rounding

คุณยังสามารถทำคณิตศาสตร์ได้และจะถูกตัดออกไปตอนท้าย - โดยสมมติว่าคุณกำลังแสดงและไม่ได้ทำ if-s กับผลลัพธ์ หากคุณสามารถทำเช่นนั้น. toFixed (... ) อาจมีประสิทธิภาพมากกว่า

หากคุณกำลังทำ if-s / การเปรียบเทียบและไม่ต้องการตัดจากนั้นคุณยังต้องการค่าคงที่ขนาดเล็กมักจะเรียกว่า eps ซึ่งเป็นทศนิยมหนึ่งตำแหน่งที่สูงกว่าข้อผิดพลาดที่คาดไว้สูงสุด สมมติว่าการตัดของคุณเป็นทศนิยมสองตำแหน่งสุดท้ายจากนั้น eps ของคุณจะมี 1 ที่อันดับ 3 จากจุดสุดท้าย (อย่างมีนัยสำคัญน้อยที่สุดที่ 3) และคุณสามารถใช้เพื่อเปรียบเทียบว่าผลลัพธ์นั้นอยู่ในช่วง eps ที่คาดหวังหรือไม่ (0.02 -eps <0.1 * 0.2 <0.02 + eps)


นอกจากนี้คุณยังสามารถเพิ่ม 0.5 เพื่อทำการปัดเศษของคนยากจน: Math.floor (x * PREC_LIM + 0.5) / PREC_LIM
cmroanirgo

หมายเหตุแม้ว่าเช่นที่เป็นMath.floor(-2.1) -3ดังนั้นอาจใช้เช่นMath[x<0?'ceil':'floor'](x*PREC_LIM)/PREC_LIM
MikeM

ทำไมfloorแทนround?
Quinn Comendant

12

คุณสามารถใช้parseFloat()และtoFixed()หากคุณต้องการหลีกเลี่ยงปัญหานี้สำหรับการดำเนินการขนาดเล็ก:

a = 0.1;
b = 0.2;

a + b = 0.30000000000000004;

c = parseFloat((a+b).toFixed(2));

c = 0.3;

a = 0.3;
b = 0.2;

a - b = 0.09999999999999998;

c = parseFloat((a-b).toFixed(2));

c = 0.1;

11

ฟังก์ชัน round () ที่ phpjs.org ทำงานได้ดี: http://phpjs.org/functions/round

num = .01 + .06;  // yields 0.0699999999999
rnum = round(num,12); // yields 0.07

2
@jrg จากการประชุมตัวเลขที่ลงท้ายด้วย "5" จะถูกปัดเศษเป็นเลขคู่ที่ใกล้เคียงที่สุด (เนื่องจากการปัดเศษขึ้นหรือลงเสมอจะทำให้มีอคติกับผลลัพธ์ของคุณ) ดังนั้นการปัดเศษ 4.725 ให้เป็นทศนิยมสองตำแหน่งควรเป็น 4.72
Mark A. Durham

9

0.6 * 3 มันยอดเยี่ยมมาก!)) สำหรับฉันมันใช้งานได้ดี:

function dec( num )
{
    var p = 100;
    return Math.round( num * p ) / p;
}

ง่ายมาก))


มันจะใช้งานได้กับสิ่งที่ชอบ8.22e-8 * 1.3หรือไม่
Paul Carlton

0.6 x 3 = 1.8 รหัสที่คุณให้ผลลัพธ์เป็น 2 ... จึงไม่ดี
Zyo

@Zyo มันส่งคืน 1.8 ในอินสแตนซ์นี้ คุณเรียกใช้มันได้อย่างไร
Drenai

น่าสนใจ คุณสามารถสลับโอเปอเรเตอร์การคูณและการหารในส่วนนี้ได้
แอนดรู

9

โปรดสังเกตว่าสำหรับการใช้งานตามวัตถุประสงค์ทั่วไปพฤติกรรมนี้น่าจะยอมรับได้
ปัญหาเกิดขึ้นเมื่อเปรียบเทียบค่าคะแนนลอยตัวเหล่านั้นเพื่อพิจารณาการดำเนินการที่เหมาะสม
ด้วยการถือกำเนิดของ ES6 ค่าคงที่ใหม่Number.EPSILONจะถูกกำหนดเพื่อกำหนดระยะขอบข้อผิดพลาดที่ยอมรับได้:
ดังนั้นแทนที่จะทำการเปรียบเทียบเช่นนี้

0.1 + 0.2 === 0.3 // which returns false

คุณสามารถกำหนดฟังก์ชั่นการเปรียบเทียบแบบกำหนดเองเช่นนี้

function epsEqu(x, y) {
    return Math.abs(x - y) < Number.EPSILON;
}
console.log(epsEqu(0.1+0.2, 0.3)); // true

แหล่งที่มา: http://2ality.com/2015/04/numbers-math-es6.html#numberepsilon


ในกรณีของฉัน Number.PSPSON มีขนาดเล็กเกินไปซึ่งส่งผลให้เช่น0.9 !== 0.8999999761581421
Tom

8

ผลลัพธ์ที่คุณได้รับนั้นถูกต้องและสอดคล้องกันอย่างเป็นธรรมในการใช้งานจุดลอยตัวในภาษาต่าง ๆ ตัวประมวลผลและระบบปฏิบัติการ - สิ่งเดียวที่การเปลี่ยนแปลงคือระดับของความไม่ถูกต้องเมื่อจริง ๆ แล้วการลอยตัวนั้นเป็นสองเท่า

0.1 ในจำนวนทศนิยมแบบไบนารีนั้นเท่ากับ 1/3 ในทศนิยม (เช่น 0.3333333333333 ... ตลอดไป) ไม่มีวิธีที่ถูกต้องในการจัดการ

หากคุณกำลังจัดการกับลอยอยู่เสมอคาดหวังข้อผิดพลาดในการปัดเศษเล็กดังนั้นคุณจะต้องปัดเศษผลลัพธ์ที่แสดงออกมาเป็นสิ่งที่สมเหตุสมผล ในทางกลับกันคุณจะได้รับเลขคณิตที่รวดเร็วและทรงพลังมากเพราะการคำนวณทั้งหมดอยู่ในไบนารีดั้งเดิมของโปรเซสเซอร์

ส่วนใหญ่เวลาแก้ปัญหาไม่ได้เปลี่ยนเป็นเลขคณิตจุดคงที่ส่วนใหญ่เป็นเพราะมันช้ากว่ามากและ 99% ของเวลาที่คุณไม่ต้องการความแม่นยำ หากคุณกำลังจัดการกับสิ่งที่ต้องการระดับความแม่นยำ (เช่นธุรกรรมทางการเงิน) Javascript อาจไม่ใช่เครื่องมือที่ดีที่สุดที่จะใช้ต่อไป (เนื่องจากคุณต้องการบังคับใช้ประเภทจุดคงที่ภาษาแบบคงที่น่าจะดีกว่า )

คุณกำลังมองหาวิธีแก้ปัญหาที่สง่างามแล้วฉันก็กลัวว่ามันคือ: การล่องลอยรวดเร็ว แต่มีข้อผิดพลาดในการปัดเศษเล็ก ๆ


8

เพื่อหลีกเลี่ยงปัญหานี้คุณควรทำงานกับค่าจำนวนเต็มแทนที่จะเป็นทศนิยม ดังนั้นเมื่อคุณต้องการให้ความแม่นยำ 2 ตำแหน่งทำงานกับค่า * 100 สำหรับ 3 ตำแหน่งใช้ 1,000 เมื่อแสดงคุณใช้ฟอร์แมตเตอร์เพื่อวางในตัวคั่น

ระบบจำนวนมากละเว้นการทำงานกับทศนิยมด้วยวิธีนี้ นั่นคือเหตุผลที่ระบบจำนวนมากทำงานร่วมกับเซ็นต์ (เป็นจำนวนเต็ม) แทนที่จะเป็นดอลลาร์ / ยูโร (เป็นทศนิยม)


7

ปัญหา

จุดลอยตัวไม่สามารถเก็บค่าทศนิยมทั้งหมดได้อย่างแน่นอน ดังนั้นเมื่อใช้รูปแบบจุดลอยจะมีข้อผิดพลาดในการปัดเศษในค่าอินพุต ข้อผิดพลาดในอินพุตของผลลัพธ์เป็นผลของข้อผิดพลาดในเอาต์พุต ในกรณีที่ฟังก์ชั่นไม่ต่อเนื่องหรือผู้ประกอบการอาจมีความแตกต่างใหญ่ในการส่งออกรอบจุดที่ฟังก์ชั่นหรือผู้ประกอบการที่ไม่ต่อเนื่อง

อินพุตและเอาต์พุตสำหรับค่าทศนิยม

ดังนั้นเมื่อใช้ตัวแปร floating point คุณควรตระหนักถึงสิ่งนี้เสมอ และผลลัพธ์อะไรก็ตามที่คุณต้องการจากการคำนวณที่มีคะแนนลอยตัวควรจัดรูปแบบ / กำหนดเงื่อนไขก่อนที่จะแสดงด้วยสิ่งนี้
เมื่อใช้เฉพาะฟังก์ชันและตัวดำเนินการต่อเนื่องการปัดเศษไปยังความแม่นยำที่ต้องการมักจะทำ (ไม่ตัด) คุณสมบัติการจัดรูปแบบมาตรฐานที่ใช้ในการแปลงลอยเป็นสตริงมักจะทำเพื่อคุณ
เนื่องจากการปัดเศษเพิ่มข้อผิดพลาดซึ่งอาจทำให้เกิดข้อผิดพลาดทั้งหมดมากกว่าครึ่งหนึ่งของความแม่นยำที่ต้องการผลลัพธ์ควรได้รับการแก้ไขตามความแม่นยำที่คาดหวังของอินพุตและความแม่นยำที่ต้องการของเอาต์พุต คุณควร

  • ปัดเศษของความแม่นยำที่คาดไว้หรือตรวจสอบให้แน่ใจว่าไม่มีค่าใดถูกป้อนด้วยความแม่นยำที่สูงขึ้น
  • เพิ่มค่าเล็กน้อยให้กับเอาต์พุตก่อนการปัดเศษ / จัดรูปแบบซึ่งมีขนาดเล็กกว่าหรือเท่ากับ 1/4 ของความแม่นยำที่ต้องการและใหญ่กว่าข้อผิดพลาดที่คาดไว้สูงสุดที่เกิดจากข้อผิดพลาดในการปัดเศษในอินพุตและระหว่างการคำนวณ หากเป็นไปไม่ได้การรวมกันของความแม่นยำของชนิดข้อมูลที่ใช้จะไม่เพียงพอที่จะส่งมอบความแม่นยำของเอาต์พุตที่ต้องการสำหรับการคำนวณของคุณ

2 สิ่งเหล่านี้มักจะไม่ได้ทำและในกรณีส่วนใหญ่ความแตกต่างที่เกิดจากการไม่ทำนั้นเล็กเกินไปที่จะสำคัญสำหรับผู้ใช้ส่วนใหญ่ แต่ฉันมีโครงการแล้วซึ่งผลลัพธ์ไม่ได้รับการยอมรับจากผู้ใช้โดยไม่มีการแก้ไขเหล่านั้น

ฟังก์ชั่นที่ไม่ต่อเนื่องหรือผู้ประกอบการ (เช่น modula)

เมื่อมีการใช้ตัวดำเนินการหรือฟังก์ชันที่ไม่ต่อเนื่องอาจต้องมีการแก้ไขเพิ่มเติมเพื่อให้แน่ใจว่าผลลัพธ์จะเป็นไปตามที่คาดไว้ การปัดเศษและการเพิ่มการแก้ไขเล็ก ๆ ก่อนที่จะปัดเศษไม่สามารถแก้ปัญหาได้
การตรวจสอบ / แก้ไขพิเศษเกี่ยวกับผลการคำนวณระดับกลางทันทีหลังจากใช้ฟังก์ชั่นที่ไม่ต่อเนื่องหรือผู้ประกอบการอาจจำเป็นต้องใช้ สำหรับกรณีเฉพาะ (ตัวดำเนินการ modula) ดูคำตอบของฉันในคำถาม: ทำไมตัวดำเนินการมอดุลัสจึงส่งกลับเศษส่วนใน javascript

ดีกว่าหลีกเลี่ยงปัญหา

บ่อยครั้งที่มีประสิทธิภาพมากกว่าในการหลีกเลี่ยงปัญหาเหล่านี้โดยใช้ชนิดข้อมูล (รูปแบบจำนวนเต็มหรือจุดคงที่) สำหรับการคำนวณเช่นนี้ซึ่งสามารถจัดเก็บอินพุตที่คาดไว้โดยไม่มีข้อผิดพลาดในการปัดเศษ ตัวอย่างของที่คุณไม่ควรใช้ค่าจุดลอยตัวสำหรับการคำนวณทางการเงิน


4

มีลักษณะที่คงที่จุดเลขคณิต มันอาจจะแก้ปัญหาของคุณได้ถ้าช่วงของตัวเลขที่คุณต้องการใช้มีขนาดเล็ก (เช่นสกุลเงิน) ฉันจะปัดมันออกเป็นทศนิยมสองสามค่าซึ่งเป็นวิธีที่ง่ายที่สุด


5
ปัญหาไม่ได้เป็นทศนิยมเมื่อเทียบกับจุดคงที่ปัญหาคือไบนารีกับทศนิยม
Michael Borgwardt

4

ลองห้องสมุดเลขคณิตของฉัน chiliadic ซึ่งคุณสามารถดูที่นี่ หากคุณต้องการรุ่นที่ใหม่กว่าฉันจะได้รับหนึ่ง


4

คุณไม่สามารถแสดงเศษส่วนทศนิยมส่วนใหญ่ด้วยประเภทจุดทศนิยมแบบไบนารี (ซึ่งเป็นสิ่งที่ ECMAScript ใช้เพื่อแสดงค่าจุดลอยตัว) ดังนั้นจึงไม่มีทางออกที่สง่างามเว้นแต่คุณจะใช้ประเภทเลขคณิตความแม่นยำตามอำเภอใจหรือทศนิยมชนิดอิงทศนิยม ยกตัวอย่างเช่นแอพพลิเคคำนวณที่มากับ Windows ตอนนี้ใช้ความแม่นยำทางคณิตศาสตร์โดยพลการจะแก้ปัญหานี้


4

ป้อนคำอธิบายรูปภาพที่นี่

    You can use library https://github.com/MikeMcl/decimal.js/. 
    it will   help  lot to give proper solution. 
    javascript console output 95 *722228.630 /100 = 686117.1984999999
    decimal library implementation 
    var firstNumber = new Decimal(95);
    var secondNumber = new Decimal(722228.630);
    var thirdNumber = new Decimal(100);
    var partialOutput = firstNumber.times(secondNumber);
    console.log(partialOutput);
    var output = new Decimal(partialOutput).div(thirdNumber);
    alert(output.valueOf());
    console.log(output.valueOf())== 686117.1985

3

คุณพูดถูกเพราะเหตุผลคือความแม่นยำ จำกัด ของจำนวนจุดลอยตัว จัดเก็บหมายเลขเหตุผลของคุณเป็นส่วนหนึ่งของจำนวนเต็มสองจำนวนและในสถานการณ์ส่วนใหญ่คุณจะสามารถจัดเก็บหมายเลขโดยไม่สูญเสียความแม่นยำใด ๆ เมื่อพูดถึงการพิมพ์คุณอาจต้องการแสดงผลเป็นเศษส่วน ด้วยการเป็นตัวแทนที่ฉันเสนอให้มันกลายเป็นเรื่องไม่สำคัญ

แน่นอนว่าไม่ได้ช่วยอะไรมากมายกับตัวเลขที่ไม่มีเหตุผล แต่คุณอาจต้องการที่จะเพิ่มประสิทธิภาพการคำนวณของคุณในทางที่พวกเขาจะทำให้เกิดปัญหาน้อยที่สุด sqrt(3)^2)(เช่นการตรวจสอบสถานการณ์เช่น


คุณมีสิทธิเหตุผลที่ว่าคือความแม่นยำ จำกัด ของตัวเลขทศนิยม - <pedant>จริง OP ใส่มันลงไปไม่แน่ชัดการดำเนินงานจุดลอยซึ่งเป็นสิ่งที่ผิด</pedant>
detly

3

ฉันมีปัญหาข้อผิดพลาดในการปัดเศษที่น่ารังเกียจกับ mod 3 บางครั้งเมื่อฉันควรได้รับ 0 ฉันจะได้รับ. 000 ... 01 ง่ายพอที่จะจัดการได้แค่ทดสอบ <= .01 แต่บางครั้งฉันก็จะได้ 2.99999999999998 โอ๊ย!

BigNumbersแก้ไขปัญหา แต่แนะนำให้คนอื่นปัญหาที่น่าขัน เมื่อพยายามโหลด 8.5 ลงใน BigNumbers ฉันได้รับแจ้งว่ามันคือ 8.4999 จริง ๆ …และมีตัวเลขสำคัญมากกว่า 15 หลัก นี่หมายความว่า BigNumbers ไม่สามารถยอมรับได้ (ฉันเชื่อว่าฉันพูดถึงปัญหานี้ค่อนข้างน่าขัน)

วิธีแก้ปัญหาแดกดันง่าย ๆ :

x = Math.round(x*100);
// I only need 2 decimal places, if i needed 3 I would use 1,000, etc.
x = x / 100;
xB = new BigNumber(x);

2

ใช้หมายเลข (1.234443) .toFixed (2); มันจะพิมพ์ 1.23

function test(){
    var x = 0.1 * 0.2;
    document.write(Number(x).toFixed(2));
}
test();

2

decimal.js , big.jsหรือbignumber.jsสามารถนำมาใช้เพื่อหลีกเลี่ยงปัญหาการจัดการจุดลอยตัวใน Javascript:

0.1 * 0.2                                // 0.020000000000000004
x = new Decimal(0.1)
y = x.times(0.2)                          // '0.2'
x.times(0.2).equals(0.2)                  // true

big.js: มินิมอล; ง่ายต่อการใช้; ความแม่นยำที่ระบุในตำแหน่งทศนิยม ความแม่นยำที่ใช้กับแผนกเท่านั้น

bignumber.js: ฐาน 2-64; ตัวเลือกการกำหนดค่า; น่าน; อินฟินิตี้; ความแม่นยำที่ระบุในตำแหน่งทศนิยม ความแม่นยำที่ใช้กับแผนกเท่านั้น คำนำหน้าฐาน

ทศนิยม. js: ฐาน 2-64; ตัวเลือกการกำหนดค่า; น่าน; อินฟินิตี้; อำนาจที่ไม่ใช่จำนวนเต็ม, exp, ln, log; ความแม่นยำที่ระบุไว้ในหลักสำคัญ; ใช้ความแม่นยำเสมอ ตัวเลขสุ่ม

ลิงก์ไปยังการเปรียบเทียบโดยละเอียด


2

สวยงามคาดเดาได้และสามารถใช้ซ้ำได้

มาจัดการกับปัญหาด้วยวิธีที่นำกลับมาใช้ใหม่ได้อย่างสง่างาม เจ็ดบรรทัดต่อไปนี้จะช่วยให้คุณเข้าถึงความแม่นยำของจุดลอยตัวที่คุณต้องการได้ทุกหมายเลขเพียงต่อ.decimalท้ายหมายเลขสูตรหรือMathฟังก์ชันในตัว

// First extend the native Number object to handle precision. This populates
// the functionality to all math operations.

Object.defineProperty(Number.prototype, "decimal", {
  get: function decimal() {
    Number.precision = "precision" in Number ? Number.precision : 3;
    var f = Math.pow(10, Number.precision);
    return Math.round( this * f ) / f;
  }
});


// Now lets see how it works by adjusting our global precision level and 
// checking our results.

console.log("'1/3 + 1/3 + 1/3 = 1' Right?");
console.log((0.3333 + 0.3333 + 0.3333).decimal == 1); // true

console.log(0.3333.decimal); // 0.333 - A raw 4 digit decimal, trimmed to 3...

Number.precision = 3;
console.log("Precision: 3");
console.log((0.8 + 0.2).decimal); // 1
console.log((0.08 + 0.02).decimal); // 0.1
console.log((0.008 + 0.002).decimal); // 0.01
console.log((0.0008 + 0.0002).decimal); // 0.001

Number.precision = 2;
console.log("Precision: 2");
console.log((0.8 + 0.2).decimal); // 1
console.log((0.08 + 0.02).decimal); // 0.1
console.log((0.008 + 0.002).decimal); // 0.01
console.log((0.0008 + 0.0002).decimal); // 0

Number.precision = 1;
console.log("Precision: 1");
console.log((0.8 + 0.2).decimal); // 1
console.log((0.08 + 0.02).decimal); // 0.1
console.log((0.008 + 0.002).decimal); // 0
console.log((0.0008 + 0.0002).decimal); // 0

Number.precision = 0;
console.log("Precision: 0");
console.log((0.8 + 0.2).decimal); // 1
console.log((0.08 + 0.02).decimal); // 0
console.log((0.008 + 0.002).decimal); // 0
console.log((0.0008 + 0.0002).decimal); // 0

ไชโย!


2
หากคุณเลือกที่จะ downvote อย่างน้อยก็ให้เหตุผล
Bernesto

1

ใช้

var x = 0.1*0.2;
 x =Math.round(x*Math.pow(10,2))/Math.pow(10,2);

4
อืม ... แต่ทราบว่ามันจะปัดเศษทศนิยมเป็น 2 เสมอ แน่นอนว่าจะเป็นตัวเลือก แต่สิ่งที่เกี่ยวกับการคำนวณ 0.55 * 0.55 (เนื่องจากฉันไม่ทราบจำนวนที่แน่นอนล่วงหน้าที่จะให้ 0.3 แทน 0.3025 แน่นอนฉันสามารถใช้Math.round(x*Math.pow(10,4))/Math.pow(10,4);การปัดเศษเป็นตัวเลือกเสมอ แต่ผมแค่อยากจะรู้ว่ามีบางทางออกที่ดีกว่า
Juri



1

สิ่งนี้ใช้ได้กับฉัน:

function round_up( value, precision ) { 
    var pow = Math.pow ( 10, precision ); 
    return ( Math.ceil ( pow * value ) + Math.ceil ( pow * value - Math.ceil ( pow * value ) ) ) / pow; 
}

round_up(341.536, 2); // 341.54

1
น่าเสียดาย round_up (4.15,2) => 4.16
jrg

1

เอาต์พุตโดยใช้ฟังก์ชันต่อไปนี้:

var toFixedCurrency = function(num){
    var num = (num).toString();
    var one = new RegExp(/\.\d{1}$/).test(num);
    var two = new RegExp(/\.\d{2,}/).test(num);
    var result = null;

    if(one){ result = num.replace(/\.(\d{1})$/, '.$10');
    } else if(two){ result = num.replace(/\.(\d{2})\d*/, '.$1');
    } else { result = num*100; }

    return result;
}

function test(){
    var x = 0.1 * 0.2;
    document.write(toFixedCurrency(x));
}

test();

toFixedCurrency(x)ให้ความสนใจที่จะส่งออก

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.