ปัดเศษทศนิยมไม่เกิน 2 ตำแหน่ง (หากจำเป็น)


2757

ฉันต้องการที่จะรอบที่มากที่สุดทศนิยม 2 ตำแหน่ง แต่เฉพาะในกรณีที่จำเป็น

การป้อนข้อมูล:

10
1.7777777
9.1

เอาท์พุท:

10
1.78
9.1

ฉันจะทำสิ่งนี้ใน JavaScript ได้อย่างไร


22
ฉันทำซอกับเทคนิคต่าง ๆ ที่นำเสนอเป็นวิธีแก้ปัญหาที่นี่ ... เพื่อให้คุณสามารถเปรียบเทียบ: ซอ
dsdsdsdsd

37
Number.EPSILONดูเหมือนไม่มีใครที่จะตระหนักถึง Math.round( num * 100 + Number.EPSILON ) / 100ใช้
cronvel

3
สำหรับผู้อ่านใหม่คุณไม่สามารถทำเช่นนี้จนกว่าคุณจะมีผลประเภทสตริง คณิตศาสตร์จุดลอยตัวในการแทนค่าตัวเลขไบนารีภายในหมายความว่ามีจำนวนเสมอที่ไม่สามารถแสดงเป็นทศนิยมอย่างเรียบร้อย
Walf

9
@cronvel คุณช่วยอธิบายสาเหตุของการใช้Number.EPSILONที่นี่ได้ไหม?
บรูซซัน

5
ฉันล้มหลุมกระต่ายและทดสอบคำตอบที่น่าสนใจในหน้านี้ (หน้าแรกเท่านั้น) นี่เป็น Codepen คำแนะนำ: ยิ่งมีคำตอบมากเกินเท่าไหร่โอกาสที่จะได้รับก็จะต่ำลงเท่านั้น
Adam Jagosz

คำตอบ:


3491

ใช้ Math.round(num * 100) / 100

แก้ไข:เพื่อให้แน่ใจว่าสิ่งต่างๆเช่น 1.005 รอบถูกต้องเราใช้

Math.round((num + Number.EPSILON) * 100) / 100


395
ขณะนี้จะใช้งานได้สำหรับกรณีส่วนใหญ่ แต่จะไม่ทำงานสำหรับ 1.005 ซึ่งจะจบลงด้วยการเป็น 1 แทนที่จะเป็น 1.01
James

83
@James Wow แปลกจริง ๆ - ฉันทำงานในคอนโซล Chrome dev และฉันสังเกตเห็นว่า 1.005 * 100 = 100.49999999999999 Math.round (100.49999999999999) ประเมินถึง 100 ในขณะที่ Math.round (100.5) ประเมินถึง 101. IE9 ทำสิ่งเดียวกัน นี่เป็นเพราะความแปลกจุดลอยในจาวาสคริปต์
stinkycheeseman

153
วิธีแก้ปัญหาง่ายๆ สำหรับ 2 DP, Math.round((num + 0.00001) * 100) / 100การใช้งาน ลองMath.round((1.005 + 0.00001) * 100) / 100และMath.round((1.0049 + 0.00001) * 100) / 100
mrkschan

31
@ mrkschan ทำไมถึงใช้งานได้และนั่นเป็นสิ่งที่เข้าใจผิดได้สำหรับตัวเลขทั้งหมด?
CMCDragonkai

86
สำหรับผู้ที่ไม่เข้าใจเทคนิคนี้เรียกว่าการปรับสเกล โดยทั่วไปสิ่งที่คำตอบทำตรงนี้คือนำตัวเลขสองตัวข้ามจุดทศนิยมเปลี่ยนตัวเลขเป็นจำนวนเต็มเพื่อหลีกเลี่ยงปัญหาจุดลอยตัวที่บ้าคลั่งแล้วปัดมันกลับมาเป็นค่าเดิมก่อนหารด้วย 100 และคุณมี ตอบ 2dp
Alex_Nabu

3062

หากค่าเป็นประเภทข้อความ:

parseFloat("123.456").toFixed(2);

ถ้าค่าเป็นตัวเลข:

var numb = 123.23454;
numb = numb.toFixed(2);

มีข้อเสียที่ค่าเช่น 1.5 จะให้ "1.50" เป็นผลลัพธ์ การแก้ไขที่แนะนำโดย @minitech:

var numb = 1.5;
numb = +numb.toFixed(2);
// Note the plus sign that drops any "extra" zeroes at the end.
// It changes the result (which is a string) into a number again (think "0 + foo"),
// which means that it uses only as many digits as necessary.

ดูเหมือนว่าMath.roundเป็นทางออกที่ดีกว่า แต่มันไม่ใช่! ในบางกรณีมันจะไม่ถูกต้อง:

Math.round(1.005 * 1000)/1000 // Returns 1 instead of expected 1.01!

toFixed () จะไม่ถูกต้องในบางกรณี (ทดสอบใน Chrome v.55.0.2883.87)!

ตัวอย่าง:

parseFloat("1.555").toFixed(2); // Returns 1.55 instead of 1.56.
parseFloat("1.5550").toFixed(2); // Returns 1.55 instead of 1.56.
// However, it will return correct result if you round 1.5551.
parseFloat("1.5551").toFixed(2); // Returns 1.56 as expected.

1.3555.toFixed(3) // Returns 1.355 instead of expected 1.356.
// However, it will return correct result if you round 1.35551.
1.35551.toFixed(2); // Returns 1.36 as expected.

ฉันเดาว่านี่เป็นเพราะ 1.555 จริงๆแล้วมันเหมือนกับ float 1.55499994 ที่อยู่เบื้องหลัง

โซลูชันที่ 1คือการใช้สคริปต์ด้วยอัลกอริทึมการปัดเศษที่ต้องการตัวอย่างเช่น:

function roundNumber(num, scale) {
  if(!("" + num).includes("e")) {
    return +(Math.round(num + "e+" + scale)  + "e-" + scale);
  } else {
    var arr = ("" + num).split("e");
    var sig = ""
    if(+arr[1] + scale > 0) {
      sig = "+";
    }
    return +(Math.round(+arr[0] + "e" + sig + (+arr[1] + scale)) + "e-" + scale);
  }
}

https://plnkr.co/edit/uau8BlS1cqbvWPCHJeOy?p=preview

หมายเหตุ:นี่ไม่ใช่วิธีการแก้ปัญหาสากลสำหรับทุกคน มีขั้นตอนวิธีการปัดเศษที่แตกต่างกันหลายประการการใช้งานของคุณอาจแตกต่างกันไปขึ้นอยู่กับความต้องการของคุณ https://en.wikipedia.org/wiki/Rounding

โซลูชันที่ 2คือการหลีกเลี่ยงการคำนวณส่วนหน้าและดึงค่าที่ปัดเศษจากเซิร์ฟเวอร์ส่วนหลัง


81
วิธีนี้ (toFixed) เป็นวิธีที่ดีและใช้งานได้สำหรับฉัน แต่วิธีนี้ไม่สอดคล้องกับคำขอดั้งเดิมของ "เฉพาะเมื่อจำเป็น" (มันปัดเศษ 1.5 ถึง 1.50, ซึ่งทำลายสเป็ค)
ต่อ Lundberg

29
สำหรับข้อกำหนด "เมื่อจำเป็น" ให้ทำดังนี้: parseFloat(number.toFixed(decimalPlaces)); @PerLundberg
Onur Yıldırım

36
parseFloat("55.555").toFixed(2)ส่งคืน"55.55"ในคอนโซล Chrome dev
Levi Botelho

22
ไม่มีประโยชน์จากการใช้ toFixed แทน Math.round toFixed นำไปสู่ปัญหาการปัดเศษที่ค่อนข้างเหมือนกัน (ลองใช้กับ 5.555 และ 1.005) แต่เหมือน 500x (ไม่มีการล้อเล่น) ช้ากว่า Math.round ... ดูเหมือนว่าคำตอบ @MarkG นั้นแม่นยำกว่า
Pierre

17
toFixed ไม่ใช่ "บางครั้ง" จะส่งคืนสตริง แต่จะส่งคืนสตริงเสมอ
McGuireV10

464

คุณสามารถใช้ได้

function roundToTwo(num) {    
    return +(Math.round(num + "e+2")  + "e-2");
}

ผมพบว่าในช่วงนี้ในMDN ทางของพวกเขาหลีกเลี่ยงการมีปัญหากับ 1.005 ที่ได้รับการกล่าวถึง

