start up engine, please wait ...
- -diff --git a/ChangeLog b/ChangeLog index d1d20fb..57aeefa 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,16 @@ +2016-01-08 11:40 marc + + * ChangeLog, cordova/platforms/android/AndroidManifest.xml, + cordova/platforms/android/assets/www/cordova_plugins.js, + cordova/platforms/android/res/xml/config.xml, html/login.php, + html/opendb.php, html/safechat.js, html/send.php, + test/makefile.am, test/runtests.sh, test/settings.wt: non working + experimental status + +2015-12-18 16:07 marc + + * build-in-docker.conf, html/schema.sql: fix build in docker + 2015-12-04 08:36 marc * COPYING, ChangeLog, INSTALL, build-in-docker.conf, diff --git a/build-in-docker.sh b/build-in-docker.sh index f63e0e9..4fc1652 100755 --- a/build-in-docker.sh +++ b/build-in-docker.sh @@ -113,7 +113,6 @@ function traperror() { fi echo fi - echo "**** Entering docker container ${DOCKER_ID}, exit with Ctrl-D" echo -n " ... cleanup docker: " docker rm -f "${DOCKER_ID}" echo "returning status: $e" @@ -163,7 +162,7 @@ for repo in "${repos[@]}"; do ifthenelse "${repo}" "apt-add-repository ARG" done for key in "${keys[@]}"; do - wget -O- \ + wget -O- "$key" \ | docker exec -i ${DOCKER_ID} apt-key add - done docker exec ${DOCKER_ID} apt-get update diff --git a/configure.ac b/configure.ac index 6663916..8262d1b 100644 --- a/configure.ac +++ b/configure.ac @@ -8,7 +8,8 @@ m4_define(x_package_name, safechat) # project's name m4_define(x_major, 0) # project's major version -m4_define(x_minor, 3) # project's minor version +m4_define(x_minor, 5) # project's minor version +m4_define(x_least_diff, 63) m4_include(ax_init_standard_project.m4) AC_INIT(x_package_name, x_version, x_package_name) AM_INIT_AUTOMAKE([1.9 tar-pax]) @@ -20,7 +21,7 @@ AX_INIT_STANDARD_PROJECT AX_USE_SCRIPTS AX_USE_DOXYGEN AX_USE_DEBIAN_PACKAGING -AX_BUILD_HTML +#AX_BUILD_HTML AX_USE_RPM_PACKAGING #AX_USE_CPPUNIT AX_BUILD_TEST @@ -43,7 +44,8 @@ fi AM_CONDITIONAL(HAVE_CORDOVA, [test ${CORDOVA} != 0 -a ${ANDROID} != 0]) AX_SUBST(CORDOVA) -AC_CONFIG_FILES([html/index.html]) +AC_CONFIG_FILES([nodejs/package.json]) +AC_CONFIG_FILES([nodejs/makefile]) AC_CONFIG_FILES([cordova/makefile]) AC_CONFIG_FILES([cordova/config.xml]) diff --git a/cordova/makefile.am b/cordova/makefile.am index f0c6c23..82d1b3f 100644 --- a/cordova/makefile.am +++ b/cordova/makefile.am @@ -20,7 +20,7 @@ ${ANDROID_SRC}: chmod -R u+w "$${file}" ); \ done if [ "$$(whoami 2> /dev/null)" != "root" ]; then \ - ${CORDOVA} plugin add https://github.com/katzer/cordova-plugin-background-mode.git; \ + #${CORDOVA} plugin add https://github.com/katzer/cordova-plugin-background-mode.git; \ ${CORDOVA} build --debug || ${CORDOVA} build --debug; \ fi diff --git a/doc/doxyfile.in b/doc/doxyfile.in index e2a2e88..d1a800f 100644 --- a/doc/doxyfile.in +++ b/doc/doxyfile.in @@ -748,7 +748,7 @@ WARN_LOGFILE = doxygen.errors # spaces. # Note: If this tag is empty the current directory is searched. -INPUT = @top_srcdir@/html +INPUT = @top_srcdir@/nodejs INPUT += @top_srcdir@/scripts INPUT += @top_srcdir@/test diff --git a/html/chat.html b/html/chat.html deleted file mode 100644 index ae23cbc..0000000 --- a/html/chat.html +++ /dev/null @@ -1,40 +0,0 @@ -
start up engine, please wait ...
- -All you need to start is a username and a password:
- -Please chose any username, e.g. a pseudonym, your e-mail, your phone number, your real name, and chose a safe password.
-Safe Chat is a chat program to protect your privacy. It is designed to be extremely easy to use, with all cool features, but with highest security through strong encryption. For more information, open «About Safe Chat» in the menu () above.
This messenger is absolutely secure. But on the other hand, that means, no one except you knows your password. No one can read your messages exept you, not even our administrator. It is technically impossible to restore a password. You would have to delete your account and create a new one. In that case, all messages are lost.
-Safe Chat internally uses OpenPGP for public/private-key encryption. Your password is not transfered to the server and not stored, it is used only to create and encrypt your private key. There are two keys, a secret private key, that is stored in the browser (or app) on your computer, encrypted with your password and not sent to the server. There is also public key, which is sent to the server and used by other users to encrypt messages that only you can read. You can backup and restore your keys and setings to and from a file. This is the only way to transfer your account to another browser or to computer. Do not delete your browser's local storage unless you have backed up your keys.
diff --git a/html/opendb.php b/html/opendb.php deleted file mode 100644 index e516dfb..0000000 --- a/html/opendb.php +++ /dev/null @@ -1,42 +0,0 @@ -query("create database if not exists safechat;"); - if (!$db) error("cannot create database"); - $db->select_db("safechat"); - if (!$db) error("cannot select database"); - $query = file_get_contents("schema.sql"); - if (!$query) error("cannot load database schema"); - foreach (split(";\n", $query) as $create) { - if ($create) { - $db->query($create); - if (!$db) error("cannot create database tables"); - } - } -} catch (Exception $e) { - error('database error on server: '+$e->getMessage()); -} -?> diff --git a/html/photo.png b/html/photo.png deleted file mode 100644 index baa033d..0000000 Binary files a/html/photo.png and /dev/null differ diff --git a/html/pubkey.php b/html/pubkey.php deleted file mode 100644 index f1a2b77..0000000 --- a/html/pubkey.php +++ /dev/null @@ -1,44 +0,0 @@ -real_escape_string($user); - $q = $db->query("select pubkey from user where name='$user';"); - /* if ($q->num_rows!=1 && $user=="safechat") { */ - /* require_once("optionstable.php"); */ - /* createSafechatUser(); */ - /* $q = $db->query("select pubkey from user where name='$user';"); */ - /* } */ - if ($q->num_rows==1) { - echo json_encode($q->fetch_row()[0]); - } else { - echo json_encode(null); - } - } catch (Exception $e) { - echo json_encode(null); - } -} -pubkey($_REQUEST['user']); -?> diff --git a/html/safechat.js b/html/safechat.js deleted file mode 100644 index 9d89bb5..0000000 --- a/html/safechat.js +++ /dev/null @@ -1,722 +0,0 @@ -/*! @file - - @id $Id$ - - This is the main application as it is fully run in the user's browser. - - @dot - digraph X { - - start [URL="\ref start()"]; - newuser [URL="\ref newuser()"]; - login [URL="\ref login()"]; - createkeypair [URL="\ref createkeypair()"]; - chat [URL="\ref chat()"]; - getpwd [URL="\ref getpwd()"]; - setpw [URL="\ref setpw()"]; - get [URL="\ref get()"]; - sendmessage [URL="\ref sendmessage()"]; - - start -> newuser [label="if no keys exist"]; - start -> login [label="if keys exist"]; - newuser -> createkeypair [label="on submit"]; - createkeypair -> "openpgp.generateKeyPair"; - "openpgp.generateKeyPair" -> login [label="keys generated in local store"]; - login -> chat [label="user is valid on server"]; - chat -> getpwd [label="password not yet entered"]; - getpwd -> setpw [label="on input"]; - setpw -> chat [label="password is valid"]; - chat -> chat [label="remain in chat"]; - chat -> get [label="start timer"]; - get -> get [label="restart timer"]; - chat -> sendmessage [label="on submit"]; - sendmessage -> chat [label="remain in chat"]; - } - @enddot -*/ -// 1 2 3 4 5 6 7 8 -// 45678901234567890123456789012345678901234567890123456789012345678901234567890 - -var password = null; ///< password, only stored temporary, until reload -var username = null; ///< username, only used during registration -var filecontent = new Array(); ///< temporary storage for attachments - -/// Show error messsage -/** Fades in an error message and logs to console. - @param data (optional) The error can be a string or any structure. - Strings are shown to the user, structures are logged only. - @param stay (optional) If not given as @c true, reloads page after 5s. */ -function error(data, stay) { - $("#status").fadeOut("slow", function() { - $("#status").addClass("error") - $("#status").removeClass("notice") - $("#status").removeClass("success") - if (data) { - if (typeof data == 'string') { - $("#status").html(data); - console.log("error: "+data); - } else { - $("#status").html('unknown error: '+JSON.stringify(data)); - console.log("error: "+JSON.stringify(data)); - } - } else { - $("#status").html('error'); - console.log("error"); - } - $("#status").fadeIn("slow"); - if (!stay) setTimeout(start, 5000); - }); -} - -/// Show notice messsage -/** Fades in an notice message and logs to console. - @param text (optional) The data is a string. */ -function notice(text) { - $("#status").fadeOut("slow", function() { - $("#status").addClass("notice") - $("#status").removeClass("error") - $("#status").removeClass("success") - if (text) { - $("#status").html(text); - console.log("notice: "+text); - } else { - $("#status").html(''); - console.log("notice"); - } - $("#status").fadeIn("slow"); - }); -} - -/// Show notice messsage -/** Fades in an success message and logs to console. - @param text (optional) The data is a string. */ -function success(text) { - $("#status").fadeOut("slow", function() { - $("#status").addClass("success") - $("#status").removeClass("error") - $("#status").removeClass("notice") - if (text) { - $("#status").html(text); - console.log("success: "+text); - } else { - $("#status").html(''); - console.log("success"); - } - $("#status").fadeIn("slow"); - }); -} - -/// Show status message in the main screen area -/** @param text Text is a message or some complex HTML from the server. - @param msg The success message text */ -function status(text, msg) { - $("#main").fadeOut("slow", function() { - $("#main").html(text); - if (msg) success(msg); - else setTimeout("$('#status').fadeOut('slow')", 5000); - $("#main").fadeIn("slow", function() { - $("form input:first-child").focus(); - }) - }); -} - -var getLoopTimeout = null; ///< store get timeout to make sure only one is running -/// Set timeout for next get request -/** @param time timeout time in ms, defaults to 10000 */ -function getLoop(time) { - if (!time) time = 10000; - getLoopStop(); - getLoopTimeout = setTimeout(get, time); -} -/// Stop get loop if it is running -function getLoopStop() { - if (getLoopTimeout) clearTimeout(getLoopTimeout); - getLoopTimeout = null; -} - -/// Alert user -/** Alert user, e.g. that a new message has arrived. */ -function alert() { - navigator.vibrate = - navigator.vibrate || navigator.webkitVibrate || navigator.mozVibrate || navigator.msVibrate; - if (navigator.vibrate) { - // vibration API supported - navigator.vibrate(1000); - } - (new Audio("A-Tone-His_Self-1266414414.mp3")).play(); -} - -/// Toggle Menu Display -function togglemenu() { - $("#menu").toggle(); -} - -/// Download Profile Backup -function backup() { - getLoopStop(); - status("Starting backup download ...
", ""); - var download = document.createElement('a'); - download.href = 'data:attachment/text,'+encodeURI(JSON.stringify(localStorage)); - download.target = '_blank'; - function pad(n) {return n<10 ? '0'+n : n} - var now = new Date(); - download.download = - pad(now.getFullYear())+pad(now.getMonth()+1)+pad(now.getDate())+ - "-"+userid()+"@"+window.location.hostname+".bak"; - var clickEvent = new MouseEvent("click", { - "view": window, - "bubbles": true, - "cancelable": false - }); - download.dispatchEvent(clickEvent); - togglemenu(); - setTimeout(start, 2000); -} - -/// Upload Profile Backup -function restore(evt) { - getLoopStop(); - status("Starting backup restore ...
", ""); - if (!window.FileReader) - return error("your browser dows not support file upload", true); - for (var i=0, f; f=evt.target.files[i]; ++i) { - var file = f; - var reader = new FileReader(); - reader.onload = function(evt) { - if (evt.target.error) return error("error reading file", true); - if (evt.target.readyState==0) return notice("waiting for data ..."); - if (evt.target.readyState==1) return notice("loading data ..."); - var parsed=JSON.parse(evt.target.result); - togglemenu(); - localStorage.pubKey = parsed.pubKey; - localStorage.privKey = parsed.privKey; - setTimeout(start, 2000); - } - reader.readAsText(file); - } -} - -/// Configure local groups -/** ... */ -function groups() { -} - -/// Check if user name is available -/** Calls checknewuser.php on server and displays an error, if the - user name is already in use. This function is used when creating a - new user. It immediately gives the user a feedback, whether the - chosen user name is available or not. - - Called when user edits the user name fields. - - Sets @ref username and checks @ref password - if both are well - defined, enables the submit button. - - @param user User name to check. */ -function checkuser(user) { - $("#register").submit(function(event) { - return false; - }); - $.post("checknewuser.php", {user: user}) - .done(function(res) { - username=JSON.parse(res); - if (!username||username.length<1) username=null; - $("#createuser").prop("disabled", !(username && password)); - if (username) { - if (password) success("user is ready to be created"); - else notice("user name is available, please set password"); - } else notice("user name is not available"); - }).fail(function(res) { - username=null; - $("#createuser").prop("disabled", !(username && password)); - error("offline"); - }); -} - -/// Check if password is set and matches the repeated password -/** Checks if both passwords are identical and valid and gives - feedback to the user. - - Called when user edits the password fields. - - Sets @ref username and checks @ref password - if both are well - defined, enables the submit button. - - @param pwd The password. - @param pwd2 The repeated password. */ -function checkpwd(pwd, pwd2) { - $("#register").submit(function(event) { - return false; - }); - if (pwd==pwd2) password=pwd; - else password=null; - if (!password||password.length<1) password=null; - $("#createuser").prop("disabled", !(username && password)); - if (password) { - if (username) success("user is ready to be created"); - else notice("password matches, please chose a valid user name"); - } else notice("passwords don't match"); -} - -/// Checks if the receiver of a message exists on server. -/** Calls checknewuser.php on server and enables the message submit - button if the receiver of the message exists on the server. */ -function checkpartner(user) { - $("#chat").submit(function(event) { - return false; - }); - $.post("checknewuser.php", {user: user}) - .done(function(res) { - if (JSON.parse(res)) { - notice("receiver does not exist"); - $("#send").prop("disabled", true); - return; - } - $("#send").prop("disabled", false); - success("receiver exists"); - }).fail(function(res) { - error("offline", true); - $("#send").prop("disabled", true); - }); -} - -/// Create Local Public-/Private-Key Pair -/** Called if user has not yet his keys, just generates a new key pair. */ -function createkeypair(user, pwd) { - status("generate keys"); - openpgp.generateKeyPair({ - numBits: 4096, - userId: user, - passphrase: pwd - }).then(function(keyPair) { - success("keys generated"); - localStorage["pubKey"] = keyPair.publicKeyArmored; - localStorage["privKey"] = keyPair.privateKeyArmored; - login(); - }).catch(function(e) { - console.log(e.stack); - error("generating key pairs failed"); - }); -} - -/// Get Own Public Key -/** @return public key object */ -function publicKey() { - if (typeof localStorage["pubKey"] == 'undefined') return null; - return openpgp.key.readArmored(localStorage["pubKey"]); -} - -/// Get Own Private Key -/** @return private key object */ -function privateKey() { - if (typeof localStorage["privKey"] == 'undefined') return null; - return openpgp.key.readArmored(localStorage["privKey"]); -} - -/// Get Own User Name -/** Get user name as user id of first public key */ -function userid() { - if (!publicKey() || - publicKey().keys.length < 1 || - publicKey().keys[0].getUserIds().length < 1) return null - return publicKey().keys[0].getUserIds()[0]; -} - -/// Clear Message Text And Attachments -/** Does not remove the receiver's name */ -function clearmessage() { - filecontent = new Array(); - $('#preview').empty(); - $("#msg").val(""); - notice("message cleared"); -} - -/// Display Image Attachments -function attachments(files, id) { - if (files) files.forEach(function(file) { - if (file.content.length<100000) { - var img = document.createElement('img'); - img.src = file.content; - $(id).append(img); - } - }); -} - -/// Upload Attachment -/** Prepares attachment to be sent in a message. If the attachment is - an image, it resizes the image to 400px on the lager side. - - By now, only images are supported. - - Stores data in global variable @ref filecontent. */ -function fileupload(evt) { - if (!window.FileReader) - return error("your browser dows not support file upload", true); - for (var i=0, f; f=evt.target.files[i]; ++i) { - var file = f; - var reader = new FileReader(); - reader.onload = function(evt) { - if (evt.target.error) return error("error reading file", true); - if (evt.target.readyState==0) return notice("waiting for data ..."); - if (evt.target.readyState==1) return notice("loading data ..."); - if (!file.type.match('^image/')) - return error(file.name+": not an image", true); - var img = document.createElement("img"); - img.onload = function() { - var MAX = 400; - var width = img.width; - var height = img.height; - if (width > MAX) { - height *= MAX / width; - width = MAX; - } - if (height > MAX) { - width *= MAX / height; - height = MAX; - } - var canvas = document.createElement("canvas"); - canvas.width = width; - canvas.height = height; - var ctx = canvas.getContext("2d"); - ctx.drawImage(img, 0, 0, width, height); - img.onload = function() { - filecontent.push({type: file.type, content: img.src}); - $("#preview").append(img); - success('image of type '+file.type+' is ready to be sent'); - } - img.src = canvas.toDataURL(file.type); - } - img.src=evt.target.result; - } - reader.readAsDataURL(file); - } -} - -/// Sets Receiver's Name -/** Called when clicked on a receiver's name. Sets focus to the - message text field. - - @param name The receiver's name. */ -function setreceiver(name) { - $("#recv").val(name); - checkpartner(name); - $("#msg").focus(); -} - -var userMap = null; -/// Get a user's public key. -/** The first time, gets it from the server, later from the cache. */ -function getPublicKey(user) { - var deferredObject = $.Deferred(); - if (userMap == null) { - if (localStorage.userMap) { - userMap = JSON.parse(localStorage.userMap); - console.log("got userMap from localStorage"); - } else { - userMap = new Array(); - } - } - if (userMap[user]) { - console.log("user "+user+" is in cache"); - deferredObject.resolve(userMap[user]); - } else { - $.post("pubkey.php", {user: user}) // get sender's key - .done(function(pk) { - console.log("got user "+user+" from server"); - userMap[user] = pk; - localStorage.userMap = JSON.stringify(userMap); - deferredObject.resolve(pk); - }).fail(function(e) { - error("offline"); - deferredObject.reject(e); - }); - } - return deferredObject.promise(); -} - -var startmsg = 0; ///< number of last downloaded message -/// Poll For New Messages, Get And Show Them -/** The global variable @ref startmsg stores the id of the last - downloaded message. This function is called by timer in regulary - periods. It calls get on server and passes @ref startmsg. The - server returns all newer messages. They are then decrypted. If - decryption is successful, then the message is shown, including - attachments. If decryption fails, the message is sent to someone - else, so failure is simply ignored. Beeps a sound once, if new - messages have been displayed. */ -function get() { - var beeped = false; // beep only once - $.post("get.php", {start: startmsg}) - .done(function(res) { // new messages from server received - var msgs = JSON.parse(res); - if (msgs) { - msgs.forEach(function(e) { // one single message - if (startmsgYou can only remove your local data. '+ - 'You will have to create a new account with a new name on the server. '+ - 'This means, you loose all your messages and you loose your account '+ - 'name «'+userid()+'» forever. '+ - 'This chat program is secure, nobody can restore your password. '+ - 'Without password, you can\'t prove, that you are «'+userid()+'».
'+ - ' ', ""); -} - -/// Main Chat Window -/** Gets chat widgets from server and displays them. Starts timer for - get() which polls for new messages. */ -function chat() { - $("#username").html(userid()+"@"+window.location.hostname); - if (!password) return getpwd(); - $.ajax({url: "chat.html", success: function(res) { - status(res); - getLoop(2000); - }}).fail(function() { - error("offline") - }); -} - -/// Login User -/** This is not really a login, it is just some kind of validation. - The server does not care if a user is online or not, it is only - interesting to the client to make sure, everything is fine. User - is logged in the following way: User name and public key are sent - to the server. If the user name exists on the server and the - public key is the same, the user is considered logged in, his - credentials seem to be valid. If user does not yet exits on - server, it is created now. If user exists, but public key is - different, then this is a complete failure, something went - terribly wrong. */ -function login() { - status("login ..."); - $.post("login.php", {user: userid(), - pubkey: localStorage.pubKey}, - function(res) { - var st = JSON.parse(res); - if (st.success) { - status("logged in ...", st.txt); - chat(); - } else { - error(st.txt); - } - }) - .fail(function(e) { - error("offline"); - }); -} - -/// Get And Display Form To Create New User -/** Shows user creation form. On submit, a private key is generated in - createkeypair(), then login() creates the user. */ -function newuser() { - status("new user ..."); - $.ajax({url: "newuser.html", success: function(res) { - status(res); - }}).fail(function() { - error("offline"); - }); -} - -/// Check if local storage is available -function checkLocalStorage() { - var test = 'test'; - try { - localStorage.setItem(test, test); - localStorage.removeItem(test); - return true; - } catch(e) { - status("No access to local storage. Please allow "+window.location.hostname - +" to access localhost, i.e. do not block cookies.
");
- error("local storage not available");
- }
- return false;
-}
-
-/// Initial Function: Startup
-/** Decide whether to login or to create a new user */
-function start() {
- $("#menu").hide();
- if (checkLocalStorage())
- try {
- status("Starting up ...");
- if (!userid()) {
- newuser();
- } else {
- login();
- }
- } catch (m) {
- console.log(m.stack);
- error(m);
- }
-}
-
-/// On Load, Call @ref start
-$(window.onbeforeunload = function() {
- return "Are you sure you want to navigate away?";
-});
-$(window.onunload = function() { // you probably don't want to leave now...
- alert('You are trying to leave.');
- return false;
-});
-/// Allow Running in Background on Android
-document.addEventListener('deviceready', function () {
- if (cordova && cordova.plugins.backgroundMode) {
- cordova.plugins.backgroundMode.enable();
- }
-}, false);
-
-/// Start Main Loop
-$(start);
-
diff --git a/html/send.php b/html/send.php
deleted file mode 100644
index 5505ad0..0000000
--- a/html/send.php
+++ /dev/null
@@ -1,48 +0,0 @@
-real_escape_string($user);
- $msg = $db->real_escape_string($msg);
- if (strlen($_REQUEST['msg'])>100000) error("message is too long");
- $q = $db->query("select pubkey from user where name='$user';");
- if (!$q || $q->num_rows!=1) error("user not found on server");
- /*
- $pubkey = gnupg_import($pgp, $q->fetch_row()[0]);
- if (!$pubkey) error("wrong identity");
- */
- $q = $db->query("insert into message (user, msg) values ('$user', '$msg');");
- if (!$q) {
- error_log("Error storing message: ".$db->error);
- error("storing message failed");
- }
- success("message stored");
- } catch (Exception $e) {
- error_log("Error storing message: ".$e->message);
- error("storing message failed");
- }
-}
-send($_REQUEST['user'], $_REQUEST['msg']);
-?>
\ No newline at end of file
diff --git a/html/update-messages.js b/html/update-messages.js
deleted file mode 100644
index 202f1a8..0000000
--- a/html/update-messages.js
+++ /dev/null
@@ -1,40 +0,0 @@
-importScripts('jquery.js');
-importScripts('openpgp.js');
-
-function attachments(files) {
- var res = '';
- if (files) files.forEach(function(file) {
- if (file.type.match("^image/"))
- res += '';
- });
- return res;
-}
-
-addEventListener('message', function(data) {
- var e = data.e;
- var key = data.key;
- var message = openpgp.message.readArmored(e["msg"]);
- var privkey = privateKey().keys[0];
- if (privkey.decrypt(password))
- openpgp.decryptAndVerifyMessage(privkey, key.keys, message)
- .then(function(msg) {
- var message = JSON.parse(msg.text);
- // todo: check msg.signatures[0].valid
- postMessage('
+ My page +
+<%- include('footer') -%> +``` + +## Client-side support + +Go to the [Latest Release](https://github.com/mde/ejs/releases/latest), download +`./ejs.js` or `./ejs.min.js`. + +Include one of these on your page, and `ejs.render(str)`. + +## Related projects + +There are a number of implementations of EJS: + + * TJ's implementation, the v1 of this library: https://github.com/tj/ejs + * Jupiter Consulting's EJS: http://www.embeddedjs.com/ + * EJS Embedded JavaScript Framework on Google Code: https://code.google.com/p/embeddedjavascript/ + * Sam Stephenson's Ruby implementation: https://rubygems.org/gems/ejs + * Erubis, an ERB implementation which also runs JavaScript: http://www.kuwata-lab.com/erubis/users-guide.04.html#lang-javascript + +## License + +Licensed under the Apache License, Version 2.0 +(\n My page\n
\n<%- include('footer') -%>\n```\n\n## Client-side support\n\nGo to the [Latest Release](https://github.com/mde/ejs/releases/latest), download\n`./ejs.js` or `./ejs.min.js`.\n\nInclude one of these on your page, and `ejs.render(str)`.\n\n## Related projects\n\nThere are a number of implementations of EJS:\n\n * TJ's implementation, the v1 of this library: https://github.com/tj/ejs\n * Jupiter Consulting's EJS: http://www.embeddedjs.com/\n * EJS Embedded JavaScript Framework on Google Code: https://code.google.com/p/embeddedjavascript/\n * Sam Stephenson's Ruby implementation: https://rubygems.org/gems/ejs\n * Erubis, an ERB implementation which also runs JavaScript: http://www.kuwata-lab.com/erubis/users-guide.04.html#lang-javascript\n\n## License\n\nLicensed under the Apache License, Version 2.0\n(yay
'); + assert.equal(fn(), 'yay
'); + }); + + test('empty input works', function () { + var fn = ejs.compile(''); + assert.equal(fn(), ''); + }); + + test('throw if there are syntax errors', function () { + try { + ejs.compile(fixture('fail.ejs')); + } + catch (err) { + assert.ok(err.message.indexOf('compiling ejs') > -1); + + try { + ejs.compile(fixture('fail.ejs'), {filename: 'fail.ejs'}); + } + catch (err) { + assert.ok(err.message.indexOf('fail.ejs') > -1); + return; + } + } + throw new Error('no error reported when there should be'); + }); + + test('allow customizing delimiter local var', function () { + var fn; + fn = ejs.compile('= name ?>
', {delimiter: '?'}); + assert.equal(fn({name: 'geddy'}), 'geddy
'); + + fn = ejs.compile('<:= name :>
', {delimiter: ':'}); + assert.equal(fn({name: 'geddy'}), 'geddy
'); + + fn = ejs.compile('<$= name $>
', {delimiter: '$'}); + assert.equal(fn({name: 'geddy'}), 'geddy
'); + }); + + test('default to using ejs.delimiter', function () { + var fn; + ejs.delimiter = '&'; + fn = ejs.compile('<&= name &>
'); + assert.equal(fn({name: 'geddy'}), 'geddy
'); + + fn = ejs.compile('<|= name |>
', {delimiter: '|'}); + assert.equal(fn({name: 'geddy'}), 'geddy
'); + delete ejs.delimiter; + }); + + test('have a working client option', function () { + var fn + , str + , preFn; + fn = ejs.compile('<%= foo %>
', {client: true}); + str = fn.toString(); + if (!process.env.running_under_istanbul) { + eval('var preFn = ' + str); + assert.equal(preFn({foo: 'bar'}), 'bar
'); + } + }); + + test('support client mode without locals', function () { + var fn + , str + , preFn; + fn = ejs.compile('<%= "foo" %>
', {client: true}); + str = fn.toString(); + if (!process.env.running_under_istanbul) { + eval('var preFn = ' + str); + assert.equal(preFn(), 'foo
'); + } + }); + + test('not include rethrow() in client mode if compileDebug is false', function () { + var fn = ejs.compile('<%= "foo" %>
', { + client: true + , compileDebug: false + }); + // There could be a `rethrow` in the function declaration + assert((fn.toString().match(/rethrow/g) || []).length <= 1); + }); +}); + +suite('ejs.render(str, data, opts)', function () { + test('render the template', function () { + assert.equal(ejs.render('yay
'), 'yay
'); + }); + + test('empty input works', function () { + assert.equal(ejs.render(''), ''); + }); + + test('undefined renders nothing escaped', function () { + assert.equal(ejs.render('<%= undefined %>'), ''); + }); + + test('undefined renders nothing raw', function () { + assert.equal(ejs.render('<%- undefined %>'), ''); + }); + + test('null renders nothing escaped', function () { + assert.equal(ejs.render('<%= null %>'), ''); + }); + + test('null renders nothing raw', function () { + assert.equal(ejs.render('<%- null %>'), ''); + }); + + test('zero-value data item renders something escaped', function () { + assert.equal(ejs.render('<%= 0 %>'), '0'); + }); + + test('zero-value data object renders something raw', function () { + assert.equal(ejs.render('<%- 0 %>'), '0'); + }); + + test('accept locals', function () { + assert.equal(ejs.render('<%= name %>
', {name: 'geddy'}), + 'geddy
'); + }); + + test('accept locals without using with() {}', function () { + assert.equal(ejs.render('<%= locals.name %>
', {name: 'geddy'}, + {_with: false}), + 'geddy
'); + assert.throws(function() { + ejs.render('<%= name %>
', {name: 'geddy'}, + {_with: false}); + }, /name is not defined/); + }); + + test('accept custom name for locals', function () { + ejs.localsName = 'it'; + assert.equal(ejs.render('<%= it.name %>
', {name: 'geddy'}, + {_with: false}), + 'geddy
'); + assert.throws(function() { + ejs.render('<%= name %>
', {name: 'geddy'}, + {_with: false}); + }, /name is not defined/); + ejs.localsName = 'locals'; + }); + + test('support caching', function () { + var file = __dirname + '/tmp/render.ejs' + , options = {cache: true, filename: file} + , out = ejs.render('Old
', {}, options) + , expected = 'Old
'; + assert.equal(out, expected); + // Assert no change, still in cache + out = ejs.render('New
', {}, options); + assert.equal(out, expected); + }); + + test('support LRU caching', function () { + var oldCache = ejs.cache + , file = __dirname + '/tmp/render.ejs' + , options = {cache: true, filename: file} + , out + , expected = 'Old
'; + + // Switch to LRU + ejs.cache = LRU(); + + out = ejs.render('Old
', {}, options); + assert.equal(out, expected); + // Assert no change, still in cache + out = ejs.render('New
', {}, options); + assert.equal(out, expected); + + // Restore system cache + ejs.cache = oldCache; + }); + + test('opts.context', function () { + var ctxt = {foo: 'FOO'} + , out = ejs.render('<%= this.foo %>', {}, {context: ctxt}); + assert.equal(out, ctxt.foo); + }); +}); + +suite('ejs.renderFile(path, [data], [options], fn)', function () { + test('render a file', function(done) { + ejs.renderFile('test/fixtures/para.ejs', function(err, html) { + if (err) { + return done(err); + } + assert.equal(html, 'hey
\n'); + done(); + }); + }); + + test('accept locals', function(done) { + var data = {name: 'fonebone'} + , options = {delimiter: '$'}; + ejs.renderFile('test/fixtures/user.ejs', data, options, function(err, html) { + if (err) { + return done(err); + } + assert.equal(html, 'Old
' + , file = __dirname + '/tmp/renderFile.ejs' + , options = {cache: true}; + fs.writeFileSync(file, 'Old
'); + + ejs.renderFile(file, {}, options, function (err, out) { + if (err) { + done(err); + } + fs.writeFileSync(file, 'New
'); + assert.equal(out, expected); + + ejs.renderFile(file, {}, options, function (err, out) { + if (err) { + done(err); + } + // Assert no change, still in cache + assert.equal(out, expected); + done(); + }); + }); + }); + + test('opts.context', function (done) { + var ctxt = {foo: 'FOO'}; + ejs.renderFile('test/fixtures/with-context.ejs', {}, + {context: ctxt}, function(err, html) { + if (err) { + return done(err); + } + assert.equal(html, ctxt.foo + '\n'); + done(); + }); + + }); +}); + +suite('cache specific', function () { + test('`clearCache` work properly', function () { + var expected = 'Old
' + , file = __dirname + '/tmp/clearCache.ejs' + , options = {cache: true, filename: file} + , out = ejs.render('Old
', {}, options); + assert.equal(out, expected); + + ejs.clearCache(); + + expected = 'New
'; + out = ejs.render('New
', {}, options); + assert.equal(out, expected); + }); + + test('`clearCache` work properly, LRU', function () { + var expected = 'Old
' + , oldCache = ejs.cache + , file = __dirname + '/tmp/clearCache.ejs' + , options = {cache: true, filename: file} + , out; + + ejs.cache = LRU(); + + out = ejs.render('Old
', {}, options); + assert.equal(out, expected); + ejs.clearCache(); + expected = 'New
'; + out = ejs.render('New
', {}, options); + assert.equal(out, expected); + + ejs.cache = oldCache; + }); + + test('LRU with cache-size 1', function () { + var oldCache = ejs.cache + , options + , out + , expected + , file; + + ejs.cache = LRU(1); + + file = __dirname + '/tmp/render1.ejs'; + options = {cache: true, filename: file}; + out = ejs.render('File1
', {}, options); + expected = 'File1
'; + assert.equal(out, expected); + + // Same filename, different template, but output + // should be the same because cache + file = __dirname + '/tmp/render1.ejs'; + options = {cache: true, filename: file}; + out = ejs.render('ChangedFile1
', {}, options); + expected = 'File1
'; + assert.equal(out, expected); + + // Different filiename -- output should be different, + // and previous cache-entry should be evicted + file = __dirname + '/tmp/render2.ejs'; + options = {cache: true, filename: file}; + out = ejs.render('File2
', {}, options); + expected = 'File2
'; + assert.equal(out, expected); + + // Entry with first filename should now be out of cache, + // results should be different + file = __dirname + '/tmp/render1.ejs'; + options = {cache: true, filename: file}; + out = ejs.render('ChangedFile1
', {}, options); + expected = 'ChangedFile1
'; + assert.equal(out, expected); + + ejs.cache = oldCache; + }); +}); + +suite('<%', function () { + test('without semicolons', function () { + assert.equal(ejs.render(fixture('no.semicolons.ejs')), + fixture('no.semicolons.html')); + }); +}); + +suite('<%=', function () { + test('escape & + + + +A JavaScript library for arbitrary-precision arithmetic.
+ + ++ See the README on GitHub for a + quick-start introduction. +
+
+ In all examples below, var
and semicolons are not shown, and if a commented-out
+ value is in quotes it means toString
has been called on the preceding expression.
+
BigNumber(value [, base]) ⇒ BigNumber
+ value
0
, ±Infinity
and
+ NaN
.
+ 15
significant digits are
+ considered invalid (if ERRORS
is true) as calling
+ toString
or valueOf
on
+ such numbers may not result in the intended value.
+ console.log( 823456789123456.3 ); // 823456789123456.2+
'0xff'
, are valid, as are
+ string values with the octal and binary prefixs '0o'
and '0b'
.
+ String values in octal literal form without the prefix will be interpreted as
+ decimals, e.g. '011'
is interpreted as 11, not 9.
+ 10
to 36
, lower and/or upper case letters can be
+ used to represent values from 10
to 35
.
+ a-z
represents values from 10
to
+ 35
, A-Z
from 36
to 61
, and
+ $
and _
represent 62
and 63
respectively
+ (this can be changed by editing the ALPHABET
variable near the top of the
+ source file).
+ base
2
to 64
inclusive
+ value
.base
is omitted, or is null
or undefined
, base
+ 10
is assumed.
+ Returns a new instance of a BigNumber object.
+
+ If a base is specified, the value is rounded according to
+ the current DECIMAL_PLACES
and
+ ROUNDING_MODE
configuration.
+
+ See Errors for the treatment of an invalid value
or
+ base
.
+
+x = new BigNumber(9) // '9' +y = new BigNumber(x) // '9' + +// 'new' is optional if ERRORS is false +BigNumber(435.345) // '435.345' + +new BigNumber('5032485723458348569331745.33434346346912144534543') +new BigNumber('4.321e+4') // '43210' +new BigNumber('-735.0918e-430') // '-7.350918e-428' +new BigNumber(Infinity) // 'Infinity' +new BigNumber(NaN) // 'NaN' +new BigNumber('.5') // '0.5' +new BigNumber('+2') // '2' +new BigNumber(-10110100.1, 2) // '-180.5' +new BigNumber(-0b10110100.1) // '-180.5' +new BigNumber('123412421.234324', 5) // '607236.557696' +new BigNumber('ff.8', 16) // '255.5' +new BigNumber('0xff.8') // '255.5'+
+ The following throws 'not a base 2 number'
if
+ ERRORS
is true, otherwise it returns a BigNumber with value
+ NaN
.
+
new BigNumber(9, 2)+
+ The following throws 'number type has more than 15 significant digits'
if
+ errors
is true, otherwise it returns a BigNumber with value
+ 96517860459076820
.
+
new BigNumber(96517860459076817.4395)+
+ The following throws 'not a number'
if ERRORS
+ is true, otherwise it returns a BigNumber with value NaN
.
+
new BigNumber('blurgh')+
+ A value is only rounded by the constructor if a base is specified. +
+BigNumber.config({ DECIMAL_PLACES: 5 }) +new BigNumber(1.23456789) // '1.23456789' +new BigNumber(1.23456789, 10) // '1.23457'+ + + +
The static methods of a BigNumber constructor.
+ + + + +.another([obj]) ⇒ BigNumber constructor
+ obj
: object
+ Returns a new independent BigNumber constructor with configuration as described by
+ obj
(see config
), or with the default
+ configuration if obj
is null
or undefined
.
+
BigNumber.config({ DECIMAL_PLACES: 5 }) +BN = BigNumber.another({ DECIMAL_PLACES: 9 }) + +x = new BigNumber(1) +y = new BN(1) + +x.div(3) // 0.33333 +y.div(3) // 0.333333333 + +// BN = BigNumber.another({ DECIMAL_PLACES: 9 }) is equivalent to: +BN = BigNumber.another() +BN.config({ DECIMAL_PLACES: 9 })+ + + +
config([obj]) ⇒ object
+ obj
: object: an object that contains some or all of the following
+ properties.
+
Configures the 'global' settings for this particular BigNumber constructor.
+Note: the configuration can also be supplied as an argument list, see below.
+DECIMAL_PLACES
0
to 1e+9
inclusive20
+ BigNumber.config({ DECIMAL_PLACES: 5 }) +BigNumber.config(5) // equivalent+
ROUNDING_MODE
0
to 8
inclusive4
(ROUND_HALF_UP
)
+ round
,
+ toExponential
,
+ toFixed
,
+ toFormat
and
+ toPrecision
.
+ BigNumber.config({ ROUNDING_MODE: 0 }) +BigNumber.config(null, BigNumber.ROUND_UP) // equivalent+
EXPONENTIAL_AT
0
to 1e+9
inclusive, or
+ -1e+9
to 0
inclusive, integer
+ 0
to 1e+9
inclusive ][-7, 20]
+ toString
returns exponential notation.
+ [-7, 20]
.
+ BigNumber.config({ EXPONENTIAL_AT: 2 }) +new BigNumber(12.3) // '12.3' e is only 1 +new BigNumber(123) // '1.23e+2' +new BigNumber(0.123) // '0.123' e is only -1 +new BigNumber(0.0123) // '1.23e-2' + +BigNumber.config({ EXPONENTIAL_AT: [-7, 20] }) +new BigNumber(123456789) // '123456789' e is only 8 +new BigNumber(0.000000123) // '1.23e-7' + +// Almost never return exponential notation: +BigNumber.config({ EXPONENTIAL_AT: 1e+9 }) + +// Always return exponential notation: +BigNumber.config({ EXPONENTIAL_AT: 0 })+
EXPONENTIAL_AT
, the toFixed
method
+ will always return a value in normal notation and the toExponential
method
+ will always return a value in exponential form.
+ toString
with a base argument, e.g. toString(10)
, will
+ also always return normal notation.
+ RANGE
1
to 1e+9
inclusive, or
+ -1e+9
to -1
inclusive, integer
+ 1
to 1e+9
inclusive ][-1e+9, 1e+9]
+ Infinity
and underflow to
+ zero occurs.
+ Infinity
and those with a
+ negative exponent of greater magnitude become zero.
+ Infinity
, use [-324, 308]
.
+ BigNumber.config({ RANGE: 500 }) +BigNumber.config().RANGE // [ -500, 500 ] +new BigNumber('9.999e499') // '9.999e+499' +new BigNumber('1e500') // 'Infinity' +new BigNumber('1e-499') // '1e-499' +new BigNumber('1e-500') // '0' + +BigNumber.config({ RANGE: [-3, 4] }) +new BigNumber(99999) // '99999' e is only 4 +new BigNumber(100000) // 'Infinity' e is 5 +new BigNumber(0.001) // '0.01' e is only -3 +new BigNumber(0.0001) // '0' e is -4+
9.999...e+1000000000
.1e-1000000000
.
+ ERRORS
true
, false
, 0
or
+ 1
.true
+ ERRORS
is false, no errors will be thrown.
+ BigNumber.config({ ERRORS: false })
CRYPTO
true
, false
, 0
or
+ 1
.false
+ CRYPTO
is set to true
then the
+ random
method will generate random digits using
+ crypto.getRandomValues
in browsers that support it, or
+ crypto.randomBytes
if using a version of Node.js that supports it.
+ CRYPTO
to true
will fail, and if ERRORS
+ is true
an exception will be thrown.
+ CRYPTO
is false
then the source of randomness used will be
+ Math.random
(which is assumed to generate at least 30
bits of
+ randomness).
+ random
.BigNumber.config({ CRYPTO: true }) +BigNumber.config().CRYPTO // true +BigNumber.random() // 0.54340758610486147524+
MODULO_MODE
0
to 9
inclusive1
(ROUND_DOWN
)
+ a mod n
.q = a / n
, is calculated according to the
+ ROUNDING_MODE
that corresponds to the chosen
+ MODULO_MODE
.
+ r
, is calculated as: r = a - n * q
.Property | Value | Description |
---|---|---|
ROUND_UP | 0 | ++ The remainder is positive if the dividend is negative, otherwise it is negative. + | +
ROUND_DOWN | 1 | +
+ The remainder has the same sign as the dividend. + This uses 'truncating division' and matches the behaviour of JavaScript's + remainder operator % .
+ |
+
ROUND_FLOOR | 3 | +
+ The remainder has the same sign as the divisor. + This matches Python's % operator.
+ |
+
ROUND_HALF_EVEN | 6 | +The IEEE 754 remainder function. | +
EUCLID | 9 | +
+ The remainder is always positive. Euclidian division: + q = sign(n) * floor(a / abs(n))
+ |
+
modulo
.BigNumber.config({ MODULO_MODE: BigNumber.EUCLID }) +BigNumber.config({ MODULO_MODE: 9 }) // equivalent+
POW_PRECISION
0
to 1e+9
inclusive.100
+ 0
, the number of signifcant digits will not be limited.toPower
.BigNumber.config({ POW_PRECISION: 100 })
FORMAT
FORMAT
object configures the format of the string returned by the
+ toFormat
method.
+ FORMAT
object that are
+ recognised, and their default values.
+ FORMAT
object will not be checked for validity. The existing
+ FORMAT
object will simply be replaced by the object that is passed in.
+ Note that all the properties shown below do not have to be included.
+ toFormat
for examples of usage.+BigNumber.config({ + FORMAT: { + // the decimal separator + decimalSeparator: '.', + // the grouping separator of the integer part + groupSeparator: ',', + // the primary grouping size of the integer part + groupSize: 3, + // the secondary grouping size of the integer part + secondaryGroupSize: 0, + // the grouping separator of the fraction part + fractionGroupSeparator: ' ', + // the grouping size of the fraction part + fractionGroupSize: 0 + } +});+
Returns an object with the above properties and their current values.
+
+ If the value to be assigned to any of the above properties is null
or
+ undefined
it is ignored.
+
See Errors for the treatment of invalid values.
++BigNumber.config({ + DECIMAL_PLACES: 40, + ROUNDING_MODE: BigNumber.ROUND_HALF_CEIL, + EXPONENTIAL_AT: [-10, 20], + RANGE: [-500, 500], + ERRORS: true, + CRYPTO: true, + MODULO_MODE: BigNumber.ROUND_FLOOR, + POW_PRECISION: 80, + FORMAT: { + groupSize: 3, + groupSeparator: ' ', + decimalSeparator: ',' + } +}); + +// Alternatively but equivalently (excluding FORMAT): +BigNumber.config( 40, 7, [-10, 20], 500, 1, 1, 3, 80 ) + +obj = BigNumber.config(); +obj.ERRORS // true +obj.RANGE // [-500, 500]+ + + +
.max([arg1 [, arg2, ...]]) ⇒ BigNumber
+
+ arg1
, arg2
, ...: number|string|BigNumber
+ See BigNumber
for further parameter details.
+
+ Returns a BigNumber whose value is the maximum of arg1
,
+ arg2
,... .
+
The argument to this method can also be an array of values.
+The return value is always exact and unrounded.
+x = new BigNumber('3257869345.0378653') +BigNumber.max(4e9, x, '123456789.9') // '4000000000' + +arr = [12, '13', new BigNumber(14)] +BigNumber.max(arr) // '14'+ + + +
.min([arg1 [, arg2, ...]]) ⇒ BigNumber
+
+ arg1
, arg2
, ...: number|string|BigNumber
+ See BigNumber
for further parameter details.
+
+ Returns a BigNumber whose value is the minimum of arg1
,
+ arg2
,... .
+
The argument to this method can also be an array of values.
+The return value is always exact and unrounded.
+x = new BigNumber('3257869345.0378653') +BigNumber.min(4e9, x, '123456789.9') // '123456789.9' + +arr = [2, new BigNumber(-14), '-15.9999', -12] +BigNumber.min(arr) // '-15.9999'+ + + +
.random([dp]) ⇒ BigNumber
+ dp
: number: integer, 0
to 1e+9
inclusive
+ Returns a new BigNumber with a pseudo-random value equal to or greater than 0
and
+ less than 1
.
+
+ The return value will have dp
decimal places (or less if trailing zeros are
+ produced).
+ If dp
is omitted then the number of decimal places will default to the current
+ DECIMAL_PLACES
setting.
+
+ Depending on the value of this BigNumber constructor's
+ CRYPTO
setting and the support for the
+ crypto
object in the host environment, the random digits of the return value are
+ generated by either Math.random
(fastest), crypto.getRandomValues
+ (Web Cryptography API in recent browsers) or crypto.randomBytes
(Node.js).
+
+ If CRYPTO
is true
, i.e. one of the
+ crypto
methods is to be used, the value of a returned BigNumber should be
+ cryptographically-secure and statistically indistinguishable from a random value.
+
BigNumber.config({ DECIMAL_PLACES: 10 }) +BigNumber.random() // '0.4117936847' +BigNumber.random(20) // '0.78193327636914089009'+ + + +
+ The library's enumerated rounding modes are stored as properties of the constructor.
+ (They are not referenced internally by the library itself.)
+
+ Rounding modes 0
to 6
(inclusive) are the same as those of Java's
+ BigDecimal class.
+
Property | +Value | +Description | +
---|---|---|
ROUND_UP | +0 | +Rounds away from zero | +
ROUND_DOWN | +1 | +Rounds towards zero | +
ROUND_CEIL | +2 | +Rounds towards Infinity |
+
ROUND_FLOOR | +3 | +Rounds towards -Infinity |
+
ROUND_HALF_UP | +4 | +
+ Rounds towards nearest neighbour. + If equidistant, rounds away from zero + |
+
ROUND_HALF_DOWN | +5 | +
+ Rounds towards nearest neighbour. + If equidistant, rounds towards zero + |
+
ROUND_HALF_EVEN | +6 | +
+ Rounds towards nearest neighbour. + If equidistant, rounds towards even neighbour + |
+
ROUND_HALF_CEIL | +7 | +
+ Rounds towards nearest neighbour. + If equidistant, rounds towards Infinity
+ |
+
ROUND_HALF_FLOOR | +8 | +
+ Rounds towards nearest neighbour. + If equidistant, rounds towards -Infinity
+ |
+
+BigNumber.config({ ROUNDING_MODE: BigNumber.ROUND_CEIL }) +BigNumber.config({ ROUNDING_MODE: 2 }) // equivalent+ + +
The methods inherited by a BigNumber instance from its constructor's prototype object.
+A BigNumber is immutable in the sense that it is not changed by its methods.
+
+ The treatment of ±0
, ±Infinity
and NaN
is
+ consistent with how JavaScript treats these values.
+
+ Many method names have a shorter alias.
+ (Internally, the library always uses the shorter method names.)
+
.abs() ⇒ BigNumber
+ Returns a BigNumber whose value is the absolute value, i.e. the magnitude, of the value of + this BigNumber. +
+The return value is always exact and unrounded.
++x = new BigNumber(-0.8) +y = x.absoluteValue() // '0.8' +z = y.abs() // '0.8'+ + + +
.ceil() ⇒ BigNumber
+ Returns a BigNumber whose value is the value of this BigNumber rounded to
+ a whole number in the direction of positive Infinity
.
+
+x = new BigNumber(1.3) +x.ceil() // '2' +y = new BigNumber(-1.8) +y.ceil() // '-1'+ + + +
.cmp(n [, base]) ⇒ number
+ n
: number|string|BigNumber
+ base
: number
+ See BigNumber for further parameter details.
+
Returns | |
---|---|
1 |
+ If the value of this BigNumber is greater than the value of n |
+
-1 |
+ If the value of this BigNumber is less than the value of n |
+
0 |
+ If this BigNumber and n have the same value |
+
null |
+ If the value of either this BigNumber or n is NaN |
+
+x = new BigNumber(Infinity) +y = new BigNumber(5) +x.comparedTo(y) // 1 +x.comparedTo(x.minus(1)) // 0 +y.cmp(NaN) // null +y.cmp('110', 2) // -1+ + + +
.dp() ⇒ number
+ Return the number of decimal places of the value of this BigNumber, or null
if
+ the value of this BigNumber is ±Infinity
or NaN
.
+
+x = new BigNumber(123.45) +x.decimalPlaces() // 2 +y = new BigNumber('9.9e-101') +y.dp() // 102+ + + +
.div(n [, base]) ⇒ BigNumber
+
+ n
: number|string|BigNumber
+ base
: number
+ See BigNumber for further parameter details.
+
+ Returns a BigNumber whose value is the value of this BigNumber divided by
+ n
, rounded according to the current
+ DECIMAL_PLACES
and
+ ROUNDING_MODE
configuration.
+
+x = new BigNumber(355) +y = new BigNumber(113) +x.dividedBy(y) // '3.14159292035398230088' +x.div(5) // '71' +x.div(47, 16) // '5'+ + + +
.divToInt(n [, base]) ⇒
+ BigNumber
+
+ n
: number|string|BigNumber
+ base
: number
+ See BigNumber for further parameter details.
+
+ Return a BigNumber whose value is the integer part of dividing the value of this BigNumber by
+ n
.
+
+x = new BigNumber(5) +y = new BigNumber(3) +x.dividedToIntegerBy(y) // '1' +x.divToInt(0.7) // '7' +x.divToInt('0.f', 16) // '5'+ + + +
.eq(n [, base]) ⇒ boolean
+ n
: number|string|BigNumber
+ base
: number
+ See BigNumber for further parameter details.
+
+ Returns true
if the value of this BigNumber equals the value of n
,
+ otherwise returns false
.
+ As with JavaScript, NaN
does not equal NaN
.
+
Note: This method uses the comparedTo
method internally.
+0 === 1e-324 // true +x = new BigNumber(0) +x.equals('1e-324') // false +BigNumber(-0).eq(x) // true ( -0 === 0 ) +BigNumber(255).eq('ff', 16) // true + +y = new BigNumber(NaN) +y.equals(NaN) // false+ + + +
.floor() ⇒ BigNumber
+ Returns a BigNumber whose value is the value of this BigNumber rounded to a whole number in
+ the direction of negative Infinity
.
+
+x = new BigNumber(1.8) +x.floor() // '1' +y = new BigNumber(-1.3) +y.floor() // '-2'+ + + +
.gt(n [, base]) ⇒ boolean
+ n
: number|string|BigNumber
+ base
: number
+ See BigNumber for further parameter details.
+
+ Returns true
if the value of this BigNumber is greater than the value of
+ n
, otherwise returns false
.
+
Note: This method uses the comparedTo
method internally.
+0.1 > (0.3 - 0.2) // true +x = new BigNumber(0.1) +x.greaterThan(BigNumber(0.3).minus(0.2)) // false +BigNumber(0).gt(x) // false +BigNumber(11, 3).gt(11.1, 2) // true+ + + +
.gte(n [, base]) ⇒ boolean
+
+ n
: number|string|BigNumber
+ base
: number
+ See BigNumber for further parameter details.
+
+ Returns true
if the value of this BigNumber is greater than or equal to the value
+ of n
, otherwise returns false
.
+
Note: This method uses the comparedTo
method internally.
+(0.3 - 0.2) >= 0.1 // false +x = new BigNumber(0.3).minus(0.2) +x.greaterThanOrEqualTo(0.1) // true +BigNumber(1).gte(x) // true +BigNumber(10, 18).gte('i', 36) // true+ + + +
.isFinite() ⇒ boolean
+ Returns true
if the value of this BigNumber is a finite number, otherwise
+ returns false
.
+
+ The only possible non-finite values of a BigNumber are NaN
, Infinity
+ and -Infinity
.
+
+x = new BigNumber(1) +x.isFinite() // true +y = new BigNumber(Infinity) +y.isFinite() // false+
+ Note: The native method isFinite()
can be used if
+ n <= Number.MAX_VALUE
.
+
.isInt() ⇒ boolean
+ Returns true
if the value of this BigNumber is a whole number, otherwise returns
+ false
.
+
+x = new BigNumber(1) +x.isInteger() // true +y = new BigNumber(123.456) +y.isInt() // false+ + + +
.isNaN() ⇒ boolean
+ Returns true
if the value of this BigNumber is NaN
, otherwise
+ returns false
.
+
+x = new BigNumber(NaN) +x.isNaN() // true +y = new BigNumber('Infinity') +y.isNaN() // false+
Note: The native method isNaN()
can also be used.
.isNeg() ⇒ boolean
+ Returns true
if the value of this BigNumber is negative, otherwise returns
+ false
.
+
+x = new BigNumber(-0) +x.isNegative() // true +y = new BigNumber(2) +y.isNeg() // false+
Note: n < 0
can be used if n <= -Number.MIN_VALUE
.
.isZero() ⇒ boolean
+ Returns true
if the value of this BigNumber is zero or minus zero, otherwise
+ returns false
.
+
+x = new BigNumber(-0) +x.isZero() && x.isNeg() // true +y = new BigNumber(Infinity) +y.isZero() // false+
Note: n == 0
can be used if n >= Number.MIN_VALUE
.
.lt(n [, base]) ⇒ boolean
+ n
: number|string|BigNumber
+ base
: number
+ See BigNumber for further parameter details.
+
+ Returns true
if the value of this BigNumber is less than the value of
+ n
, otherwise returns false
.
+
Note: This method uses the comparedTo
method internally.
+(0.3 - 0.2) < 0.1 // true +x = new BigNumber(0.3).minus(0.2) +x.lessThan(0.1) // false +BigNumber(0).lt(x) // true +BigNumber(11.1, 2).lt(11, 3) // true+ + + +
.lte(n [, base]) ⇒ boolean
+
+ n
: number|string|BigNumber
+ base
: number
+ See BigNumber for further parameter details.
+
+ Returns true
if the value of this BigNumber is less than or equal to the value of
+ n
, otherwise returns false
.
+
Note: This method uses the comparedTo
method internally.
+0.1 <= (0.3 - 0.2) // false +x = new BigNumber(0.1) +x.lessThanOrEqualTo(BigNumber(0.3).minus(0.2)) // true +BigNumber(-1).lte(x) // true +BigNumber(10, 18).lte('i', 36) // true+ + + +
.minus(n [, base]) ⇒ BigNumber
+
+ n
: number|string|BigNumber
+ base
: number
+ See BigNumber for further parameter details.
+
Returns a BigNumber whose value is the value of this BigNumber minus n
.
The return value is always exact and unrounded.
++0.3 - 0.1 // 0.19999999999999998 +x = new BigNumber(0.3) +x.minus(0.1) // '0.2' +x.minus(0.6, 20) // '0'+ + + +
.mod(n [, base]) ⇒ BigNumber
+ n
: number|string|BigNumber
+ base
: number
+ See BigNumber for further parameter details.
+
+ Returns a BigNumber whose value is the value of this BigNumber modulo n
, i.e.
+ the integer remainder of dividing this BigNumber by n
.
+
+ The value returned, and in particular its sign, is dependent on the value of the
+ MODULO_MODE
setting of this BigNumber constructor.
+ If it is 1
(default value), the result will have the same sign as this BigNumber,
+ and it will match that of Javascript's %
operator (within the limits of double
+ precision) and BigDecimal's remainder
method.
+
The return value is always exact and unrounded.
+
+ See MODULO_MODE
for a description of the other
+ modulo modes.
+
+1 % 0.9 // 0.09999999999999998 +x = new BigNumber(1) +x.modulo(0.9) // '0.1' +y = new BigNumber(33) +y.mod('a', 33) // '3'+ + + +
.neg() ⇒ BigNumber
+ Returns a BigNumber whose value is the value of this BigNumber negated, i.e. multiplied by
+ -1
.
+
+x = new BigNumber(1.8) +x.negated() // '-1.8' +y = new BigNumber(-1.3) +y.neg() // '1.3'+ + + +
.plus(n [, base]) ⇒ BigNumber
+ n
: number|string|BigNumber
+ base
: number
+ See BigNumber for further parameter details.
+
Returns a BigNumber whose value is the value of this BigNumber plus n
.
The return value is always exact and unrounded.
++0.1 + 0.2 // 0.30000000000000004 +x = new BigNumber(0.1) +y = x.plus(0.2) // '0.3' +BigNumber(0.7).plus(x).plus(y) // '1' +x.plus('0.1', 8) // '0.225'+ + + +
.sd([z]) ⇒ number
+ z
: boolean|number: true
, false
, 0
+ or 1
+
Returns the number of significant digits of the value of this BigNumber.
+
+ If z
is true
or 1
then any trailing zeros of the
+ integer part of a number are counted as significant digits, otherwise they are not.
+
+x = new BigNumber(1.234) +x.precision() // 4 +y = new BigNumber(987000) +y.sd() // 3 +y.sd(true) // 6+ + + +
.round([dp [, rm]]) ⇒ BigNumber
+ dp
: number: integer, 0
to 1e+9
inclusive
+ rm
: number: integer, 0
to 8
inclusive
+
+ Returns a BigNumber whose value is the value of this BigNumber rounded by rounding mode
+ rm
to a maximum of dp
decimal places.
+
+ if dp
is omitted, or is null
or undefined
, the
+ return value is n
rounded to a whole number.
+ if rm
is omitted, or is null
or undefined
,
+ ROUNDING_MODE
is used.
+
+ See Errors for the treatment of other non-integer or out of range
+ dp
or rm
values.
+
+x = 1234.56 +Math.round(x) // 1235 + +y = new BigNumber(x) +y.round() // '1235' +y.round(1) // '1234.6' +y.round(2) // '1234.56' +y.round(10) // '1234.56' +y.round(0, 1) // '1234' +y.round(0, 6) // '1235' +y.round(1, 1) // '1234.5' +y.round(1, BigNumber.ROUND_HALF_EVEN) // '1234.6' +y // '1234.56'+ + + +
.shift(n) ⇒ BigNumber
+ n
: number: integer,
+ -9007199254740991
to 9007199254740991
inclusive
+
+ Returns a BigNumber whose value is the value of this BigNumber shifted n
places.
+
+ The shift is of the decimal point, i.e. of powers of ten, and is to the left if n
+ is negative or to the right if n
is positive.
+
The return value is always exact and unrounded.
++x = new BigNumber(1.23) +x.shift(3) // '1230' +x.shift(-3) // '0.00123'+ + + +
.sqrt() ⇒ BigNumber
+ Returns a BigNumber whose value is the square root of the value of this BigNumber,
+ rounded according to the current
+ DECIMAL_PLACES
and
+ ROUNDING_MODE
configuration.
+
+ The return value will be correctly rounded, i.e. rounded as if the result was first calculated + to an infinite number of correct digits before rounding. +
++x = new BigNumber(16) +x.squareRoot() // '4' +y = new BigNumber(3) +y.sqrt() // '1.73205080756887729353'+ + + +
.times(n [, base]) ⇒ BigNumber
+ n
: number|string|BigNumber
+ base
: number
+ See BigNumber for further parameter details.
+
Returns a BigNumber whose value is the value of this BigNumber times n
.
The return value is always exact and unrounded.
++0.6 * 3 // 1.7999999999999998 +x = new BigNumber(0.6) +y = x.times(3) // '1.8' +BigNumber('7e+500').times(y) // '1.26e+501' +x.times('-a', 16) // '-6'+ + + +
.toDigits([sd [, rm]]) ⇒ BigNumber
+
+ sd
: number: integer, 1
to 1e+9
inclusive.
+ rm
: number: integer, 0
to 8
inclusive.
+
+ Returns a BigNumber whose value is the value of this BigNumber rounded to sd
+ significant digits using rounding mode rm
.
+
+ If sd
is omitted or is null
or undefined
, the return
+ value will not be rounded.
+ If rm
is omitted or is null
or undefined
,
+ ROUNDING_MODE
will be used.
+
+ See Errors for the treatment of other non-integer or out of range
+ sd
or rm
values.
+
+BigNumber.config({ precision: 5, rounding: 4 }) +x = new BigNumber(9876.54321) + +x.toSignificantDigits() // '9876.5' +x.toSignificantDigits(6) // '9876.54' +x.toSignificantDigits(6, BigNumber.ROUND_UP) // '9876.55' +x.toSD(2) // '9900' +x.toSD(2, 1) // '9800' +x // '9876.54321'+ + + +
.toExponential([dp [, rm]]) ⇒ string
+
+ dp
: number: integer, 0
to 1e+9
inclusive
+ rm
: number: integer, 0
to 8
inclusive
+
+ Returns a string representing the value of this BigNumber in exponential notation rounded
+ using rounding mode rm
to dp
decimal places, i.e with one digit
+ before the decimal point and dp
digits after it.
+
+ If the value of this BigNumber in exponential notation has fewer than dp
fraction
+ digits, the return value will be appended with zeros accordingly.
+
+ If dp
is omitted, or is null
or undefined
, the number
+ of digits after the decimal point defaults to the minimum number of digits necessary to
+ represent the value exactly.
+ If rm
is omitted or is null
or undefined
,
+ ROUNDING_MODE
is used.
+
+ See Errors for the treatment of other non-integer or out of range
+ dp
or rm
values.
+
+x = 45.6 +y = new BigNumber(x) +x.toExponential() // '4.56e+1' +y.toExponential() // '4.56e+1' +x.toExponential(0) // '5e+1' +y.toExponential(0) // '5e+1' +x.toExponential(1) // '4.6e+1' +y.toExponential(1) // '4.6e+1' +y.toExponential(1, 1) // '4.5e+1' (ROUND_DOWN) +x.toExponential(3) // '4.560e+1' +y.toExponential(3) // '4.560e+1'+ + + +
.toFixed([dp [, rm]]) ⇒ string
+
+ dp
: number: integer, 0
to 1e+9
inclusive
+ rm
: number: integer, 0
to 8
inclusive
+
+ Returns a string representing the value of this BigNumber in normal (fixed-point) notation
+ rounded to dp
decimal places using rounding mode rm
.
+
+ If the value of this BigNumber in normal notation has fewer than dp
fraction
+ digits, the return value will be appended with zeros accordingly.
+
+ Unlike Number.prototype.toFixed
, which returns exponential notation if a number
+ is greater or equal to 1021
, this method will always return normal
+ notation.
+
+ If dp
is omitted or is null
or undefined
, the return
+ value will be unrounded and in normal notation. This is also unlike
+ Number.prototype.toFixed
, which returns the value to zero decimal places.
+ It is useful when fixed-point notation is required and the current
+ EXPONENTIAL_AT
setting causes
+ toString
to return exponential notation.
+ If rm
is omitted or is null
or undefined
,
+ ROUNDING_MODE
is used.
+
+ See Errors for the treatment of other non-integer or out of range
+ dp
or rm
values.
+
+x = 3.456 +y = new BigNumber(x) +x.toFixed() // '3' +y.toFixed() // '3.456' +y.toFixed(0) // '3' +x.toFixed(2) // '3.46' +y.toFixed(2) // '3.46' +y.toFixed(2, 1) // '3.45' (ROUND_DOWN) +x.toFixed(5) // '3.45600' +y.toFixed(5) // '3.45600'+ + + +
.toFormat([dp [, rm]]) ⇒ string
+
+ dp
: number: integer, 0
to 1e+9
inclusive
+ rm
: number: integer, 0
to 8
inclusive
+
+
+ Returns a string representing the value of this BigNumber in normal (fixed-point) notation
+ rounded to dp
decimal places using rounding mode rm
, and formatted
+ according to the properties of the FORMAT
object.
+
+ See the examples below for the properties of the
+ FORMAT
object, their types and their usage.
+
+ If dp
is omitted or is null
or undefined
, then the
+ return value is not rounded to a fixed number of decimal places.
+ If rm
is omitted or is null
or undefined
,
+ ROUNDING_MODE
is used.
+
+ See Errors for the treatment of other non-integer or out of range
+ dp
or rm
values.
+
+format = { + decimalSeparator: '.', + groupSeparator: ',', + groupSize: 3, + secondaryGroupSize: 0, + fractionGroupSeparator: ' ', + fractionGroupSize: 0 +} +BigNumber.config({ FORMAT: format }) + +x = new BigNumber('123456789.123456789') +x.toFormat() // '123,456,789.123456789' +x.toFormat(1) // '123,456,789.1' + +// If a reference to the object assigned to FORMAT has been retained, +// the format properties can be changed directly +format.groupSeparator = ' ' +format.fractionGroupSize = 5 +x.toFormat() // '123 456 789.12345 6789' + +BigNumber.config({ + FORMAT: { + decimalSeparator = ',', + groupSeparator = '.', + groupSize = 3, + secondaryGroupSize = 2 + } +}) + +x.toFormat(6) // '12.34.56.789,123'+ + + +
.toFraction([max]) ⇒ [string, string]
+
+ max
: number|string|BigNumber: integer >= 1
and <
+ Infinity
+
+ Returns a string array representing the value of this BigNumber as a simple fraction with an
+ integer numerator and an integer denominator. The denominator will be a positive non-zero
+ value less than or equal to max
.
+
+ If a maximum denominator, max
, is not specified, or is null
or
+ undefined
, the denominator will be the lowest value necessary to represent the
+ number exactly.
+
+ See Errors for the treatment of other non-integer or out of range
+ max
values.
+
+x = new BigNumber(1.75) +x.toFraction() // '7, 4' + +pi = new BigNumber('3.14159265358') +pi.toFraction() // '157079632679,50000000000' +pi.toFraction(100000) // '312689, 99532' +pi.toFraction(10000) // '355, 113' +pi.toFraction(100) // '311, 99' +pi.toFraction(10) // '22, 7' +pi.toFraction(1) // '3, 1'+ + + +
.toJSON() ⇒ string
As valueOf
.
+x = new BigNumber('177.7e+457') +y = new BigNumber(235.4325) +z = new BigNumber('0.0098074') + +// Serialize an array of three BigNumbers +str = JSON.stringify( [x, y, z] ) +// "["1.777e+459","235.4325","0.0098074"]" + +// Return an array of three BigNumbers +JSON.parse(str, function (key, val) { + return key === '' ? val : new BigNumber(val) +})+ + + +
.toNumber() ⇒ number
Returns the value of this BigNumber as a JavaScript number primitive.
++ Type coercion with, for example, the unary plus operator will also work, except that a + BigNumber with the value minus zero will be converted to positive zero. +
++x = new BigNumber(456.789) +x.toNumber() // 456.789 ++x // 456.789 + +y = new BigNumber('45987349857634085409857349856430985') +y.toNumber() // 4.598734985763409e+34 + +z = new BigNumber(-0) +1 / +z // Infinity +1 / z.toNumber() // -Infinity+ + + +
.pow(n) ⇒ BigNumber
+ n
: number: integer,
+ -9007199254740991
to 9007199254740991
inclusive
+
+ Returns a BigNumber whose value is the value of this BigNumber raised to the power
+ n
.
+
+ If n
is negative the result is rounded according to the current
+ DECIMAL_PLACES
and
+ ROUNDING_MODE
configuration.
+
+ If n
is not an integer or is out of range:
+
+ If ERRORS
is true
a BigNumber Error is thrown,
+ else if n
is greater than 9007199254740991
, it is interpreted as
+ Infinity
;
+ else if n
is less than -9007199254740991
, it is interpreted as
+ -Infinity
;
+ else if n
is otherwise a number, it is truncated to an integer;
+ else it is interpreted as NaN
.
+
+ As the number of digits of the result of the power operation can grow so large so quickly,
+ e.g. 123.45610000 has over 50000
digits, the number of significant
+ digits calculated is limited to the value of the
+ POW_PRECISION
setting (default value:
+ 100
).
+
+ Set POW_PRECISION
to 0
for an
+ unlimited number of significant digits to be calculated (this will cause the method to slow
+ dramatically for larger exponents).
+
+ Negative exponents will be calculated to the number of decimal places specified by
+ DECIMAL_PLACES
(but not to more than
+ POW_PRECISION
significant digits).
+
+Math.pow(0.7, 2) // 0.48999999999999994 +x = new BigNumber(0.7) +x.toPower(2) // '0.49' +BigNumber(3).pow(-2) // '0.11111111111111111111'+ + + +
.toPrecision([sd [, rm]]) ⇒ string
+
+ sd
: number: integer, 1
to 1e+9
inclusive
+ rm
: number: integer, 0
to 8
inclusive
+
+ Returns a string representing the value of this BigNumber rounded to sd
+ significant digits using rounding mode rm
.
+
+ If sd
is less than the number of digits necessary to represent the integer part
+ of the value in normal (fixed-point) notation, then exponential notation is used.
+
+ If sd
is omitted, or is null
or undefined
, then the
+ return value is the same as n.toString()
.
+ If rm
is omitted or is null
or undefined
,
+ ROUNDING_MODE
is used.
+
+ See Errors for the treatment of other non-integer or out of range
+ sd
or rm
values.
+
+x = 45.6 +y = new BigNumber(x) +x.toPrecision() // '45.6' +y.toPrecision() // '45.6' +x.toPrecision(1) // '5e+1' +y.toPrecision(1) // '5e+1' +y.toPrecision(2, 0) // '4.6e+1' (ROUND_UP) +y.toPrecision(2, 1) // '4.5e+1' (ROUND_DOWN) +x.toPrecision(5) // '45.600' +y.toPrecision(5) // '45.600'+ + + +
.toString([base]) ⇒ string
base
: number: integer, 2
to 64
inclusive
+ Returns a string representing the value of this BigNumber in the specified base, or base
+ 10
if base
is omitted or is null
or
+ undefined
.
+
+ For bases above 10
, values from 10
to 35
are
+ represented by a-z
(as with Number.prototype.toString
),
+ 36
to 61
by A-Z
, and 62
and
+ 63
by $
and _
respectively.
+
+ If a base is specified the value is rounded according to the current
+ DECIMAL_PLACES
+ and ROUNDING_MODE
configuration.
+
+ If a base is not specified, and this BigNumber has a positive
+ exponent that is equal to or greater than the positive component of the
+ current EXPONENTIAL_AT
setting,
+ or a negative exponent equal to or less than the negative component of the
+ setting, then exponential notation is returned.
+
If base
is null
or undefined
it is ignored.
+ See Errors for the treatment of other non-integer or out of range
+ base
values.
+
+x = new BigNumber(750000) +x.toString() // '750000' +BigNumber.config({ EXPONENTIAL_AT: 5 }) +x.toString() // '7.5e+5' + +y = new BigNumber(362.875) +y.toString(2) // '101101010.111' +y.toString(9) // '442.77777777777777777778' +y.toString(32) // 'ba.s' + +BigNumber.config({ DECIMAL_PLACES: 4 }); +z = new BigNumber('1.23456789') +z.toString() // '1.23456789' +z.toString(10) // '1.2346'+ + + +
.trunc() ⇒ BigNumber
+ Returns a BigNumber whose value is the value of this BigNumber truncated to a whole number. +
++x = new BigNumber(123.456) +x.truncated() // '123' +y = new BigNumber(-12.3) +y.trunc() // '-12'+ + + +
.valueOf() ⇒ string
+ As toString
, but does not accept a base argument and includes the minus sign
+ for negative zero.
+
+x = new BigNumber('-0') +x.toString() // '0' +x.valueOf() // '-0' +y = new BigNumber('1.777e+457') +y.valueOf() // '1.777e+457'+ + + +
A BigNumber is an object with three properties:
+Property | +Description | +Type | +Value | +
---|---|---|---|
c | +coefficient* | +number[] |
+ Array of base 1e14 numbers |
+
e | +exponent | +number | +Integer, -1000000000 to 1000000000 inclusive |
+
s | +sign | +number | +-1 or 1 |
+
*significand
+The value of any of the three properties may also be null
.
+ From v2.0.0 of this library, the value of the coefficient of a BigNumber is stored in a
+ normalised base 100000000000000
floating point format, as opposed to the base
+ 10
format used in v1.x.x
+
+ This change means the properties of a BigNumber are now best considered to be read-only. + Previously it was acceptable to change the exponent of a BigNumber by writing to its exponent + property directly, but this is no longer recommended as the number of digits in the first + element of the coefficient array is dependent on the exponent, so the coefficient would also + need to be altered. +
++ Note that, as with JavaScript numbers, the original exponent and fractional trailing zeros are + not necessarily preserved. +
+x = new BigNumber(0.123) // '0.123' +x.toExponential() // '1.23e-1' +x.c // '1,2,3' +x.e // -1 +x.s // 1 + +y = new Number(-123.4567000e+2) // '-12345.67' +y.toExponential() // '-1.234567e+4' +z = new BigNumber('-123.4567000e+2') // '-12345.67' +z.toExponential() // '-1.234567e+4' +z.c // '1,2,3,4,5,6,7' +z.e // 4 +z.s // -1+ + + +
+ The table below shows how ±0
, NaN
and
+ ±Infinity
are stored.
+
+ | c | +e | +s | +
---|---|---|---|
±0 | +[0] |
+ 0 |
+ ±1 |
+
NaN | +null |
+ null |
+ null |
+
±Infinity | +null |
+ null |
+ ±1 |
+
+x = new Number(-0) // 0 +1 / x == -Infinity // true + +y = new BigNumber(-0) // '0' +y.c // '0' ( [0].toString() ) +y.e // 0 +y.s // -1+ + + +
+ The errors that are thrown are generic Error
objects with name
+ BigNumber Error.
+
+ The table below shows the errors that may be thrown if ERRORS
is
+ true
, and the action taken if ERRORS
is false
.
+
Method(s) | +ERRORS: true Throw BigNumber Error |
+ ERRORS: false Action on invalid argument |
+
---|---|---|
+
+ BigNumber |
+ number type has more than 15 significant digits |
+ Accept. | +
not a base... number | +Substitute NaN . |
+ |
base not an integer | +Truncate to integer. Ignore if not a number. |
+ |
base out of range | +Ignore. | +|
not a number* | +Substitute NaN . |
+ |
another |
+ not an object | +Ignore. | +
config |
+ DECIMAL_PLACES not an integer |
+ Truncate to integer. Ignore if not a number. |
+
DECIMAL_PLACES out of range |
+ Ignore. | +|
ROUNDING_MODE not an integer |
+ Truncate to integer. Ignore if not a number. |
+ |
ROUNDING_MODE out of range |
+ Ignore. | +|
EXPONENTIAL_AT not an integeror not [integer, integer] |
+ Truncate to integer(s). Ignore if not number(s). |
+ |
EXPONENTIAL_AT out of rangeor not [negative, positive] |
+ Ignore. | +|
RANGE not an integeror not [integer, integer] |
+ Truncate to integer(s). Ignore if not number(s). |
+ |
RANGE cannot be zero |
+ Ignore. | +|
RANGE out of rangeor not [negative, positive] |
+ Ignore. | +|
ERRORS not a booleanor binary digit |
+ Ignore. | +|
CRYPTO not a booleanor binary digit |
+ Ignore. | +|
CRYPTO crypto unavailable |
+ Ignore. | +|
MODULO_MODE not an integer |
+ Truncate to integer. Ignore if not a number. |
+ |
MODULO_MODE out of range |
+ Ignore. | +|
POW_PRECISION not an integer |
+ Truncate to integer. Ignore if not a number. |
+ |
POW_PRECISION out of range |
+ Ignore. | +|
FORMAT not an object |
+ Ignore. | +|
precision |
+ argument not a boolean or binary digit |
+ Ignore. | +
round |
+ decimal places not an integer | +Truncate to integer. Ignore if not a number. |
+
decimal places out of range | +Ignore. | +|
rounding mode not an integer | +Truncate to integer. Ignore if not a number. |
+ |
rounding mode out of range | +Ignore. | +|
shift |
+ argument not an integer | +Truncate to integer. Ignore if not a number. |
+
argument out of range | +Substitute ±Infinity .
+ | |
+ toExponential + toFixed + toFormat
+ |
+ decimal places not an integer | +Truncate to integer. Ignore if not a number. |
+
decimal places out of range | +Ignore. | +|
rounding mode not an integer | +Truncate to integer. Ignore if not a number. |
+ |
rounding mode out of range | +Ignore. | +|
toFraction |
+ max denominator not an integer | +Truncate to integer. Ignore if not a number. |
+
max denominator out of range | +Ignore. | +|
+ toDigits + toPrecision
+ |
+ precision not an integer | +Truncate to integer. Ignore if not a number. |
+
precision out of range | +Ignore. | +|
rounding mode not an integer | +Truncate to integer. Ignore if not a number. |
+ |
rounding mode out of range | +Ignore. | +|
toPower |
+ exponent not an integer | +Truncate to integer. Substitute NaN if not a number. |
+
exponent out of range | +Substitute ±Infinity .
+ |
+ |
toString |
+ base not an integer | +Truncate to integer. Ignore if not a number. |
+
base out of range | +Ignore. | +
*No error is thrown if the value is NaN
or 'NaN'.
+ The message of a BigNumber Error will also contain the name of the method from which + the error originated. +
+To determine if an exception is a BigNumber Error:
++try { + // ... +} catch (e) { + if ( e instanceof Error && e.name == 'BigNumber Error' ) { + // ... + } +}+ + + +
+ Some arbitrary-precision libraries retain trailing fractional zeros as they can indicate the + precision of a value. This can be useful but the results of arithmetic operations can be + misleading. +
++x = new BigDecimal("1.0") +y = new BigDecimal("1.1000") +z = x.add(y) // 2.1000 + +x = new BigDecimal("1.20") +y = new BigDecimal("3.45000") +z = x.multiply(y) // 4.1400000+
+ To specify the precision of a value is to specify that the value lies + within a certain range. +
+
+ In the first example, x
has a value of 1.0
. The trailing zero shows
+ the precision of the value, implying that it is in the range 0.95
to
+ 1.05
. Similarly, the precision indicated by the trailing zeros of y
+ indicates that the value is in the range 1.09995
to 1.10005
.
+
+ If we add the two lowest values in the ranges we have, 0.95 + 1.09995 = 2.04995
,
+ and if we add the two highest values we have, 1.05 + 1.10005 = 2.15005
, so the
+ range of the result of the addition implied by the precision of its operands is
+ 2.04995
to 2.15005
.
+
+ The result given by BigDecimal of 2.1000
however, indicates that the value is in
+ the range 2.09995
to 2.10005
and therefore the precision implied by
+ its trailing zeros may be misleading.
+
+ In the second example, the true range is 4.122744
to 4.157256
yet
+ the BigDecimal answer of 4.1400000
indicates a range of 4.13999995
+ to 4.14000005
. Again, the precision implied by the trailing zeros may be
+ misleading.
+
+ This library, like binary floating point and most calculators, does not retain trailing
+ fractional zeros. Instead, the toExponential
, toFixed
and
+ toPrecision
methods enable trailing zeros to be added if and when required.
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +287 +288 +289 +290 +291 +292 +293 +294 +295 +296 +297 +298 +299 +300 +301 +302 +303 +304 +305 +306 +307 +308 +309 +310 +311 +312 +313 +314 +315 +316 +317 +318 +319 +320 +321 +322 +323 +324 +325 +326 +327 +328 +329 +330 +331 +332 +333 +334 +335 +336 +337 +338 +339 +340 +341 +342 +343 +344 +345 +346 +347 +348 +349 +350 +351 +352 +353 +354 +355 +356 +357 +358 +359 +360 +361 +362 +363 +364 +365 +366 +367 +368 +369 +370 +371 +372 +373 +374 +375 +376 +377 +378 +379 +380 +381 +382 +383 +384 +385 +386 +387 +388 +389 +390 +391 +392 +393 +394 +395 +396 +397 +398 +399 +400 +401 +402 +403 +404 +405 +406 +407 +408 +409 +410 +411 +412 +413 +414 +415 +416 +417 +418 +419 +420 +421 +422 +423 +424 +425 +426 +427 +428 +429 +430 +431 +432 +433 +434 +435 +436 +437 +438 +439 +440 +441 +442 +443 +444 +445 +446 +447 +448 +449 +450 +451 +452 +453 +454 +455 +456 +457 +458 +459 +460 +461 +462 +463 +464 +465 +466 +467 +468 +469 +470 +471 +472 +473 +474 +475 +476 +477 +478 +479 +480 +481 +482 +483 +484 +485 +486 +487 +488 +489 +490 +491 +492 +493 +494 +495 +496 +497 +498 +499 +500 +501 +502 +503 +504 +505 +506 +507 +508 +509 +510 +511 +512 +513 +514 +515 +516 +517 +518 +519 +520 +521 +522 +523 +524 +525 +526 +527 +528 +529 +530 +531 +532 +533 +534 +535 +536 +537 +538 +539 +540 +541 +542 +543 +544 +545 +546 +547 +548 +549 +550 +551 +552 +553 +554 +555 +556 +557 +558 +559 +560 +561 +562 +563 +564 +565 +566 +567 +568 +569 +570 +571 +572 +573 +574 +575 +576 +577 +578 +579 +580 +581 +582 +583 +584 +585 +586 +587 +588 +589 +590 +591 +592 +593 +594 +595 +596 +597 +598 +599 +600 +601 +602 +603 +604 +605 +606 +607 +608 +609 +610 +611 +612 +613 +614 +615 +616 +617 +618 +619 +620 +621 +622 +623 +624 +625 +626 +627 +628 +629 +630 +631 +632 +633 +634 +635 +636 +637 +638 +639 +640 +641 +642 +643 +644 +645 +646 +647 +648 +649 +650 +651 +652 +653 +654 +655 +656 +657 +658 +659 +660 +661 +662 +663 +664 +665 +666 +667 +668 +669 +670 +671 +672 +673 +674 +675 +676 +677 +678 +679 +680 +681 +682 +683 +684 +685 +686 +687 +688 +689 +690 +691 +692 +693 +694 +695 +696 +697 +698 +699 +700 +701 +702 +703 +704 +705 +706 +707 +708 +709 +710 +711 +712 +713 +714 +715 +716 +717 +718 +719 +720 +721 +722 +723 +724 +725 +726 +727 +728 +729 +730 +731 +732 +733 +734 +735 +736 +737 +738 +739 +740 +741 +742 +743 +744 +745 +746 +747 +748 +749 +750 +751 +752 +753 +754 +755 +756 +757 +758 +759 +760 +761 +762 +763 +764 +765 +766 +767 +768 +769 +770 +771 +772 +773 +774 +775 +776 +777 +778 +779 +780 +781 +782 +783 +784 +785 +786 +787 +788 +789 +790 +791 +792 +793 +794 +795 +796 +797 +798 +799 +800 +801 +802 +803 +804 +805 +806 +807 +808 +809 +810 +811 +812 +813 +814 +815 +816 +817 +818 +819 +820 +821 +822 +823 +824 +825 +826 +827 +828 +829 +830 +831 +832 +833 +834 +835 +836 +837 +838 +839 +840 +841 +842 +843 +844 +845 +846 +847 +848 +849 +850 +851 +852 +853 +854 +855 +856 +857 +858 +859 +860 +861 +862 +863 +864 +865 +866 +867 | +1 + +1 + + + +1 + + +1 + + + +1 + +1 + + +1 +1 + + + + + + + +1 +1 + + +1 + + + + + + + + +1 +3 + + + + +3 +3 + + + +3 + + +1 + +2 + +2 +1 +1 + +1 +4 + +1 +1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +1 + + +2 +1 +1 +1 + + + +1 + +1 +1 +1 +1 + +1 + +1 +1 + + + +1 + + +1 +1 + + + +1 + + + + + + + + +1 + + +3 + +1 +1 +1 +1 + +1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +1 + + + + + + | /*! JSON v3.2.5 | http://bestiejs.github.io/json3 | Copyright 2012-2013, Kit Cambridge | http://kit.mit-license.org */ +;(function (window) { + // Convenience aliases. + var getClass = {}.toString, isProperty, forEach, undef; + + // Detect the `define` function exposed by asynchronous module loaders. The + // strict `define` check is necessary for compatibility with `r.js`. + var isLoader = typeof define === "function" && define.amd; + + // Detect native implementations. + var nativeJSON = typeof JSON == "object" && JSON; + + // Set up the JSON 3 namespace, preferring the CommonJS `exports` object if + // available. + var JSON3 = typeof exports == "object" && exports && !exports.nodeType && exports; + + Eif (JSON3 && nativeJSON) { + // Explicitly delegate to the native `stringify` and `parse` + // implementations in CommonJS environments. + JSON3.stringify = nativeJSON.stringify; + JSON3.parse = nativeJSON.parse; + } else { + // Export for web browsers, JavaScript engines, and asynchronous module + // loaders, using the global `JSON` object if available. + JSON3 = window.JSON = nativeJSON || {}; + } + + // Test the `Date#getUTC*` methods. Based on work by @Yaffle. + var isExtended = new Date(-3509827334573292); + try { + // The `getUTCFullYear`, `Month`, and `Date` methods return nonsensical + // results for certain dates in Opera >= 10.53. + isExtended = isExtended.getUTCFullYear() == -109252 && isExtended.getUTCMonth() === 0 && isExtended.getUTCDate() === 1 && + // Safari < 2.0.2 stores the internal millisecond time value correctly, + // but clips the values returned by the date methods to the range of + // signed 32-bit integers ([-2 ** 31, 2 ** 31 - 1]). + isExtended.getUTCHours() == 10 && isExtended.getUTCMinutes() == 37 && isExtended.getUTCSeconds() == 6 && isExtended.getUTCMilliseconds() == 708; + } catch (exception) {} + + // Internal: Determines whether the native `JSON.stringify` and `parse` + // implementations are spec-compliant. Based on work by Ken Snyder. + function has(name) { + Iif (has[name] != null) { + // Return cached feature test result. + return has[name]; + } + + var isSupported; + Iif (name == "bug-string-char-index") { + // IE <= 7 doesn't support accessing string characters using square + // bracket notation. IE 8 only supports this for primitives. + isSupported = "a"[0] != "a"; + } else if (name == "json") { + // Indicates whether both `JSON.stringify` and `JSON.parse` are + // supported. + isSupported = has("json-stringify") && has("json-parse"); + } else { + var value, serialized = '{"a":[1,true,false,null,"\\u0000\\b\\n\\f\\r\\t"]}'; + // Test `JSON.stringify`. + if (name == "json-stringify") { + var stringify = JSON3.stringify, stringifySupported = typeof stringify == "function" && isExtended; + Eif (stringifySupported) { + // A test function object with a custom `toJSON` method. + (value = function () { + return 1; + }).toJSON = value; + try { + stringifySupported = + // Firefox 3.1b1 and b2 serialize string, number, and boolean + // primitives as object literals. + stringify(0) === "0" && + // FF 3.1b1, b2, and JSON 2 serialize wrapped primitives as object + // literals. + stringify(new Number()) === "0" && + stringify(new String()) == '""' && + // FF 3.1b1, 2 throw an error if the value is `null`, `undefined`, or + // does not define a canonical JSON representation (this applies to + // objects with `toJSON` properties as well, *unless* they are nested + // within an object or array). + stringify(getClass) === undef && + // IE 8 serializes `undefined` as `"undefined"`. Safari <= 5.1.7 and + // FF 3.1b3 pass this test. + stringify(undef) === undef && + // Safari <= 5.1.7 and FF 3.1b3 throw `Error`s and `TypeError`s, + // respectively, if the value is omitted entirely. + stringify() === undef && + // FF 3.1b1, 2 throw an error if the given value is not a number, + // string, array, object, Boolean, or `null` literal. This applies to + // objects with custom `toJSON` methods as well, unless they are nested + // inside object or array literals. YUI 3.0.0b1 ignores custom `toJSON` + // methods entirely. + stringify(value) === "1" && + stringify([value]) == "[1]" && + // Prototype <= 1.6.1 serializes `[undefined]` as `"[]"` instead of + // `"[null]"`. + stringify([undef]) == "[null]" && + // YUI 3.0.0b1 fails to serialize `null` literals. + stringify(null) == "null" && + // FF 3.1b1, 2 halts serialization if an array contains a function: + // `[1, true, getClass, 1]` serializes as "[1,true,],". FF 3.1b3 + // elides non-JSON values from objects and arrays, unless they + // define custom `toJSON` methods. + stringify([undef, getClass, null]) == "[null,null,null]" && + // Simple serialization test. FF 3.1b1 uses Unicode escape sequences + // where character escape codes are expected (e.g., `\b` => `\u0008`). + stringify({ "a": [value, true, false, null, "\x00\b\n\f\r\t"] }) == serialized && + // FF 3.1b1 and b2 ignore the `filter` and `width` arguments. + stringify(null, value) === "1" && + stringify([1, 2], null, 1) == "[\n 1,\n 2\n]" && + // JSON 2, Prototype <= 1.7, and older WebKit builds incorrectly + // serialize extended years. + stringify(new Date(-8.64e15)) == '"-271821-04-20T00:00:00.000Z"' && + // The milliseconds are optional in ES 5, but required in 5.1. + stringify(new Date(8.64e15)) == '"+275760-09-13T00:00:00.000Z"' && + // Firefox <= 11.0 incorrectly serializes years prior to 0 as negative + // four-digit years instead of six-digit years. Credits: @Yaffle. + stringify(new Date(-621987552e5)) == '"-000001-01-01T00:00:00.000Z"' && + // Safari <= 5.1.5 and Opera >= 10.53 incorrectly serialize millisecond + // values less than 1000. Credits: @Yaffle. + stringify(new Date(-1)) == '"1969-12-31T23:59:59.999Z"'; + } catch (exception) { + stringifySupported = false; + } + } + isSupported = stringifySupported; + } + // Test `JSON.parse`. + if (name == "json-parse") { + var parse = JSON3.parse; + Eif (typeof parse == "function") { + try { + // FF 3.1b1, b2 will throw an exception if a bare literal is provided. + // Conforming implementations should also coerce the initial argument to + // a string prior to parsing. + Eif (parse("0") === 0 && !parse(false)) { + // Simple parsing test. + value = parse(serialized); + var parseSupported = value["a"].length == 5 && value["a"][0] === 1; + Eif (parseSupported) { + try { + // Safari <= 5.1.2 and FF 3.1b1 allow unescaped tabs in strings. + parseSupported = !parse('"\t"'); + } catch (exception) {} + Eif (parseSupported) { + try { + // FF 4.0 and 4.0.1 allow leading `+` signs and leading + // decimal points. FF 4.0, 4.0.1, and IE 9-10 also allow + // certain octal literals. + parseSupported = parse("01") !== 1; + } catch (exception) {} + } + Eif (parseSupported) { + try { + // FF 4.0, 4.0.1, and Rhino 1.7R3-R4 allow trailing decimal + // points. These environments, along with FF 3.1b1 and 2, + // also allow trailing commas in JSON objects and arrays. + parseSupported = parse("1.") !== 1; + } catch (exception) {} + } + } + } + } catch (exception) { + parseSupported = false; + } + } + isSupported = parseSupported; + } + } + return has[name] = !!isSupported; + } + has["bug-string-char-index"] = null; + has["json"] = null; + has["json-stringify"] = null; + has["json-parse"] = null; + + Iif (!has("json")) { + // Common `[[Class]]` name aliases. + var functionClass = "[object Function]"; + var dateClass = "[object Date]"; + var numberClass = "[object Number]"; + var stringClass = "[object String]"; + var arrayClass = "[object Array]"; + var booleanClass = "[object Boolean]"; + + // Detect incomplete support for accessing string characters by index. + var charIndexBuggy = has("bug-string-char-index"); + + // Define additional utility methods if the `Date` methods are buggy. + if (!isExtended) { + var floor = Math.floor; + // A mapping between the months of the year and the number of days between + // January 1st and the first of the respective month. + var Months = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334]; + // Internal: Calculates the number of days between the Unix epoch and the + // first day of the given month. + var getDay = function (year, month) { + return Months[month] + 365 * (year - 1970) + floor((year - 1969 + (month = +(month > 1))) / 4) - floor((year - 1901 + month) / 100) + floor((year - 1601 + month) / 400); + }; + } + + // Internal: Determines if a property is a direct property of the given + // object. Delegates to the native `Object#hasOwnProperty` method. + if (!(isProperty = {}.hasOwnProperty)) { + isProperty = function (property) { + var members = {}, constructor; + if ((members.__proto__ = null, members.__proto__ = { + // The *proto* property cannot be set multiple times in recent + // versions of Firefox and SeaMonkey. + "toString": 1 + }, members).toString != getClass) { + // Safari <= 2.0.3 doesn't implement `Object#hasOwnProperty`, but + // supports the mutable *proto* property. + isProperty = function (property) { + // Capture and break the object's prototype chain (see section 8.6.2 + // of the ES 5.1 spec). The parenthesized expression prevents an + // unsafe transformation by the Closure Compiler. + var original = this.__proto__, result = property in (this.__proto__ = null, this); + // Restore the original prototype chain. + this.__proto__ = original; + return result; + }; + } else { + // Capture a reference to the top-level `Object` constructor. + constructor = members.constructor; + // Use the `constructor` property to simulate `Object#hasOwnProperty` in + // other environments. + isProperty = function (property) { + var parent = (this.constructor || constructor).prototype; + return property in this && !(property in parent && this[property] === parent[property]); + }; + } + members = null; + return isProperty.call(this, property); + }; + } + + // Internal: A set of primitive types used by `isHostType`. + var PrimitiveTypes = { + 'boolean': 1, + 'number': 1, + 'string': 1, + 'undefined': 1 + }; + + // Internal: Determines if the given object `property` value is a + // non-primitive. + var isHostType = function (object, property) { + var type = typeof object[property]; + return type == 'object' ? !!object[property] : !PrimitiveTypes[type]; + }; + + // Internal: Normalizes the `for...in` iteration algorithm across + // environments. Each enumerated key is yielded to a `callback` function. + forEach = function (object, callback) { + var size = 0, Properties, members, property; + + // Tests for bugs in the current environment's `for...in` algorithm. The + // `valueOf` property inherits the non-enumerable flag from + // `Object.prototype` in older versions of IE, Netscape, and Mozilla. + (Properties = function () { + this.valueOf = 0; + }).prototype.valueOf = 0; + + // Iterate over a new instance of the `Properties` class. + members = new Properties(); + for (property in members) { + // Ignore all properties inherited from `Object.prototype`. + if (isProperty.call(members, property)) { + size++; + } + } + Properties = members = null; + + // Normalize the iteration algorithm. + if (!size) { + // A list of non-enumerable properties inherited from `Object.prototype`. + members = ["valueOf", "toString", "toLocaleString", "propertyIsEnumerable", "isPrototypeOf", "hasOwnProperty", "constructor"]; + // IE <= 8, Mozilla 1.0, and Netscape 6.2 ignore shadowed non-enumerable + // properties. + forEach = function (object, callback) { + var isFunction = getClass.call(object) == functionClass, property, length; + var hasProperty = !isFunction && typeof object.constructor != 'function' && isHostType(object, 'hasOwnProperty') ? object.hasOwnProperty : isProperty; + for (property in object) { + // Gecko <= 1.0 enumerates the `prototype` property of functions under + // certain conditions; IE does not. + if (!(isFunction && property == "prototype") && hasProperty.call(object, property)) { + callback(property); + } + } + // Manually invoke the callback for each non-enumerable property. + for (length = members.length; property = members[--length]; hasProperty.call(object, property) && callback(property)); + }; + } else if (size == 2) { + // Safari <= 2.0.4 enumerates shadowed properties twice. + forEach = function (object, callback) { + // Create a set of iterated properties. + var members = {}, isFunction = getClass.call(object) == functionClass, property; + for (property in object) { + // Store each property name to prevent double enumeration. The + // `prototype` property of functions is not enumerated due to cross- + // environment inconsistencies. + if (!(isFunction && property == "prototype") && !isProperty.call(members, property) && (members[property] = 1) && isProperty.call(object, property)) { + callback(property); + } + } + }; + } else { + // No bugs detected; use the standard `for...in` algorithm. + forEach = function (object, callback) { + var isFunction = getClass.call(object) == functionClass, property, isConstructor; + for (property in object) { + if (!(isFunction && property == "prototype") && isProperty.call(object, property) && !(isConstructor = property === "constructor")) { + callback(property); + } + } + // Manually invoke the callback for the `constructor` property due to + // cross-environment inconsistencies. + if (isConstructor || isProperty.call(object, (property = "constructor"))) { + callback(property); + } + }; + } + return forEach(object, callback); + }; + + // Public: Serializes a JavaScript `value` as a JSON string. The optional + // `filter` argument may specify either a function that alters how object and + // array members are serialized, or an array of strings and numbers that + // indicates which properties should be serialized. The optional `width` + // argument may be either a string or number that specifies the indentation + // level of the output. + if (!has("json-stringify")) { + // Internal: A map of control characters and their escaped equivalents. + var Escapes = { + 92: "\\\\", + 34: '\\"', + 8: "\\b", + 12: "\\f", + 10: "\\n", + 13: "\\r", + 9: "\\t" + }; + + // Internal: Converts `value` into a zero-padded string such that its + // length is at least equal to `width`. The `width` must be <= 6. + var leadingZeroes = "000000"; + var toPaddedString = function (width, value) { + // The `|| 0` expression is necessary to work around a bug in + // Opera <= 7.54u2 where `0 == -0`, but `String(-0) !== "0"`. + return (leadingZeroes + (value || 0)).slice(-width); + }; + + // Internal: Double-quotes a string `value`, replacing all ASCII control + // characters (characters with code unit values between 0 and 31) with + // their escaped equivalents. This is an implementation of the + // `Quote(value)` operation defined in ES 5.1 section 15.12.3. + var unicodePrefix = "\\u00"; + var quote = function (value) { + var result = '"', index = 0, length = value.length, isLarge = length > 10 && charIndexBuggy, symbols; + if (isLarge) { + symbols = value.split(""); + } + for (; index < length; index++) { + var charCode = value.charCodeAt(index); + // If the character is a control character, append its Unicode or + // shorthand escape sequence; otherwise, append the character as-is. + switch (charCode) { + case 8: case 9: case 10: case 12: case 13: case 34: case 92: + result += Escapes[charCode]; + break; + default: + if (charCode < 32) { + result += unicodePrefix + toPaddedString(2, charCode.toString(16)); + break; + } + result += isLarge ? symbols[index] : charIndexBuggy ? value.charAt(index) : value[index]; + } + } + return result + '"'; + }; + + // Internal: Recursively serializes an object. Implements the + // `Str(key, holder)`, `JO(value)`, and `JA(value)` operations. + var serialize = function (property, object, callback, properties, whitespace, indentation, stack) { + var value, className, year, month, date, time, hours, minutes, seconds, milliseconds, results, element, index, length, prefix, hasMembers, result; + try { + // Necessary for host object support. + value = object[property]; + } catch (exception) {} + if (typeof value == "object" && value) { + className = getClass.call(value); + if (className == dateClass && !isProperty.call(value, "toJSON")) { + if (value > -1 / 0 && value < 1 / 0) { + // Dates are serialized according to the `Date#toJSON` method + // specified in ES 5.1 section 15.9.5.44. See section 15.9.1.15 + // for the ISO 8601 date time string format. + if (getDay) { + // Manually compute the year, month, date, hours, minutes, + // seconds, and milliseconds if the `getUTC*` methods are + // buggy. Adapted from @Yaffle's `date-shim` project. + date = floor(value / 864e5); + for (year = floor(date / 365.2425) + 1970 - 1; getDay(year + 1, 0) <= date; year++); + for (month = floor((date - getDay(year, 0)) / 30.42); getDay(year, month + 1) <= date; month++); + date = 1 + date - getDay(year, month); + // The `time` value specifies the time within the day (see ES + // 5.1 section 15.9.1.2). The formula `(A % B + B) % B` is used + // to compute `A modulo B`, as the `%` operator does not + // correspond to the `modulo` operation for negative numbers. + time = (value % 864e5 + 864e5) % 864e5; + // The hours, minutes, seconds, and milliseconds are obtained by + // decomposing the time within the day. See section 15.9.1.10. + hours = floor(time / 36e5) % 24; + minutes = floor(time / 6e4) % 60; + seconds = floor(time / 1e3) % 60; + milliseconds = time % 1e3; + } else { + year = value.getUTCFullYear(); + month = value.getUTCMonth(); + date = value.getUTCDate(); + hours = value.getUTCHours(); + minutes = value.getUTCMinutes(); + seconds = value.getUTCSeconds(); + milliseconds = value.getUTCMilliseconds(); + } + // Serialize extended years correctly. + value = (year <= 0 || year >= 1e4 ? (year < 0 ? "-" : "+") + toPaddedString(6, year < 0 ? -year : year) : toPaddedString(4, year)) + + "-" + toPaddedString(2, month + 1) + "-" + toPaddedString(2, date) + + // Months, dates, hours, minutes, and seconds should have two + // digits; milliseconds should have three. + "T" + toPaddedString(2, hours) + ":" + toPaddedString(2, minutes) + ":" + toPaddedString(2, seconds) + + // Milliseconds are optional in ES 5.0, but required in 5.1. + "." + toPaddedString(3, milliseconds) + "Z"; + } else { + value = null; + } + } else if (typeof value.toJSON == "function" && ((className != numberClass && className != stringClass && className != arrayClass) || isProperty.call(value, "toJSON"))) { + // Prototype <= 1.6.1 adds non-standard `toJSON` methods to the + // `Number`, `String`, `Date`, and `Array` prototypes. JSON 3 + // ignores all `toJSON` methods on these objects unless they are + // defined directly on an instance. + value = value.toJSON(property); + } + } + if (callback) { + // If a replacement function was provided, call it to obtain the value + // for serialization. + value = callback.call(object, property, value); + } + if (value === null) { + return "null"; + } + className = getClass.call(value); + if (className == booleanClass) { + // Booleans are represented literally. + return "" + value; + } else if (className == numberClass) { + // JSON numbers must be finite. `Infinity` and `NaN` are serialized as + // `"null"`. + return value > -1 / 0 && value < 1 / 0 ? "" + value : "null"; + } else if (className == stringClass) { + // Strings are double-quoted and escaped. + return quote("" + value); + } + // Recursively serialize objects and arrays. + if (typeof value == "object") { + // Check for cyclic structures. This is a linear search; performance + // is inversely proportional to the number of unique nested objects. + for (length = stack.length; length--;) { + if (stack[length] === value) { + // Cyclic structures cannot be serialized by `JSON.stringify`. + throw TypeError(); + } + } + // Add the object to the stack of traversed objects. + stack.push(value); + results = []; + // Save the current indentation level and indent one additional level. + prefix = indentation; + indentation += whitespace; + if (className == arrayClass) { + // Recursively serialize array elements. + for (index = 0, length = value.length; index < length; hasMembers || (hasMembers = true), index++) { + element = serialize(index, value, callback, properties, whitespace, indentation, stack); + results.push(element === undef ? "null" : element); + } + result = hasMembers ? (whitespace ? "[\n" + indentation + results.join(",\n" + indentation) + "\n" + prefix + "]" : ("[" + results.join(",") + "]")) : "[]"; + } else { + // Recursively serialize object members. Members are selected from + // either a user-specified list of property names, or the object + // itself. + forEach(properties || value, function (property) { + var element = serialize(property, value, callback, properties, whitespace, indentation, stack); + if (element !== undef) { + // According to ES 5.1 section 15.12.3: "If `gap` {whitespace} + // is not the empty string, let `member` {quote(property) + ":"} + // be the concatenation of `member` and the `space` character." + // The "`space` character" refers to the literal space + // character, not the `space` {width} argument provided to + // `JSON.stringify`. + results.push(quote(property) + ":" + (whitespace ? " " : "") + element); + } + hasMembers || (hasMembers = true); + }); + result = hasMembers ? (whitespace ? "{\n" + indentation + results.join(",\n" + indentation) + "\n" + prefix + "}" : ("{" + results.join(",") + "}")) : "{}"; + } + // Remove the object from the traversed object stack. + stack.pop(); + return result; + } + }; + + // Public: `JSON.stringify`. See ES 5.1 section 15.12.3. + JSON3.stringify = function (source, filter, width) { + var whitespace, callback, properties, className; + if (typeof filter == "function" || typeof filter == "object" && filter) { + if ((className = getClass.call(filter)) == functionClass) { + callback = filter; + } else if (className == arrayClass) { + // Convert the property names array into a makeshift set. + properties = {}; + for (var index = 0, length = filter.length, value; index < length; value = filter[index++], ((getClass.call(value) == stringClass || getClass.call(value) == numberClass) && (properties[value] = 1))); + } + } + if (width) { + if ((className = getClass.call(width)) == numberClass) { + // Convert the `width` to an integer and create a string containing + // `width` number of space characters. + if ((width -= width % 1) > 0) { + for (whitespace = "", width > 10 && (width = 10); whitespace.length < width; whitespace += " "); + } + } else if (className == stringClass) { + whitespace = width.length <= 10 ? width : width.slice(0, 10); + } + } + // Opera <= 7.54u2 discards the values associated with empty string keys + // (`""`) only if they are used directly within an object member list + // (e.g., `!("" in { "": 1})`). + return serialize("", (value = {}, value[""] = source, value), callback, properties, whitespace, "", []); + }; + } + + // Public: Parses a JSON source string. + if (!has("json-parse")) { + var fromCharCode = String.fromCharCode; + + // Internal: A map of escaped control characters and their unescaped + // equivalents. + var Unescapes = { + 92: "\\", + 34: '"', + 47: "/", + 98: "\b", + 116: "\t", + 110: "\n", + 102: "\f", + 114: "\r" + }; + + // Internal: Stores the parser state. + var Index, Source; + + // Internal: Resets the parser state and throws a `SyntaxError`. + var abort = function() { + Index = Source = null; + throw SyntaxError(); + }; + + // Internal: Returns the next token, or `"$"` if the parser has reached + // the end of the source string. A token may be a string, number, `null` + // literal, or Boolean literal. + var lex = function () { + var source = Source, length = source.length, value, begin, position, isSigned, charCode; + while (Index < length) { + charCode = source.charCodeAt(Index); + switch (charCode) { + case 9: case 10: case 13: case 32: + // Skip whitespace tokens, including tabs, carriage returns, line + // feeds, and space characters. + Index++; + break; + case 123: case 125: case 91: case 93: case 58: case 44: + // Parse a punctuator token (`{`, `}`, `[`, `]`, `:`, or `,`) at + // the current position. + value = charIndexBuggy ? source.charAt(Index) : source[Index]; + Index++; + return value; + case 34: + // `"` delimits a JSON string; advance to the next character and + // begin parsing the string. String tokens are prefixed with the + // sentinel `@` character to distinguish them from punctuators and + // end-of-string tokens. + for (value = "@", Index++; Index < length;) { + charCode = source.charCodeAt(Index); + if (charCode < 32) { + // Unescaped ASCII control characters (those with a code unit + // less than the space character) are not permitted. + abort(); + } else if (charCode == 92) { + // A reverse solidus (`\`) marks the beginning of an escaped + // control character (including `"`, `\`, and `/`) or Unicode + // escape sequence. + charCode = source.charCodeAt(++Index); + switch (charCode) { + case 92: case 34: case 47: case 98: case 116: case 110: case 102: case 114: + // Revive escaped control characters. + value += Unescapes[charCode]; + Index++; + break; + case 117: + // `\u` marks the beginning of a Unicode escape sequence. + // Advance to the first character and validate the + // four-digit code point. + begin = ++Index; + for (position = Index + 4; Index < position; Index++) { + charCode = source.charCodeAt(Index); + // A valid sequence comprises four hexdigits (case- + // insensitive) that form a single hexadecimal value. + if (!(charCode >= 48 && charCode <= 57 || charCode >= 97 && charCode <= 102 || charCode >= 65 && charCode <= 70)) { + // Invalid Unicode escape sequence. + abort(); + } + } + // Revive the escaped character. + value += fromCharCode("0x" + source.slice(begin, Index)); + break; + default: + // Invalid escape sequence. + abort(); + } + } else { + if (charCode == 34) { + // An unescaped double-quote character marks the end of the + // string. + break; + } + charCode = source.charCodeAt(Index); + begin = Index; + // Optimize for the common case where a string is valid. + while (charCode >= 32 && charCode != 92 && charCode != 34) { + charCode = source.charCodeAt(++Index); + } + // Append the string as-is. + value += source.slice(begin, Index); + } + } + if (source.charCodeAt(Index) == 34) { + // Advance to the next character and return the revived string. + Index++; + return value; + } + // Unterminated string. + abort(); + default: + // Parse numbers and literals. + begin = Index; + // Advance past the negative sign, if one is specified. + if (charCode == 45) { + isSigned = true; + charCode = source.charCodeAt(++Index); + } + // Parse an integer or floating-point value. + if (charCode >= 48 && charCode <= 57) { + // Leading zeroes are interpreted as octal literals. + if (charCode == 48 && ((charCode = source.charCodeAt(Index + 1)), charCode >= 48 && charCode <= 57)) { + // Illegal octal literal. + abort(); + } + isSigned = false; + // Parse the integer component. + for (; Index < length && ((charCode = source.charCodeAt(Index)), charCode >= 48 && charCode <= 57); Index++); + // Floats cannot contain a leading decimal point; however, this + // case is already accounted for by the parser. + if (source.charCodeAt(Index) == 46) { + position = ++Index; + // Parse the decimal component. + for (; position < length && ((charCode = source.charCodeAt(position)), charCode >= 48 && charCode <= 57); position++); + if (position == Index) { + // Illegal trailing decimal. + abort(); + } + Index = position; + } + // Parse exponents. The `e` denoting the exponent is + // case-insensitive. + charCode = source.charCodeAt(Index); + if (charCode == 101 || charCode == 69) { + charCode = source.charCodeAt(++Index); + // Skip past the sign following the exponent, if one is + // specified. + if (charCode == 43 || charCode == 45) { + Index++; + } + // Parse the exponential component. + for (position = Index; position < length && ((charCode = source.charCodeAt(position)), charCode >= 48 && charCode <= 57); position++); + if (position == Index) { + // Illegal empty exponent. + abort(); + } + Index = position; + } + // Coerce the parsed value to a JavaScript number. + return +source.slice(begin, Index); + } + // A negative sign may only precede numbers. + if (isSigned) { + abort(); + } + // `true`, `false`, and `null` literals. + if (source.slice(Index, Index + 4) == "true") { + Index += 4; + return true; + } else if (source.slice(Index, Index + 5) == "false") { + Index += 5; + return false; + } else if (source.slice(Index, Index + 4) == "null") { + Index += 4; + return null; + } + // Unrecognized token. + abort(); + } + } + // Return the sentinel `$` character if the parser has reached the end + // of the source string. + return "$"; + }; + + // Internal: Parses a JSON `value` token. + var get = function (value) { + var results, hasMembers; + if (value == "$") { + // Unexpected end of input. + abort(); + } + if (typeof value == "string") { + if ((charIndexBuggy ? value.charAt(0) : value[0]) == "@") { + // Remove the sentinel `@` character. + return value.slice(1); + } + // Parse object and array literals. + if (value == "[") { + // Parses a JSON array, returning a new JavaScript array. + results = []; + for (;; hasMembers || (hasMembers = true)) { + value = lex(); + // A closing square bracket marks the end of the array literal. + if (value == "]") { + break; + } + // If the array literal contains elements, the current token + // should be a comma separating the previous element from the + // next. + if (hasMembers) { + if (value == ",") { + value = lex(); + if (value == "]") { + // Unexpected trailing `,` in array literal. + abort(); + } + } else { + // A `,` must separate each array element. + abort(); + } + } + // Elisions and leading commas are not permitted. + if (value == ",") { + abort(); + } + results.push(get(value)); + } + return results; + } else if (value == "{") { + // Parses a JSON object, returning a new JavaScript object. + results = {}; + for (;; hasMembers || (hasMembers = true)) { + value = lex(); + // A closing curly brace marks the end of the object literal. + if (value == "}") { + break; + } + // If the object literal contains members, the current token + // should be a comma separator. + if (hasMembers) { + if (value == ",") { + value = lex(); + if (value == "}") { + // Unexpected trailing `,` in object literal. + abort(); + } + } else { + // A `,` must separate each object member. + abort(); + } + } + // Leading commas are not permitted, object property names must be + // double-quoted strings, and a `:` must separate each property + // name and value. + if (value == "," || typeof value != "string" || (charIndexBuggy ? value.charAt(0) : value[0]) != "@" || lex() != ":") { + abort(); + } + results[value.slice(1)] = get(lex()); + } + return results; + } + // Unexpected token encountered. + abort(); + } + return value; + }; + + // Internal: Updates a traversed object member. + var update = function(source, property, callback) { + var element = walk(source, property, callback); + if (element === undef) { + delete source[property]; + } else { + source[property] = element; + } + }; + + // Internal: Recursively traverses a parsed JSON object, invoking the + // `callback` function for each value. This is an implementation of the + // `Walk(holder, name)` operation defined in ES 5.1 section 15.12.2. + var walk = function (source, property, callback) { + var value = source[property], length; + if (typeof value == "object" && value) { + // `forEach` can't be used to traverse an array in Opera <= 8.54 + // because its `Object#hasOwnProperty` implementation returns `false` + // for array indices (e.g., `![1, 2, 3].hasOwnProperty("0")`). + if (getClass.call(value) == arrayClass) { + for (length = value.length; length--;) { + update(value, length, callback); + } + } else { + forEach(value, function (property) { + update(value, property, callback); + }); + } + } + return callback.call(source, property, value); + }; + + // Public: `JSON.parse`. See ES 5.1 section 15.12.2. + JSON3.parse = function (source, callback) { + var result, value; + Index = 0; + Source = "" + source; + result = get(lex()); + // If a JSON string contains multiple tokens, it is invalid. + if (lex() != "$") { + abort(); + } + // Reset the parser state. + Index = Source = null; + return callback && getClass.call(callback) == functionClass ? walk((value = {}, value[""] = result, value), "", callback) : result; + }; + } + } + + // Export for asynchronous module loaders. + Iif (isLoader) { + define(function () { + return JSON3; + }); + } +}(this)); + |