roundToTwo(1.005)
1.01
roundToTwo(10)
10
roundToTwo(1.7777777)
1.78
roundToTwo(9.1)
9.1
roundToTwo(1234.5678)
1234.57

13
@Redsandro, เป็นเทียบเท่าการข่มขู่ของการใช้+(val) Number(val)การต่อ "e-2" เข้ากับหมายเลขทำให้เกิดสตริงที่จำเป็นต้องแปลงกลับเป็นตัวเลข
แจ็ค

41
ระวังว่าสำหรับทุ่นลอยขนาดใหญ่และเล็กที่จะสร้าง NaN ตั้งแต่เช่น + "1e-21 + 2" จะไม่ถูกวิเคราะห์อย่างถูกต้อง
Pierre

16
คุณได้ 'แก้ไข' ปัญหา 1.005 'แล้ว แต่เปิดตัวใหม่: ตอนนี้ในคอนโซล Chrome roundToTwo(1.0049999999999999)ออกมาเป็น 1.01 (อย่างหลีกเลี่ยงไม่ได้นับตั้งแต่1.0049999999999999 == 1.005) ฉันคิดว่าโฟลตที่คุณได้รับถ้าคุณพิมพ์num = 1.005'ชัด' 'ควร' รอบที่ 1.00 เพราะค่าที่แน่นอนของจำนวนน้อยกว่า 1.005 แน่นอนว่ามันก็ดูเหมือนว่าสตริง '1.005' 'ชัด' 'ควร' จะถูกปัดเศษเป็น 1.01 ความจริงที่ว่าคนที่แตกต่างกันดูเหมือนจะมีสัญชาติญาณต่างกันเกี่ยวกับพฤติกรรมที่ถูกต้องที่แท้จริงที่นี่เป็นส่วนหนึ่งของสาเหตุที่มันซับซ้อน
Mark Amery

35
ไม่มีหมายเลข (จุดลอย) ในระหว่าง1.0049999999999999และ1.005ดังนั้นตามคำนิยามพวกเขาเป็นหมายเลขเดียวกัน สิ่งนี้เรียกว่าการตัดด้วยความประณีต
Azmisov

6
@Azmisov ถูกต้อง ในขณะที่1.00499 < 1.005เป็นtrue, ประเมิน1.0049999999999999 < 1.005 false
falconepl

146

คำตอบของ MarkG นั้นถูกต้อง นี่คือส่วนขยายทั่วไปสำหรับจำนวนทศนิยมใด ๆ

Number.prototype.round = function(places) {
  return +(Math.round(this + "e+" + places)  + "e-" + places);
}

การใช้งาน:

var n = 1.7777;    
n.round(2); // 1.78

ทดสอบหน่วย:

it.only('should round floats to 2 places', function() {

  var cases = [
    { n: 10,      e: 10,    p:2 },
    { n: 1.7777,  e: 1.78,  p:2 },
    { n: 1.005,   e: 1.01,  p:2 },
    { n: 1.005,   e: 1,     p:0 },
    { n: 1.77777, e: 1.8,   p:1 }
  ]

  cases.forEach(function(testCase) {
    var r = testCase.n.round(testCase.p);
    assert.equal(r, testCase.e, 'didn\'t get right number');
  });
})

20
ปิแอร์หยิบยกประเด็นปัญหาขึ้นมาพร้อมกับคำตอบของ MarkG
dsjoerg

9
หมายเหตุ: หากคุณไม่ต้องการเปลี่ยน Number.prototype - เพียงแค่เขียนนี่เป็นฟังก์ชั่น: function round(number, decimals) { return +(Math.round(number + "e+" + decimals) + "e-" + decimals); }
Philipp Tsipman

2
วิธีที่ยอดเยี่ยมในการขยายสิ่งนี้ คุณสามารถตรวจสอบว่าทศนิยมเป็นลบหรือไม่แล้วสลับ e + และ e- จากนั้น n = 115; n.round (-2); จะผลิต 100
Lee Louviere

3
ลอง n: 1e + 19 - ส่งคืน NaN
DavidJ

3
อัลกอริทึมนี้จะปัดเศษขึ้นเสมอ (แทนที่จะอยู่ห่างจากศูนย์) ดังนั้นในกรณีที่ตัวเลขติดลบผลลัพธ์ไม่ใช่สิ่งที่คุณคาดหวัง:(-1.005).round(2) === -1
Aleksej Komarov

123

คุณควรใช้:

Math.round( num * 100 + Number.EPSILON ) / 100

Number.EPSILONดูเหมือนไม่มีใครที่จะตระหนักถึง

นอกจากนี้ยังเป็นที่น่าสังเกตว่านี่ไม่ใช่JavaScript ที่แปลกประหลาดเหมือนที่บางคนกล่าวไว้

นั่นเป็นเพียงวิธีที่ตัวเลขจุดลอยตัวทำงานในคอมพิวเตอร์ เช่นเดียวกับ 99% ของการเขียนโปรแกรมภาษาจาวาสคริปต์ไม่ได้ทำให้บ้านหมายเลขจุดลอย; มันขึ้นอยู่กับ CPU / FPU สำหรับสิ่งนั้น คอมพิวเตอร์ใช้เลขฐานสองและในเลขฐานสองนั้นไม่มีตัวเลขใด ๆ เหมือน0.1แต่เป็นการประมาณเลขฐานสองสำหรับสิ่งนั้น ทำไม? ด้วยเหตุผลเดียวกันที่ 1/3 ไม่สามารถเขียนเป็นทศนิยมได้ค่าของมันคือ 0.33333333 ... ด้วยจำนวนอนันต์ของสาม

มานี่Number.EPSILONสิ ตัวเลขนั้นคือความแตกต่างระหว่าง 1 และหมายเลขถัดไปที่มีอยู่ในตัวเลขทศนิยมที่มีความแม่นยำสองเท่า ที่มันมีตัวเลขระหว่างไม่เป็น1และ 1 Number.EPSILON+

แก้ไข:

ดังที่ถามไว้ในความคิดเห็นเราจะอธิบายสิ่งหนึ่ง: การเพิ่ม Number.EPSILONมีความเกี่ยวข้องเฉพาะเมื่อค่ารอบคือผลลัพธ์ของการดำเนินการทางคณิตศาสตร์

ไม่มีประโยชน์เมื่อค่ามาจากแหล่งข้อมูลโดยตรง (เช่นตัวอักษรอินพุตของผู้ใช้หรือเซ็นเซอร์)

แก้ไข (2019):

เช่น @maganap และบางคนชี้ให้เห็นว่าควรเพิ่มNumber.EPSILONก่อนที่จะทวีคูณ:

Math.round( ( num + Number.EPSILON ) * 100 ) / 100

แก้ไข (ธันวาคม 2562):

เมื่อเร็ว ๆ นี้ฉันใช้ฟังก์ชั่นที่คล้ายกับฟังก์ชันนี้เพื่อเปรียบเทียบจำนวน epsilon ที่ทราบ:

const ESPILON_RATE = 1 + Number.EPSILON ;
const ESPILON_ZERO = Number.MIN_VALUE ;

function epsilonEquals( a , b ) {
  if ( Number.isNaN( a ) || Number.isNaN( b ) ) {
    return false ;
  }
  if ( a === 0 || b === 0 ) {
    return a <= b + EPSILON_ZERO && b <= a + EPSILON_ZERO ;
  }
  return a <= b * EPSILON_RATE && b <= a * EPSILON_RATE ;
}

Use-case ของฉันคือlib + การตรวจสอบความถูกต้องของข้อมูลที่ฉันกำลังพัฒนาเป็นเวลาหลายปี

ในความเป็นจริงในรหัสฉันใช้ESPILON_RATE = 1 + 4 * Number.EPSILONและEPSILON_ZERO = 4 * Number.MIN_VALUE(สี่ครั้ง epsilon) เพราะฉันต้องการตรวจสอบความเท่าเทียมกันหลวมพอสำหรับการสะสมข้อผิดพลาดจุดลอย

จนถึงตอนนี้มันดูสมบูรณ์แบบสำหรับฉัน ฉันหวังว่ามันจะช่วย


1
@palota คุณสามารถกำหนดจำนวนขวากหนามจริงๆเช่น cronvel EPSILON กำลังพูดถึง
Daniel San

3
นี่คือคำตอบที่ถูกต้อง! ตัวปัญหาเองนั้นเกี่ยวข้องกับการทำงานของตัวเลขภายในไม่เกี่ยวกับจาวาสคริปต์
Daniel San

22
ที่จริงแล้วคุณต้องเพิ่ม epsilon ก่อนที่จะคูณด้วย Math.round( (num + Number.EPSILON) * 100) / 100100 ฉันยอมรับว่านี่เป็นวิธีที่ถูกต้องในการปัดเศษอย่างถูกต้อง (แม้ว่าจะไม่ใช่สิ่งที่ถูกถามในคำถามนี้)
maganap

2
@cronvel อืม คุณถูก; ทางเลือกของทิศทางที่ไม่เข้าท่า ดังนั้นฉันจึงคิดว่าการคัดค้านที่เหลืออยู่ของฉันก็คือการทำเช่นนี้เหมาะสมถ้าคุณทำงานในโดเมนที่คุณมีเหตุผลหลักที่จะคิดว่าคุณค่าของคุณคือ "จำนวนรอบ" หากคุณรู้ว่าข้อมูลที่คุณป้อนนั้นเป็นผลมาจากการดำเนินการทางคณิตศาสตร์อย่างง่ายบนตัวเลขที่มีจำนวนทศนิยมน้อยแล้วคุณอาจมีเพียง0.004999999999999999เพราะผลของข้อผิดพลาดจุดลอยตัวแบบผสมและผลลัพธ์ที่ถูกต้องทางคณิตศาสตร์อาจเป็น 0.005 ถ้ามันอ่านจากเซ็นเซอร์? ไม่มากนัก.
Mark Amery

1
@marchaos มันไม่ได้ล้มเหลว: ครึ่งหนึ่งจะถูกปัดเศษเสมอขึ้น เช่นMath.round(1.5)= 2 แต่Math.round(-1.5)= -1 ดังนั้นนี่คือความสอดคล้องอย่างสมบูรณ์ นี่ -1 มากกว่า -2 เช่น -1000 มากกว่า -1000.01 เพื่อไม่ให้สับสนกับจำนวนที่แน่นอนมากขึ้น
cronvel

84

.toFixed(NumberOfDecimalPlaces)หนึ่งสามารถใช้

var str = 10.234.toFixed(2); // => '10.23'
var number = Number(str); // => 10.23

4
เพราะยังจะเพิ่มศูนย์ต่อท้ายซึ่งเป็นไม่ใช่สิ่งที่คำถามเดิมถามหา
Alastair Maw

2
แต่ศูนย์ต่อท้ายจะถูกถอดออกอย่างง่ายดายด้วย regex เช่น `Number (10.10000.toFixed (2) .replace (/ 0 + $ /, ''))` => 10.1
Chad

1
@ Daniel คำตอบด้านบนเป็นเหมือนกัน ( ณ ตอนนี้) แต่ก็ไม่ได้alswaysรอบถูกต้องให้ลอง+(1.005).toFixed(2)ซึ่งผลตอบแทนแทน1 1.01
Emile Bergeron

3
@ChadMcElligott: regex ของคุณทำงานได้ไม่ดีกับจำนวนเต็ม: Number(9).toFixed(2).replace(/0+$/, '')=> "9. "
Jacob van Lingen

ดูเหมือนว่ามันจะไม่กลม มันเป็นแค่การตัดทอน ในบางกรณีดีพอ! ขอบคุณ
iedmrc

78

คำถามนี้ซับซ้อน

สมมติว่าเรามีฟังก์ชั่นซึ่งroundTo2DP(num)รับค่าทศนิยมเป็นอาร์กิวเมนต์และส่งคืนค่าที่ปัดเศษเป็นทศนิยม 2 ตำแหน่ง นิพจน์เหล่านี้แต่ละสิ่งควรประเมินเป็นอย่างไร

  • roundTo2DP(0.014999999999999999)
  • roundTo2DP(0.0150000000000000001)
  • roundTo2DP(0.015)

คำตอบ 'ชัดเจน' คือตัวอย่างแรกควรปัดเศษเป็น 0.01 (เพราะใกล้ถึง 0.01 มากกว่า 0.02) ในขณะที่อีกสองรอบเป็น 0.02 (เพราะ 0.01500000000000000000001 ใกล้กับ 0.02 มากกว่า 0.01 และ 0.015 อยู่ครึ่งทาง พวกเขาและมีการประชุมทางคณิตศาสตร์ที่ตัวเลขดังกล่าวได้ปัดเศษขึ้น)

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

0,01499999999999999944488848768742172978818416595458984375

ซึ่งใกล้เคียงกับ 0.01 มากกว่า 0.02

คุณจะเห็นว่าตัวเลขทั้งสามนั้นเหมือนกันที่คอนโซลเบราว์เซอร์ของคุณโหนดของ Node หรือล่าม JavaScript อื่น ๆ เพียงเปรียบเทียบพวกเขา:

> 0.014999999999999999 === 0.0150000000000000001
true

ดังนั้นเมื่อผมเขียนm = 0.0150000000000000001ที่คุ้มค่าแน่นอนของmที่ฉันจบลงด้วยการอยู่ใกล้กว่าก็คือการ0.01 0.02และถ้าฉันแปลงmเป็นสตริง ...

> var m = 0.0150000000000000001;
> console.log(String(m));
0.015
> var m = 0.014999999999999999;
> console.log(String(m));
0.015

... ฉันได้ 0.015 ซึ่งควรจะเป็น 0.02 และไม่เด่นชัดว่าเลขทศนิยม 56 ตำแหน่งที่ฉันกล่าวไว้ก่อนหน้านี้ว่าตัวเลขเหล่านี้ทั้งหมดเท่ากับ นี่คือเวทย์มนตร์มืดอะไร?

คำตอบที่สามารถพบได้ในข้อกำหนด ECMAScript ในส่วน7.1.12.1: ToString นำไปใช้กับประเภทจำนวน นี่คือกฎสำหรับการแปลง Number mบางส่วนเป็น String ส่วนสำคัญคือจุด 5 ซึ่งสร้างจำนวนเต็มsซึ่งจะใช้ตัวเลขในการแทนค่าสตริงของm :

ให้n , k , และsเป็นจำนวนเต็มเช่นที่k ≥ 1, 10 k -1s <10 k , ค่า Number สำหรับs × 10 n - kคือm , และkมีค่าน้อยที่สุด โปรดทราบว่า k คือจำนวนตัวเลขในการแทนค่าทศนิยมของs , sนั้นหารด้วย 10 ไม่ได้และหลักสำคัญน้อยที่สุดของsไม่จำเป็นต้องพิจารณาโดยไม่ซ้ำกันตามเกณฑ์เหล่านี้

ส่วนสำคัญที่นี่คือข้อกำหนดที่ว่า " kมีขนาดเล็กที่สุด" สิ่งที่ต้องการที่จำนวนเงินที่เป็นความต้องการที่กำหนดจำนวนmค่าของString(m)ต้องมีจำนวนน้อยที่สุดของตัวเลขNumber(String(m)) === mขณะที่ยังคงความพึงพอใจความต้องการว่า เนื่องจากเรารู้แล้ว0.015 === 0.0150000000000000001ตอนนี้มันชัดเจนว่าทำไมString(0.0150000000000000001) === '0.015'ต้องเป็นจริง

แน่นอนว่าการสนทนานี้ไม่ได้ตอบสิ่งที่roundTo2DP(m) ควรกลับโดยตรง ถ้าmค่าที่แน่นอนคือ 0.01499999999999999944488848768742172978818416595458984375 แต่การแทนค่าสตริงของมันคือ '0.015' แล้วคำตอบที่ถูกต้องคืออะไร- ทางคณิตศาสตร์, ทางปฏิบัติ, ปรัชญาหรืออะไรก็ตาม - เมื่อเราปัดเศษเป็นทศนิยมสองตำแหน่ง?

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

  • ค่าที่จะแสดงนั้นไม่ต่อเนื่องโดยเนื้อแท้เช่นจำนวนเงินในสกุลเงิน 3 ตำแหน่งทศนิยมเช่นดินาร์ ในกรณีนี้ค่าจริงของตัวเลขเช่น 0.015 คือ 0.015 และ 0.0149999999 ... การแทนค่าที่ได้รับในเลขทศนิยมแบบไบนารีนั้นเป็นข้อผิดพลาดในการปัดเศษ (แน่นอนว่าหลายคนจะโต้แย้งเหตุผลว่าคุณควรใช้ห้องสมุดทศนิยมสำหรับการจัดการค่าดังกล่าวและไม่เคยแสดงให้พวกเขาเป็นตัวเลขทศนิยมเลขฐานสองในสถานที่แรก)
  • ค่าถูกพิมพ์โดยผู้ใช้ ในกรณีนี้อีกครั้งจำนวนทศนิยมที่ป้อนจริงคือ 'จริง' มากกว่าการเป็นตัวแทนทศนิยมเลขฐานสองที่ใกล้ที่สุด

ในทางกลับกันคุณอาจต้องการเคารพค่าเลขฐานสองแบบลอยตัวและปัดเศษลงเมื่อค่าของคุณมาจากสเกลต่อเนื่องโดยเนื้อแท้ - ตัวอย่างเช่นหากเป็นการอ่านจากเซ็นเซอร์

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

/**
 * Converts num to a decimal string (if it isn't one already) and then rounds it
 * to at most dp decimal places.
 *
 * For explanation of why you'd want to perform rounding operations on a String
 * rather than a Number, see http://stackoverflow.com/a/38676273/1709587
 *
 * @param {(number|string)} num
 * @param {number} dp
 * @return {string}
 */
function roundStringNumberWithoutTrailingZeroes (num, dp) {
    if (arguments.length != 2) throw new Error("2 arguments required");

    num = String(num);
    if (num.indexOf('e+') != -1) {
        // Can't round numbers this large because their string representation
        // contains an exponent, like 9.99e+37
        throw new Error("num too large");
    }
    if (num.indexOf('.') == -1) {
        // Nothing to do
        return num;
    }

    var parts = num.split('.'),
        beforePoint = parts[0],
        afterPoint = parts[1],
        shouldRoundUp = afterPoint[dp] >= 5,
        finalNumber;

    afterPoint = afterPoint.slice(0, dp);
    if (!shouldRoundUp) {
        finalNumber = beforePoint + '.' + afterPoint;
    } else if (/^9+$/.test(afterPoint)) {
        // If we need to round up a number like 1.9999, increment the integer
        // before the decimal point and discard the fractional part.
        finalNumber = Number(beforePoint)+1;
    } else {
        // Starting from the last digit, increment digits until we find one
        // that is not 9, then stop
        var i = dp-1;
        while (true) {
            if (afterPoint[i] == '9') {
                afterPoint = afterPoint.substr(0, i) +
                             '0' +
                             afterPoint.substr(i+1);
                i--;
            } else {
                afterPoint = afterPoint.substr(0, i) +
                             (Number(afterPoint[i]) + 1) +
                             afterPoint.substr(i+1);
                break;
            }
        }

        finalNumber = beforePoint + '.' + afterPoint;
    }

    // Remove trailing zeroes from fractional part before returning
    return finalNumber.replace(/0+$/, '')
}

ตัวอย่างการใช้งาน:

> roundStringNumberWithoutTrailingZeroes(1.6, 2)
'1.6'
> roundStringNumberWithoutTrailingZeroes(10000, 2)
'10000'
> roundStringNumberWithoutTrailingZeroes(0.015, 2)
'0.02'
> roundStringNumberWithoutTrailingZeroes('0.015000', 2)
'0.02'
> roundStringNumberWithoutTrailingZeroes(1, 1)
'1'
> roundStringNumberWithoutTrailingZeroes('0.015', 2)
'0.02'
> roundStringNumberWithoutTrailingZeroes(0.01499999999999999944488848768742172978818416595458984375, 2)
'0.02'
> roundStringNumberWithoutTrailingZeroes('0.01499999999999999944488848768742172978818416595458984375', 2)
'0.01'

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

(เป็นอีกทางเลือกหนึ่งคุณสามารถลองใช้ไลบรารีround10ซึ่งมีฟังก์ชั่นการทำงานที่คล้ายคลึงกันด้วยการปรับใช้ที่แตกต่างกันอย่างดุเดือด)

แต่ถ้าคุณมี Number แบบที่สอง - ค่าที่นำมาจากสเกลต่อเนื่องซึ่งไม่มีเหตุผลที่จะคิดว่าการแทนค่าทศนิยมโดยประมาณที่มีทศนิยมน้อยกว่านั้นจะแม่นยำกว่าที่มีมากขึ้นหรือไม่ ในกรณีดังกล่าวเราไม่ต้องการเคารพการแทนค่าสตริงเนื่องจากการแสดงดังกล่าว (ตามที่อธิบายไว้ในสเป็ค) นั้นเรียงลำดับแล้ว เราไม่ต้องการทำผิดพลาดในการพูดว่า "0.014999999 ... 375 รอบสูงสุด 0.015 ซึ่งรอบมากถึง 0.02 ดังนั้น 0.014999999 ... 375 รอบสูงสุด 0.02"

ที่นี่เราสามารถใช้toFixedวิธีการในตัว โปรดทราบว่าด้วยการเรียกNumber()ใช้ String ที่ส่งคืนโดยtoFixedเราจะได้รับหมายเลขที่การแทนค่าสตริงไม่มีศูนย์ต่อท้าย (ด้วยวิธีที่ JavaScript คำนวณการแทนค่าสตริงของตัวเลขที่กล่าวถึงก่อนหน้านี้ในคำตอบนี้)

/**
 * Takes a float and rounds it to at most dp decimal places. For example
 *
 *     roundFloatNumberWithoutTrailingZeroes(1.2345, 3)
 *
 * returns 1.234
 *
 * Note that since this treats the value passed to it as a floating point
 * number, it will have counterintuitive results in some cases. For instance,
 * 
 *     roundFloatNumberWithoutTrailingZeroes(0.015, 2)
 *
 * gives 0.01 where 0.02 might be expected. For an explanation of why, see
 * http://stackoverflow.com/a/38676273/1709587. You may want to consider using the
 * roundStringNumberWithoutTrailingZeroes function there instead.
 *
 * @param {number} num
 * @param {number} dp
 * @return {number}
 */
function roundFloatNumberWithoutTrailingZeroes (num, dp) {
    var numToFixedDp = Number(num).toFixed(dp);
    return Number(numToFixedDp);
}

นี้ไม่ได้ทำงานในกรณีบางขอบ: ลอง ( jsfiddle ) roundStringNumberWithoutTrailingZeroes(362.42499999999995, 2)ด้วย ผลที่คาดหวัง (ในขณะที่ PHP ):echo round(362.42499999999995, 2) 362.43ผลลัพธ์ที่แท้จริง:362.42
ดร. Gianluigi Zane Zanettini

1
@ Dr.GianluigiZaneZanettini Huh แปลก. ฉันไม่แน่ใจว่าทำไม PHP ถึงroundให้ 362.43 ที่ดูเหมือนว่าผิดอย่างสังหรณ์ใจเนื่องจาก 362.42499999999995 นั้นน้อยกว่า 362.425 (ในวิชาคณิตศาสตร์และในรหัส - 362.42499999999995 < 362.425เป็นจริงทั้งใน JS และ PHP) หรือไม่คำตอบของ PHP 362.43 - 362.42499999999995 > 362.42499999999995 - 362.42ลดระยะห่างระหว่างเดิมและโค้งมนหมายเลขจุดลอยตั้งแต่ ตามphp.net/manual/en/function.round.php PHP นั้นroundใช้มาตรฐาน C99 ฉันจะต้องเสี่ยงเข้าไปในดินแดนแห่งซีเพื่อทำความเข้าใจว่าเกิดอะไรขึ้น
Mark Amery

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

1
@ Dr.GianluigiZaneZanettini ฉันได้สร้างรายงานข้อผิดพลาดแล้ว: bugs.php.net/bug.php?id=75644
Mark Amery

2
@ Dr.GianluigiZaneZanettini "ฉันจะทำให้รู้สึกใด? - ไม่ นั่นไม่ใช่วิธีการปัดเศษ 1.000005 ลงท้ายด้วยห้า แต่ถ้าปัดเศษเป็นจำนวนเต็มที่ใกล้ที่สุดคำตอบควรเป็น 1 ไม่ใช่ 2 เช่นเดียวกัน 1499995 ลงท้ายด้วยห้า แต่ถ้าปัดเศษเป็นล้านใกล้เคียงที่สุดผลลัพธ์ควรเป็น 1000000 ไม่ใช่ 2000000 ในกรณีของ 362.42499999999995 เมื่อถูกปัดเศษเป็น 2 DP สิ่งที่ควรกำหนดทิศทางการปัดเศษคือทศนิยมที่สามซึ่งก็คือ 4
Mark Amery

76

พิจารณา.toFixed()และ.toPrecision():

http://www.javascriptkit.com/javatutors/formatnumber.shtml


1
toFixed เพิ่มจุดทศนิยมให้กับทุกค่าไม่ว่าจะเกิดอะไรขึ้น
stinkycheeseman

13
ทั้งคู่ไม่มีประโยชน์ที่นี่
Esailija

น่าเสียดายที่ทั้งสองฟังก์ชั่นจะเพิ่มทศนิยมพิเศษซึ่ง @stinkycheeseman ดูเหมือนจะไม่ต้องการ
jackwanders


2
พวกเขากลับสตริงและตัวเลขไม่ได้ดังนั้นพวกเขาจะไม่ได้จัดรูปแบบและการคำนวณ ..
saimiris_devel

63

วิธีการปัดเศษที่แม่นยำ ที่มา: Mozilla

(function(){

    /**
     * Decimal adjustment of a number.
     *
     * @param   {String}    type    The type of adjustment.
     * @param   {Number}    value   The number.
     * @param   {Integer}   exp     The exponent (the 10 logarithm of the adjustment base).
     * @returns {Number}            The adjusted value.
     */
    function decimalAdjust(type, value, exp) {
        // If the exp is undefined or zero...
        if (typeof exp === 'undefined' || +exp === 0) {
            return Math[type](value);
        }
        value = +value;
        exp = +exp;
        // If the value is not a number or the exp is not an integer...
        if (isNaN(value) || !(typeof exp === 'number' && exp % 1 === 0)) {
            return NaN;
        }
        // Shift
        value = value.toString().split('e');
        value = Math[type](+(value[0] + 'e' + (value[1] ? (+value[1] - exp) : -exp)));
        // Shift back
        value = value.toString().split('e');
        return +(value[0] + 'e' + (value[1] ? (+value[1] + exp) : exp));
    }

    // Decimal round
    if (!Math.round10) {
        Math.round10 = function(value, exp) {
            return decimalAdjust('round', value, exp);
        };
    }
    // Decimal floor
    if (!Math.floor10) {
        Math.floor10 = function(value, exp) {
            return decimalAdjust('floor', value, exp);
        };
    }
    // Decimal ceil
    if (!Math.ceil10) {
        Math.ceil10 = function(value, exp) {
            return decimalAdjust('ceil', value, exp);
        };
    }
})();

ตัวอย่าง:

// Round
Math.round10(55.55, -1); // 55.6
Math.round10(55.549, -1); // 55.5
Math.round10(55, 1); // 60
Math.round10(54.9, 1); // 50
Math.round10(-55.55, -1); // -55.5
Math.round10(-55.551, -1); // -55.6
Math.round10(-55, 1); // -50
Math.round10(-55.1, 1); // -60
Math.round10(1.005, -2); // 1.01 -- compare this with Math.round(1.005*100)/100 above
// Floor
Math.floor10(55.59, -1); // 55.5
Math.floor10(59, 1); // 50
Math.floor10(-55.51, -1); // -55.6
Math.floor10(-51, 1); // -60
// Ceil
Math.ceil10(55.51, -1); // 55.6
Math.ceil10(51, 1); // 60
Math.ceil10(-55.59, -1); // -55.5
Math.ceil10(-59, 1); // -50

มีคนใส่นี่ใน GitHub และ npm เช่นกัน: github.com/jhohlfeld/round10
Jo Liss

Nah Math.round10(3544.5249, -2)ส่งคืน 3544.52 แทน 3544.53
Matija

2
@Matija Wolfram alphaพูดว่า 3544.52 ด้วย คุณต้องการลดข้อผิดพลาดระหว่างหมายเลขปัจจุบันและการประมาณแบบมน ค่าที่ใกล้เคียงที่สุดของ3544.5249ทศนิยม 2 ตำแหน่งคือ3544.52(ข้อผิดพลาด = 0.0049) หากเป็น3544.53เช่นนั้นข้อผิดพลาดจะเป็น 0.0051 คุณกำลังทำการปัดเศษอย่างต่อเนื่องเช่น Math.round10 (Math.round10 (3544.5249, -3), -2) ซึ่งให้ข้อผิดพลาดในการปัดเศษที่มีขนาดใหญ่ขึ้นดังนั้นจึงไม่เป็นที่ต้องการ
ผู้ใช้

3
@Matija จากมุมมองทางคณิตศาสตร์ 3544.5249 ถูกปัดเศษเป็น 3544.52 ไม่ใช่ 3544.53 ดังนั้นรหัสนี้จึงถูกต้อง หากคุณต้องการให้โค้งมนไปยัง 3,544.53 ในเรื่องนี้และกรณีเช่นนี้ (แม้ยากที่ไม่ถูกต้อง) ทำอะไรเช่นนี้:number += 0.00011
Bozidar Sikanjic

@Matija: ฉันคิดว่าฟังก์ชั่นใช้งานได้ตามที่ควร บางทีคุณอาจต้องการย้ำการปัดเศษเช่น:Math.round10( Math.round10(3544.5249, -3) , -2)
jumxozizi

60

ไม่มีคำตอบที่พบที่นี่เป็นที่ถูกต้อง @stinkycheeseman ขอให้ปัดเศษขึ้นคุณทุกคนปัดเศษหมายเลข

ในการปัดเศษให้ใช้:

Math.ceil(num * 100)/100;

15
ตัวอย่างอินพุตและเอาต์พุตแสดงให้เห็นว่าแม้ว่าคำถามจะถามว่า 'ปัดเศษ ... ' แต่จริงๆแล้วมันตั้งใจจะเป็น 'ปัดเศษไปที่ ... '
JayDM

2
@stinkycheeseman กำลังชี้ให้เห็นข้อผิดพลาดในกรณีที่เฉพาะเจาะจงเขาไม่ต้องการที่จะวนเวียนอยู่เสมอเหมือน ceil ทำเขาแค่อยาก 0.005 เพื่อปัดเศษขึ้นถึง 0.01
mjaggard

9
พบข้อบกพร่องแปลกขณะที่การทดสอบMath.ceil(1.1 * 100)/100;ผลตอบแทน -IT 1.11เพราะ 1.1 * 100 110.00000000000001ตามแบรนด์เบราว์เซอร์ที่ทันสมัยใหม่ Firefox, Chrome, Safari และ Opera ... IE 1.1*100=1100ในแฟชั่นเก่ายังคงคิดว่า
skobaljic

1
@skobaljic tryMath.ceil(num.toFixed(4) * 100) / 100
treeface

1
@treeface Math.ceil((1.1).toFixed(4) * 100) / 100จะกลับมา1.11ใน Firefox ปัญหา / ข้อผิดพลาดของเบราว์เซอร์ที่ทันสมัยมีการคูณและผู้คนควรรู้ (ฉันทำงานกับเกมล็อตเตอรี่ในเวลานั้น)
skobaljic

47

นี่เป็นวิธีง่ายๆในการทำ:

Math.round(value * 100) / 100

คุณอาจต้องการดำเนินการต่อและสร้างฟังก์ชันแยกต่างหากเพื่อให้เหมาะกับคุณ

function roundToTwo(value) {
    return(Math.round(value * 100) / 100);
}

จากนั้นคุณก็จะผ่านค่า

คุณสามารถปรับปรุงให้ปัดเป็นจำนวนทศนิยมใด ๆ ก็ได้โดยเพิ่มพารามิเตอร์ตัวที่สอง

function myRound(value, places) {
    var multiplier = Math.pow(10, places);

    return (Math.round(value * multiplier) / multiplier);
}

2
วิธีการแก้ปัญหานี้ไม่ถูกต้องดูstackoverflow.com/questions/38322372/…หากคุณป้อน 156893.145 และปัดเศษด้วยฟังก์ชันด้านบนคุณจะได้รับ 156893.14 แทน 156893.15 !!!
saimiris_devel

2
@saimiris_devel คุณมีการจัดการเพื่อค้นหาอินสแตนซ์ที่แสดงตัวเลขของตัวเลขใน JavaScript ล้มเหลว คุณถูกต้องว่ามันถูกต้องรอบ แต่ - นั่นเป็นเพราะหมายเลขตัวอย่างของคุณเมื่อคูณด้วย 100 แตกแล้ว (15689314.499999998) จริงๆแล้วคำตอบที่มีคุณลักษณะครบถ้วนจะต้องมีห้องสมุดที่ได้รับการพัฒนาขึ้นเป็นพิเศษเพื่อคำนึงถึงความไม่สอดคล้องกันในการจัดการจำนวนจริงของ JavaScript มิฉะนั้นคุณอาจยกเลิกคำตอบใด ๆ ที่ให้ไว้กับคำถามนี้ได้
JayDM

36
+(10).toFixed(2); // = 10
+(10.12345).toFixed(2); // = 10.12

(10).toFixed(2); // = 10.00
(10.12345).toFixed(2); // = 10.12

1
การดำเนินการนี้จะไม่ให้ผลลัพธ์แบบเดียวกับที่คุณจะได้รับเสมอหากคุณใช้การแทนค่าสตริงของหมายเลขและปัดเศษ ตัวอย่างเช่น+(0.015).toFixed(2) == 0.01.
Mark Amery

35

สำหรับฉันMath.round ()ไม่ได้ให้คำตอบที่ถูกต้อง ฉันพบว่าการแก้ไข (2)ทำงานได้ดีขึ้น ด้านล่างเป็นตัวอย่างของทั้งสอง:

console.log(Math.round(43000 / 80000) * 100); // wrong answer

console.log(((43000 / 80000) * 100).toFixed(2)); // correct answer


สิ่งสำคัญที่ควรทราบว่า toFixed ไม่ทำการปัดเศษและ Math.round จะปัดเศษเป็นจำนวนเต็มที่ใกล้ที่สุด เพื่อรักษาทศนิยมเราจึงจำเป็นต้องคูณจำนวนเดิมด้วยจำนวนพลังของสิบซึ่งศูนย์ที่เป็นตัวแทนของจำนวนทศนิยมที่คุณต้องการและจากนั้นหารผลลัพธ์ด้วยหมายเลขเดียวกัน ในกรณีของคุณ: Math.round (43000/80000 * 100 * 100) / 100 ในที่สุดคงที่ (2) อาจถูกนำมาใช้เพื่อให้แน่ใจว่ามีทศนิยมสองตำแหน่งเสมอในผลลัพธ์ (มีศูนย์ต่อท้ายเมื่อจำเป็น) - สมบูรณ์แบบ สำหรับการจัดตำแหน่งตัวเลขให้อยู่ในแนวตั้ง :)
เทอร์โบ

7
สิ่งสำคัญที่ต้องทราบว่า. toFIxed () จะส่งออกสตริงไม่ใช่ตัวเลข
carpiediem

2
1.005ยังคงนี้ไม่ได้แก้ปัญหาการปัดเศษสำหรับ ยังส่งผลให้เกิดการ(1.005).toFixed(2) 1.00
DPac

34

ใช้ฟังก์ชั่นนี้ Number(x).toFixed(2);


8
ตัดทั้งหมดNumberอีกครั้งหากคุณไม่ต้องการให้ส่งคืนเป็นสตริง:Number(Number(x).toFixed(2));

5
การNumberโทรไม่จำเป็นต้องใช้x.toFixed(2)งานได้
bgusach

3
@bgusach ต้องการการเรียกใช้หมายเลขเนื่องจากคำสั่ง x.toFixed (2) ส่งคืนสตริงและไม่ใช่ตัวเลข ในการแปลงอีกครั้งเป็นจำนวนเราต้องตัดด้วย Number
Mohan Ram

2
เมื่อใช้วิธีนี้(1).toFixed(2)จะคืนค่า1.00แต่ผู้ถามจำเป็น1ในกรณีนี้
Eugene Mala

1
นี้ไม่ได้ทำงาน, 1.005.toFixed(2)อัตราผลตอบแทนเมื่อมันควรจะเป็น"1" "1.01"
Adam Jagosz

33

2017
เพียงแค่ใช้รหัสพื้นเมือง.toFixed()

number = 1.2345;
number.toFixed(2) // "1.23"

หากคุณจำเป็นต้องเข้มงวดและเพิ่มตัวเลขหากจำเป็นก็สามารถใช้ replace

number = 1; // "1"
number.toFixed(5).replace(/\.?0*$/g,'');

3
เมธอด toFixed ส่งคืนสตริง หากคุณต้องการผลลัพธ์ตัวเลขคุณจะต้องส่งผลลัพธ์ของไปยังแก้ไขเพื่อแยกวิเคราะห์
Zambonilli

@ Zambonilli หรือเพียงแค่คูณด้วย 1 ถ้าจำเป็น แต่เนื่องจากจำนวนคงที่กรณีส่วนใหญ่สำหรับการแสดงผลและไม่ได้สำหรับการคำนวณสตริงเป็นรูปแบบที่เหมาะสม
Pery Mimon

2
-1; ไม่เพียง แต่ได้toFixedรับคำแนะนำจากหลายคำตอบเมื่อหลายปีก่อนหน้าคุณ แต่ไม่สามารถตอบสนองเงื่อนไข "เฉพาะในกรณีที่จำเป็น" ในคำถาม (1).toFixed(2)ให้"1.00"ที่ผู้"1"ถามต้องการ
Mark Amery

เข้าใจแล้ว ฉันเพิ่มวิธีแก้ปัญหาสำหรับกรณีนั้นด้วย
pery mimon

หากคุณกำลังใช้ lodash มันจะยิ่งง่ายขึ้น: _.round (ตัวเลข, ตำแหน่งทศนิยม) ลบความคิดเห็นล่าสุดของฉันเพราะมันมีปัญหา Lodash _.round ใช้งานได้แม้ว่า 1.005 ที่มีทศนิยม 2 ตำแหน่งแปลงเป็น 1.01
Devin Fields

32

ลองวิธีแก้ปัญหาน้ำหนักเบานี้:

function round(x, digits){
  return parseFloat(x.toFixed(digits))
}

 round(1.222,  2) ;
 // 1.22
 round(1.222, 10) ;
 // 1.222

ใครรู้ว่ามีความแตกต่างระหว่างนี้และreturn Number(x.toFixed(digits))?

1
@ JoeRocc ... ไม่ควรสร้างความแตกต่างเท่าที่ฉันเห็นเพราะ.toFixed()อนุญาตเฉพาะตัวเลขเท่านั้น
petermeissner

4
คำตอบนี้มีปัญหาเช่นเดียวกับที่กล่าวถึงหลายครั้งในหน้านี้ ลองround(1.005, 2)และดูผลของการแทน1 1.01
MilConDoin

ดูเหมือนว่าปัญหาของการปัดเศษอัลโก? - มีมากกว่าหนึ่งจินตนาการ: en.wikipedia.org/wiki/Rounding ... round(0.995, 2) => 0.99; round(1.006, 2) => 1.01; round(1.005, 2) => 1
petermeissner

31

มีสองวิธีในการทำเช่นนั้น สำหรับคนอย่างฉันตัวแปรของ Lodash

function round(number, precision) {
    var pair = (number + 'e').split('e')
    var value = Math.round(pair[0] + 'e' + (+pair[1] + precision))
    pair = (value + 'e').split('e')
    return +(pair[0] + 'e' + (+pair[1] - precision))
}

การใช้งาน:

round(0.015, 2) // 0.02
round(1.005, 2) // 1.01

หากโครงการของคุณใช้ jQuery หรือ lodash คุณสามารถค้นหาroundวิธีการที่เหมาะสมในไลบรารี

อัปเดต 1

ฉันลบตัวแปรออกn.toFixed(2)เพราะไม่ถูกต้อง ขอบคุณ @ avalanche1


ตัวเลือกที่สองจะคืนค่าสตริงที่มีทศนิยมสองตำแหน่ง คำถามจะถามถึงจุดทศนิยมถ้าจำเป็น ตัวเลือกแรกดีกว่าในกรณีนี้
Marcos Lima

@MarcosLima Number.toFixed()จะส่งคืนสตริง แต่มีสัญลักษณ์เครื่องหมายบวกอยู่ข้างหน้าตัวแปล JS จะแปลงสตริงเป็นตัวเลข นี่คือซินแท็กซ์น้ำตาล
stanleyxu2005

บน Firefox alert((+1234).toFixed(2))แสดง "1234.00"
Marcos Lima

บน Firefox, พ่นalert(+1234.toFixed(2)) SyntaxError: identifier starts immediately after numeric literalฉันติดกับตัวเลือกที่ 1
Marcos Lima

นี้ไม่ได้ทำงานในกรณีบางขอบ: ลอง ( jsfiddle ) 362.42499999999995ด้วย ผลที่คาดหวัง (ในขณะที่ PHP ):echo round(362.42499999999995, 2) 362.43ผลลัพธ์ที่แท้จริง:362.42
Dr. Gianluigi Zane Zanettini

26

หากคุณกำลังใช้ห้องสมุด lodash คุณสามารถใช้วิธีการรอบของ lodash ดังต่อไปนี้

_.round(number, precision)

เช่น:

_.round(1.7777777, 2) = 1.78

@Peter ชุดของฟังก์ชันที่ Lodash ให้นั้นดีมากเมื่อเทียบกับ Javascript มาตรฐาน อย่างไรก็ตามฉันได้ยินมาว่า Lodash มีปัญหาด้านประสิทธิภาพบางอย่างเมื่อเทียบกับ JS มาตรฐาน codeburst.io/…
Madura Pradeep

1
ฉันยอมรับจุดของคุณว่ามีข้อบกพร่องในการปฏิบัติงานกับการใช้ lodash ฉันคิดว่าปัญหาเหล่านี้เป็นเรื่องธรรมดาของนามธรรมหลายประการ แต่เพียงดูจำนวนคำตอบที่มีอยู่ในเธรดนี้และวิธีการแก้ปัญหาที่ใช้งานง่ายล้มเหลวในกรณีขอบ เราได้เห็นรูปแบบนี้ด้วย jQuery และปัญหารากได้รับการแก้ไขเมื่อเบราว์เซอร์ใช้มาตรฐานทั่วไปที่แก้ไขกรณีการใช้งานส่วนใหญ่ของเรา คอขวดของประสิทธิภาพถูกย้ายไปยังเอนจิ้นเบราว์เซอร์แล้ว ฉันคิดว่าสิ่งเดียวกันควรเกิดขึ้นกับ lodash :)
ปีเตอร์

26

เนื่องจาก ES6 มีวิธีที่ 'ถูกต้อง' (โดยไม่มีการแทนที่สถิตยศาสตร์และการสร้างวิธีแก้ไขปัญหา) เพื่อทำสิ่งนี้โดยใช้ toPrecision

var x = 1.49999999999;
console.log(x.toPrecision(4));
console.log(x.toPrecision(3));
console.log(x.toPrecision(2));

var y = Math.PI;
console.log(y.toPrecision(6));
console.log(y.toPrecision(5));
console.log(y.toPrecision(4));

var z = 222.987654
console.log(z.toPrecision(6));
console.log(z.toPrecision(5));
console.log(z.toPrecision(4));

จากนั้นคุณสามารถทำได้parseFloatและเลขศูนย์จะ 'หายไป'

console.log(parseFloat((1.4999).toPrecision(3)));
console.log(parseFloat((1.005).toPrecision(3)));
console.log(parseFloat((1.0051).toPrecision(3)));

มันไม่ได้แก้ '1.005 ปัดเศษปัญหา' แม้ว่า - เพราะมันเป็นธรรมวิธีเศษส่วนลอยมีการประมวลผล

console.log(1.005 - 0.005);

หากคุณเปิดห้องสมุดคุณสามารถใช้bignumber.js

console.log(1.005 - 0.005);
console.log(new BigNumber(1.005).minus(0.005));

console.log(new BigNumber(1.005).round(4));
console.log(new BigNumber(1.005).round(3));
console.log(new BigNumber(1.005).round(2));
console.log(new BigNumber(1.005).round(1));
<script src="https://cdnjs.cloudflare.com/ajax/libs/bignumber.js/2.3.0/bignumber.min.js"></script>


3
(1.005).toPrecision(3)ยังคงส่งคืน1.00แทนที่จะเป็น1.01จริง
Giacomo

toPrecisionส่งคืนสตริงซึ่งเปลี่ยนประเภทเอาต์พุตที่ต้องการ
adamduren

@Giacomo มันไม่ได้เป็นข้อบกพร่องของ.toPrecisionวิธีการที่มันเป็นความจำเพาะของจำนวนจุดลอยตัว (ซึ่งตัวเลขใน JS มี) - ลองก็จะกลับ1.005 - 0.005 0.9999999999999999
shau-kote

1
(1).toPrecision(3)ส่งคืน '1.00' แต่ผู้ถามต้องการมี1กรณีนี้
Eugene Mala

1
ดังที่ @Giacomo กล่าวว่าคำตอบนี้ดูเหมือนจะทำให้เกิดความสับสน "ตัวเลขนัยสำคัญ" ด้วย "ปัดเศษเป็นจำนวนทศนิยม" toPrecisionทำรูปแบบไม่ใช่อย่างหลังและไม่ใช่คำตอบสำหรับคำถามของ OP ถึงแม้ว่าในตอนแรกมันอาจจะเกี่ยวข้องกัน แต่มันก็ผิดมาก ดูen.wikipedia.org/wiki/Significant_figures ตัวอย่างเช่นNumber(123.4).toPrecision(2)ผลตอบแทน"1.2e+2"และผลตอบแทนNumber(12.345).toPrecision(2) "12"ฉันยังเห็นด้วยกับประเด็นของ @ adamduren ว่าจะส่งคืนสตริงที่ไม่เป็นที่ต้องการ (ไม่ใช่ปัญหาใหญ่ แต่ไม่เป็นที่ต้องการ)
Neek

23

MarkG และ Lavamantis เสนอทางออกที่ดีกว่าที่ได้รับการยอมรับ มันเป็นความอัปยศที่พวกเขาไม่ได้รับผู้โหวตมากขึ้น!

นี่คือการใช้งานฟังก์ชั่นผมในการแก้ปัญหาจุดลอยทศนิยมนอกจากนี้ยังขึ้นอยู่กับ MDN มันเป็นเรื่องธรรมดามากขึ้น (แต่กระชับน้อยกว่า) กว่าทางออกของ Lavamantis:

function round(value, exp) {
  if (typeof exp === 'undefined' || +exp === 0)
    return Math.round(value);

  value = +value;
  exp  = +exp;

  if (isNaN(value) || !(typeof exp === 'number' && exp % 1 === 0))
    return NaN;

  // Shift
  value = value.toString().split('e');
  value = Math.round(+(value[0] + 'e' + (value[1] ? (+value[1] + exp) : exp)));

  // Shift back
  value = value.toString().split('e');
  return +(value[0] + 'e' + (value[1] ? (+value[1] - exp) : -exp));
}

ใช้กับ:

round(10.8034, 2);      // Returns 10.8
round(1.275, 2);        // Returns 1.28
round(1.27499, 2);      // Returns 1.27
round(1.2345678e+2, 2); // Returns 123.46

เมื่อเปรียบเทียบกับโซลูชันของ Lavamantis เราสามารถทำ ...

round(1234.5678, -2); // Returns 1200
round("123.45");      // Returns 123

2
โซลูชันของคุณไม่ครอบคลุมบางกรณีซึ่งตรงข้ามกับโซลูชันของ MDN แม้ว่ามันอาจจะสั้นกว่า แต่ก็ไม่ถูกต้อง ...
astorije

1
round (-1835.665,2) => -1835.66
Jorge Sampayo

21

สิ่งนี้อาจช่วยคุณได้:

var result = Math.round(input*100)/100;

สำหรับข้อมูลเพิ่มเติมคุณสามารถดูลิงค์นี้

Math.round (NUM) vs num.toFixed (0) และเบราว์เซอร์ไม่สอดคล้องกัน


1
ทำไมคำตอบที่ได้รับการยอมรับในโลกจึงมีคะแนนมากกว่านี้มากเพราะมันเหมือนกัน แต่จริง ๆ แล้วคำถามนี้ถูกโพสต์ใน 1 นาทีหลังจากคำตอบที่ยอมรับ?
อ้างอิง Dave

18

วิธีที่ง่ายที่สุดคือการใช้ toFixed แล้วตัดค่าศูนย์ต่อท้ายโดยใช้ฟังก์ชัน Number:

const number = 15.5;
Number(number.toFixed(2)); // 15.5
const number = 1.7777777;
Number(number.toFixed(2)); // 1.78

สิ่งนี้ไม่สามารถใช้ได้กับทุกกรณี ทำแบบทดสอบก่อนโพสต์คำตอบ
baburao

@baburao โปรดโพสต์เคสที่โซลูชันด้านบนใช้งานไม่ได้
Marcin Wanago

จำนวน const = 15; จำนวน (number.toFixed (2)); //15.00 แทน 15
Kevin Jhangiani

1
@KevinJhangiani จำนวน const = 15; จำนวน (number.toFixed (2)); // 15 - ฉันทดสอบทั้งบน Chrome และ Firefox ใหม่ล่าสุด
Marcin Wanago

@KevinJhangiani คุณจะได้รับ15.00อย่างไร? ตัวเลขใน JS จะไม่เก็บตำแหน่งทศนิยมและการแสดงผลใด ๆ จะตัดทอนทศนิยมส่วนเกินโดยอัตโนมัติ (เลขศูนย์ใด ๆ ในตอนท้าย)
VLAZ

16
var roundUpto = function(number, upto){
    return Number(number.toFixed(upto));
}
roundUpto(0.1464676, 2);

toFixed(2) ที่นี่ 2 คือจำนวนหลักที่เราต้องการปัดเศษตัวเลขนี้


.toFixed () นี้ง่ายต่อการใช้ เพียงแค่ผ่านมันไปครั้งเดียว
Ritesh Dhuri

16

มันอาจทำงานให้คุณ

Math.round(num * 100)/100;

เพื่อทราบความแตกต่างระหว่าง toFixed และ round คุณสามารถมีลักษณะที่Math.Round (NUM) VS num.toFixed (0) และไม่สอดคล้องกันเบราว์เซอร์


14

วิธีที่ง่ายที่สุด:

+num.toFixed(2)

มันแปลงเป็นสตริงแล้วกลับไปเป็นจำนวนเต็ม / ลอย


ขอบคุณสำหรับคำตอบที่ง่ายที่สุดนี้ อย่างไรก็ตาม '+' ใน + NUM คืออะไร มันไม่ทำงานสำหรับฉันที่มีค่าทศนิยมมาเป็นสตริง ฉันทำ: (NUM * 1) .toFixed (2)
Ethan

@momo เพียงเปลี่ยนอาร์กิวเมนต์toFixed()เป็น 3 ดังนั้นมันจะเป็น+num.toFixed(3)เช่นนั้น มันใช้งานได้อย่างที่ควรจะเป็น 1.005 จะถูกปัดเศษเป็น 1.00 ซึ่งเท่ากับ 1
bigpotato

1
@Edmund มันควรจะส่งคืน 1.01 ไม่ใช่ 1.00
mmm

13

นี่คือวิธีต้นแบบ:

Number.prototype.round = function(places){
    places = Math.pow(10, places); 
    return Math.round(this * places)/places;
}

var yournum = 10.55555;
yournum = yournum.round(2);

13

ใช้สิ่งนี้ "parseFloat (parseFloat (ค่า) .toFixed (2))"

parseFloat(parseFloat("1.7777777").toFixed(2))-->1.78 
parseFloat(parseFloat("10").toFixed(2))-->10 
parseFloat(parseFloat("9.1").toFixed(2))-->9.1

1
ไม่ใช่หากความไม่ถูกต้องเป็นจริงกับการเป็นตัวแทนลอย คุณเพียงแค่จะลบมันแล้วแนะนำข้อผิดพลาดเดียวกันอีกครั้งโดยการแปลงกลับไปลอยอีกครั้ง!
Ben McIntyre

12

วิธีหนึ่งในการบรรลุถึงการปัดเศษเฉพาะในกรณีที่จำเป็นต้องใช้Number.prototype.toLocaleString () :

myNumber.toLocaleString('en', {maximumFractionDigits:2, useGrouping:false})

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


นี่เป็นวิธีการแก้ปัญหาที่สะอาดที่สุดเท่าที่จะทำได้และหลีกเลี่ยงปัญหาจุดลอยตัวที่ซับซ้อนทั้งหมด แต่การสนับสนุนMDNยังไม่สมบูรณ์ - Safari ยังไม่รองรับการส่งผ่านข้อโต้แย้งไปtoLocaleStringยัง
Mark Amery

@MarkAmery ในตอนนี้เฉพาะเบราว์เซอร์ Android เท่านั้นที่มีปัญหา: caniuse.com/#search=toLocaleString
ptyskju

12

หลังจากดำเนินการซ้ำหลายวิธีที่เป็นไปได้ทั้งหมดเพื่อให้ได้ความแม่นยำในการปัดเศษทศนิยมที่แม่นยำจริง ๆ เป็นที่ชัดเจนว่าโซลูชันที่แม่นยำและมีประสิทธิภาพมากที่สุดคือการใช้ Number.PSPSON นี่เป็นวิธีแก้ปัญหาทางคณิตศาสตร์ที่แท้จริงสำหรับปัญหาความแม่นยำทางคณิตศาสตร์ของจุดลอยตัว มันสามารถโพลีฟิลได้อย่างง่ายดายดังแสดงที่นี่: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/EPSILONเพื่อสนับสนุนผู้ใช้ IE ที่เหลืออยู่ทั้งหมดอีกครั้ง ควรหยุดทำเช่นนั้น)

ดัดแปลงจากโซลูชันที่มีให้ที่นี่: https://stackoverflow.com/a/48850944/6910392

วิธีการแก้ปัญหาง่ายๆที่ให้การปัดเศษทศนิยมการปูพื้นและเพดานที่แม่นยำพร้อมตัวแปรความแม่นยำเสริมโดยไม่ต้องเพิ่มทั้งไลบรารี

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

var DecimalPrecision = (function(){
        if (Number.EPSILON === undefined) {
            Number.EPSILON = Math.pow(2, -52);
        }
        this.round = function(n, p=2){
            let r = 0.5 * Number.EPSILON * n;
            let o = 1; while(p-- > 0) o *= 10;
            if(n < 0)
                o *= -1;
            return Math.round((n + r) * o) / o;
        }
        this.ceil = function(n, p=2){
            let r = 0.5 * Number.EPSILON * n;
            let o = 1; while(p-- > 0) o *= 10;
            if(n < 0)
                o *= -1;
            return Math.ceil((n + r) * o) / o;
        }
        this.floor = function(n, p=2){
            let r = 0.5 * Number.EPSILON * n;
            let o = 1; while(p-- > 0) o *= 10;
            if(n < 0)
                o *= -1;
            return Math.floor((n + r) * o) / o;
        }
        return this;
    })();
    console.log(DecimalPrecision.round(1.005));
    console.log(DecimalPrecision.ceil(1.005));
    console.log(DecimalPrecision.floor(1.005));
    console.log(DecimalPrecision.round(1.0049999));
    console.log(DecimalPrecision.ceil(1.0049999));
    console.log(DecimalPrecision.floor(1.0049999));
    console.log(DecimalPrecision.round(2.175495134384,7));
    console.log(DecimalPrecision.round(2.1753543549,8));
    console.log(DecimalPrecision.round(2.1755465135353,4));


1
(DecimalPrecision.round (0.014999999999999999, 2)) // ส่งคืน 0.02
Sergey

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

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

11

นี่คือทางออกที่ง่ายที่สุดและสง่างามกว่า (และฉันดีที่สุดในโลก;):

function roundToX(num, X) {    
    return +(Math.round(num + "e+"+X)  + "e-"+X);
}
//roundToX(66.66666666,2) => 66.67
//roundToX(10,2) => 10
//roundToX(10.904,2) => 10.9

4
นั่นเป็นวิธีที่ดีในการเขียนคำตอบที่ยอมรับเพื่อยอมรับอาร์กิวเมนต์โดยใช้Eสัญลักษณ์
AxelH

1
นี้ไม่ได้ทำงานในกรณีบางขอบ: ลอง ( jsfiddleroundToX(362.42499999999995, 2) ) ผลที่คาดหวัง (ในขณะที่ PHP ):echo round(362.42499999999995, 2) 362.43ผลลัพธ์ที่แท้จริง:362.42
Dr. Gianluigi Zane Zanettini

6
IMHO ผลลัพธ์ PHP ของคุณผิด ไม่ว่าจะเกิดอะไรขึ้นหลังจากทศนิยมที่สามถ้าทศนิยมที่สามต่ำกว่า 5 ก็จะทำให้ทศนิยมที่สองนั้นยังคงเหมือนเดิม นั่นคือนิยามทางคณิตศาสตร์
Soldeplata Saketos

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